AadSupportFunctions.psm1
<#
.SYNOPSIS Manually consent to permissions .DESCRIPTION Manually consent to permissions. This works for Admin consent, user consent, delegated permissions, and application permissions. EXAMPLES # Add Admin Consent for a Delegated Permission Add-AadConsent -ClientId b330d711-77c4-463b-a391-6b3fbef74ffd -ResourceId "Microsoft Graph" -PermissionType Delegated -ClaimValue User.Read # Add User Consent for single user (new OAuth2PermissionGrant) Add-AadConsent -ClientId b330d711-77c4-463b-a391-6b3fbef74ffd -ResourceId "Microsoft Graph" -PermissionType Delegated -ClaimValue User.Read -UserId john@contoso.com # Update User Consent for all users (existing OAuth2PermissionGrant) Add-AadConsent -ClientId b330d711-77c4-463b-a391-6b3fbef74ffd -ResourceId "Microsoft Graph" -ConsentType User -PermissionType Delegated -ClaimValue Directory.AccessAsUser.All # Add Admin Consent for Application Permission Add-AadConsent -ClientId b330d711-77c4-463b-a391-6b3fbef74ffd -ResourceId "Microsoft Graph" -PermissionType Application -ClaimValue User.Read.All .PARAMETER ClientId Identifier for the Enterprise App (ServicePrincipal) we will be consenting to. .PARAMETER ResourceId Identifier for the Resource (ServicePrincipal) we will be consenting permissions to. .PARAMETER UserId If you set a UserId, then it will use User Consent .PARAMETER ClaimValue This is the permission you want to add. We also accept multiple values here as space delimitted list. .PARAMETER ConsentType Specify if this is a 'Admin' consent or 'User' consent Default value if none is specified is 'Admin' .PARAMETER PermissionType Specify if this is a 'Delegated' permission or 'Application' permission .NOTES General notes #> function Add-AadConsent { [CmdletBinding(DefaultParameterSetName="DefaultSet")] param ( [Parameter(mandatory=$true, Position=0, ValueFromPipeline = $true)] [string]$ClientId, [Parameter(mandatory=$true)] [string]$ResourceId, [Parameter(mandatory=$true)] [string]$ClaimValue, [Parameter(mandatory=$false)] [ValidateSet('Admin','User')] $ConsentType="Admin", [Parameter(mandatory=$false)] [ValidateSet('Delegated','Application')] $PermissionType="Delegated", [string]$UserId ) #Required but ignored parameter $Expires = (Get-AadDateTime -AddMonths 12) # Parameter validations if($UserId) { $ConsentType = "User" } if($UserId -and $PermissionType -eq "Application") { throw "You can't provide a UserId and set PermissionType to 'Application'" } $TenantDomain = $Global:AadSupport.Session.TenantId # -------------------------------------------------- # Check if signed in user is Global Admin (As only global admins can perform admin consent) $isGlobalAdmin = Invoke-AadCommand -Command { Param( $AccountId ) $SignedInUser = Get-AzureAdUser -ObjectId $AccountId $SignedInUserObjectId = $SignedInUser.ObjectId $GlobalAdminRoleIds = (Get-AzureAdDirectoryRole | where { $_.displayName -eq 'Global Administrator' -or $_.displayName -eq 'Application Administrator' }).ObjectId foreach($GlobalAdminRoleId in $GlobalAdminRoleIds) { if( (Get-AzureAdDirectoryRoleMember -ObjectId $GlobalAdminRoleId).ObjectId -contains $SignedInUserObjectId ) { return $true } } } -Parameters $Global:AadSupport.Session.AccountId if (-not $isGlobalAdmin) { Write-Host "Your account '$($Global:AadSupport.Session.AccountId)' is not a Global Admin in $TenantDomain." throw "Exception: 'Global Administrator' or 'Application Administrator' role REQUIRED\r\n * Application Administrator can only perform consent for delegated permissions" } # ++++++++++++++++++++++++ # GET CLIENT ID # ++++++++++++++++++++++++ $Client = $null $client = Get-AadServicePrincipal -Id $ClientId $ClientId = $client.ObjectId Write-Verbose "ClientId: $ClientId" # ++++++++++++++++++++++++ # GET RESOURCE ID # ++++++++++++++++++++++++ $Resource = $null $resource = Get-AadServicePrincipal -Id $ResourceId $ResourceId = $resource.ObjectId Write-Verbose "ResourceId: $ResourceId" # ++++++++++++++++++++++++ # GET PRINCIPAL ID # ++++++++++++++++++++++++ $User = $null $PrincipalId = $null if($UserId) { $User = Invoke-AadCommand -Command { Param($UserId) Get-AzureADUser -ObjectId $UserId } -Parameters $UserId $PrincipalId = ($User).ObjectId if (-not $PrincipalId) { throw "'$UserId' does not exist in '$TenantDomain'" } } Write-Verbose "PrincipalId: $PrincipalId" # START $RequestedPermissions = $ClaimValue.Split(" ").Split(";").Split(",") $ConsentedPermissions = $null if($PermissionType -eq "Delegated") { # Check if OAuth2PermissionGrant already exists if(!$ConsentedPermissions) { $ConsentedPermissions = Get-AadConsent ` -ClientId $ClientId ` -ResourceId $ResourceId ` -ConsentType $ConsentType ` -PermissionType $PermissionType ` -UserId $PrincipalId } Write-Verbose "Showing Consented Permissions..." if($ConsentedPermissions) { Write-Verbose ($ConsentedPermissions|ConvertTo-Json) } else { Write-Verbose "NONE." } foreach($RequestedPermission in $RequestedPermissions) { Write-Verbose "Checking RequestedPermission $RequestedPermission" # Oauth2PermissionGrant exists. # Lets go update it. if($ConsentedPermissions) { foreach($Permission in $ConsentedPermissions) { Write-Verbose "Checking ConsentedPermission $($Permission.ClaimValue)" if($Permission.ClaimValue) { $CurrentScopes = $Permission.ClaimValue.Split(" ") } else { $CurrentScopes = "" } # Skip of Permission is already there if($CurrentScopes -contains $RequestedPermission) { Write-Host "$RequestedPermission already exists on OAuth2PermissinGrant $($Permission.Id)" continue } $NewScopes += $CurrentScopes $NewScopes += $RequestedPermission $MsGraphUrl = "$($Global:AadSupport.Resources.MsGraph)/beta/oauth2PermissionGrants/$($Permission.Id)" $JsonBody = @{ scope = [string]::Join(' ', $NewScopes) } | ConvertTo-Json -Compress Write-Verbose "JsonBody..." Write-Verbose $JsonBody Write-Verbose "Updating existing OAuth2PermissionGrant" Invoke-AadProtectedApi ` -Client $Global:AadSupport.Clients.AzureAdPowershell.ClientId ` -Resource $Global:AadSupport.Resources.MsGraph ` -Endpoint $MsGraphUrl -Method PATCH ` -Body $JsonBody } } # Oauth2PermissionGrant does not exist. # Lets go create one. else { # Lets start building the OAuth2PermissionGrant $newPermissionGrant = @{} $newPermissionGrant.Add("startTime", [System.DateTime]::UtcNow.AddMinutes(-5).ToString("o")) $newPermissionGrant.Add("expiryTime", $Expires) $newPermissionGrant.Add("clientId", $ClientId) $newPermissionGrant.Add("resourceId", $ResourceId) $newPermissionGrant.Add("scope", $ClaimValue) if ($PrincipalId) { $newPermissionGrant.Add("principalId", $PrincipalId) $newPermissionGrant.Add("consentType", "Principal") } else { $newPermissionGrant.Add("consentType", "AllPrincipals") } $MsGraphUrl = "$($Global:AadSupport.Resources.MsGraph)/beta/oauth2PermissionGrants" $JsonBody = $newPermissionGrant | ConvertTo-Json -Compress Write-Verbose "JsonBody..." Write-Verbose $JsonBody Write-Verbose "Adding OAuth2PermissionGrant" Invoke-AadProtectedApi ` -Client $Global:AadSupport.Clients.AzureAdPowershell.ClientId ` -Resource $Global:AadSupport.Resources.MsGraph ` -Endpoint $MsGraphUrl -Method POST ` -Body $JsonBody } } } if($PermissionType -eq "Application") { foreach($RequestedPermission in $RequestedPermissions) { $ConsentedPermissions = Get-AadConsent ` -ClientId $ClientId ` -ResourceId $ResourceId ` -PermissionType $PermissionType ` -ClaimValue $RequestedPermission if($ConsentedPermissions) { Write-Host "$RequestedPermission already exists!" continue } else { # ------------------------------------------------ # ADD ROLES (App Role Assignment) $AppRoles = $resource.AppRoles | Sort-Object Value # Get the Role ID $RoleId = ($AppRoles | where {$_.Value -eq $RequestedPermission}).Id if($RoleId) { Write-Verbose "Adding AppRole assignment" Invoke-AadCommand -Command { Param($Params) New-AzureADServiceAppRoleAssignment -ObjectId $Params.ClientObjectId -Id $Params.RoleId -ResourceId $Params.ResourceObjectId -PrincipalId $Params.ClientObjectId } -Parameters @{ ClientObjectId = $ClientId RoleId = $RoleId ResourceObjectId = $ResourceId } } else { Write-Host "'$RoleId' not found on $($resource.DisplayName)" -ForegroundColor Yellow } } } } } <# .SYNOPSIS Connect to the Azure AD Support PowerShell module. This will use the same sign-in session to access different Microsoft resources. .DESCRIPTION Connect to the Azure AD Support PowerShell module. This will use the same sign-in session to access different Microsoft resources. Example 1: Log in with your admin account... Connect-AadSupport Example 2: Log in to a specific tenant... Connect-AadSupport -TenantId contoso.onmicrosoft.com Example 3: Log in to a specific instance... Connect-AadSupport -AzureEnvironmentName AzureCloud Connect-AadSupport -AzureEnvironmentName AzureGermanyCloud Connect-AadSupport -AzureEnvironmentName AzureChinaCloud Connect-AadSupport -AzureEnvironmentName AzureUSGovernment .PARAMETER TenantId Provide the Tenant ID you want to authenticate to. .PARAMETER AccountId Provide the Account ID you want to authenticate with. .PARAMETER AzureEnvironmentName Specifies the name of the Azure environment. The acceptable values for this parameter are: - AzureCloud - AzureChinaCloud - AzureUSGovernment - AzureGermanyCloud The default value is AzureCloud. .PARAMETER LogPath The path where the log file for this PowerShell session is written to. Provide a value here if you need to deviate from the default PowerShell log file location. .NOTES General notes #> function Connect-AadSupport { [CmdletBinding(DefaultParameterSetName='DefaultSet')] param ( [string]$TenantId = "Common", [string]$AccountId, [Parameter(mandatory=$true, ParameterSetName = 'UserPrincipalSet')] [string]$Password, [ValidateSet("AzureCloud","AzureGermanyCloud","AzureUSGovernment","AzureChinaCloud")] [string]$AzureEnvironmentName = "AzureCloud", [Parameter(mandatory=$true, ParameterSetName = 'ServicePrincipalSet')] [string]$ClientId, [Parameter(mandatory=$true, ParameterSetName = 'ServicePrincipalSet')] [string]$ClientSecret, [Switch]$EnableLogging ) New-AadSupportSession if($EnableLogging) { $Global:AadSupport.Logging.Enabled = $true } else { $Global:AadSupport.Logging.Enabled = $false } if($LogPath) { $Global:AadSupport.Logging.Path = $LogPath } switch($AzureEnvironmentName) { "AzureCloud" { $Global:AadSupport.Session.AadInstance = "https://login.microsoftonline.com" $Global:AadSupport.Resources.AadGraph = "https://graph.windows.net" $Global:AadSupport.Resources.MsGraph = "https://graph.microsoft.com" $Global:AadSupport.Resources.AzureRmApi = "https://management.azure.com" $Global:AadSupport.Resources.AzureServiceApi = "https://management.core.windows.net" $Global:AadSupport.Resources.KeyVault = "https://vault.azure.net" } "AzureChinaCloud" { $Global:AadSupport.Session.AadInstance = "https://login.chinacloudapi.cn" #https://login.partner.microsoftonline.cn $Global:AadSupport.Resources.AadGraph = "https://graph.chinacloudapi.cn" $Global:AadSupport.Resources.MsGraph = "https://microsoftgraph.chinacloudapi.cn" $Global:AadSupport.Resources.AzureRmApi = "https://management.chinacloudapi.cn" $Global:AadSupport.Resources.AzureServiceApi = "https://management.core.chinacloudapi.cn" $Global:AadSupport.Resources.KeyVault = "https://vault.azure.cn" } "AzureUSGovernment" { $Global:AadSupport.Session.AadInstance = "https://login.microsoftonline.us" $Global:AadSupport.Resources.AadGraph = "https://graph.windows.net" $Global:AadSupport.Resources.MsGraph = "https://graph.microsoft.us" #DOD https://dod-graph.microsoft.us $Global:AadSupport.Resources.AzureRmApi = "https://management.usgovcloudapi.net/" $Global:AadSupport.Resources.AzureServiceApi = "https://management.core.usgovcloudapi.net/" $Global:AadSupport.Resources.KeyVault = "https://vault.usgovcloudapi.net" } "AzureGermanyCloud" { $Global:AadSupport.Session.AadInstance = "https://login.microsoftonline.de" $Global:AadSupport.Resources.AadGraph = "https://graph.cloudapi.de/" $Global:AadSupport.Resources.MsGraph = "https://graph.microsoft.de" $Global:AadSupport.Resources.AzureRmApi = "https://management.microsoftazure.de/" $Global:AadSupport.Resources.AzureServiceApi = "https://management.core.cloudapi.de/" $Global:AadSupport.Resources.KeyVault = "https://vault.microsoftazure.de" } default { $Global:AadSupport.Session.AadInstance = "https://login.microsoftonline.com" $Global:AadSupport.Resources.AadGraph = "https://graph.windows.net" $Global:AadSupport.Resources.MsGraph = "https://graph.microsoft.com" $Global:AadSupport.Resources.AzureRmApi = "https://management.azure.com" $Global:AadSupport.Resources.AzureServiceApi = "https://management.core.windows.net" $Global:AadSupport.Resources.KeyVault = "https://vault.azure.net" } } Write-Host "" Write-Host "Connecting to Azure AD PowerShell (Connect-AzureAD)" Write-Host "and Connecting to Azure PowerShell (Connect-AzAccount)" Write-Host "" # Connect to Azure AD PowerShell # Get Current Session Info if(!$AccountId) { $AccountId = $Global:AadSupport.Session.AccountId } # if still null, We want to pass an empty AccountId if(!$AccountId) { $AccountId = "" } try { if(!$AccountId) { $Prompt = "Always" } else { $Prompt = "Auto" } # Get Token for AAD Graph to be used for Azure AD PowerShell $token = $null if($Password) { $token = Get-AadTokenUsingAdal ` -ResourceId $Global:AadSupport.Resources.AadGraph ` -ClientId $Global:AadSupport.Clients.AzureAdPowershell.ClientId ` -Instance $Global:AadSupport.Session.AadInstance ` -Tenant $TenantId ` -UserId $AccountId ` -Password $Password ` -UseResourceOwnerPasswordCredential ` -SkipServicePrincipalSearch ` -HideOutput $Global:AadSupport.Session.AccountId = $token.DisplayableId $Global:AadSupport.Session.TenantId = $token.TenantId $AzureToken = Get-AadTokenUsingAdal ` -ResourceId $Global:AadSupport.Resources.AzureRmApi ` -ClientId $Global:AadSupport.Clients.AzurePowershell.ClientId ` -Instance $Global:AadSupport.Session.AadInstance ` -Tenant $TenantId ` -UserId $Global:AadSupport.Session.AccountId ` -Password $Password ` -UseResourceOwnerPasswordCredential ` -SkipServicePrincipalSearch ` -HideOutput } elseif($ClientSecret) { $token = Get-AadTokenUsingAdal ` -ResourceId $Global:AadSupport.Resources.AadGraph ` -ClientId $ClientId ` -ClientSecret $ClientSecret ` -UseClientCredential ` -Instance $Global:AadSupport.Session.AadInstance ` -Tenant $TenantId ` -SkipServicePrincipalSearch ` -HideOutput $AzureToken = Get-AadTokenUsingAdal ` -ResourceId $Global:AadSupport.Resources.AzureRmApi ` -ClientId $ClientId ` -ClientSecret $ClientSecret ` -UseClientCredential ` -Instance $Global:AadSupport.Session.AadInstance ` -Tenant $TenantId ` -SkipServicePrincipalSearch ` -HideOutput } else { $token = Get-AadTokenUsingAdal ` -ResourceId $Global:AadSupport.Resources.AadGraph ` -ClientId $Global:AadSupport.Clients.AzureAdPowershell.ClientId ` -Redirect $Global:AadSupport.Clients.AzureAdPowershell.RedirectUri ` -Instance $Global:AadSupport.Session.AadInstance ` -Tenant $TenantId ` -UserId $AccountId ` -Prompt $Prompt ` -SkipServicePrincipalSearch ` -HideOutput $Global:AadSupport.Session.AccountId = $token.DisplayableId $Global:AadSupport.Session.TenantId = $token.TenantId $token = Get-AadTokenUsingAdal ` -ResourceId $Global:AadSupport.Resources.AzureRmApi ` -ClientId $Global:AadSupport.Clients.AzurePowershell.ClientId ` -Redirect $Global:AadSupport.Clients.AzurePowershell.RedirectUri ` -UserId $Global:AadSupport.Session.AccountId ` -Instance $Global:AadSupport.Session.AadInstance ` -Tenant $Global:AadSupport.Session.TenantId ` -Prompt Never ` -SkipServicePrincipalSearch ` -HideOutput } # If we didnt get a token lets stop if(!$token) { Write-Host "Failed to authenticate. User most likely cancelled." -Foreground Red return } Write-Verbose "Token AuthenticationResult..." Write-Verbose $($token | ConvertTo-Json) $Global:AadSupport.Session.AccountId = $token.DisplayableId $Global:AadSupport.Session.TenantId = $token.TenantId $Global:AadSupport.Session.Active = $true } catch { throw $_ } } <# .SYNOPSIS Converts a single Base64Encoded certificate (Not Chained Ceritificate) to a Custom PSObject for easy readability .DESCRIPTION Converts a single Base64Encoded certificate (Not Chained Ceritificate) to a Custom PSObject for easy readability .PARAMETER Base64String The Base64Encoded Certificate .EXAMPLE ConvertFrom-AadBase64Certificate -Base64String "MIIHkDCCBnigAwIBAgIRALENqydLHXg/u+VM04+dg2QwDQYJKoZIhvcNAQELBQAwgZ..." .NOTES General notes #> function ConvertFrom-AadBase64Certificate { [cmdletbinding(DefaultParameterSetName="Default")] param( [parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,ParameterSetName="Default")] [String] $Base64String, [parameter(Mandatory=$true,Position=0,ValueFromPipeline=$true,ParameterSetName="Path")] [String] $Path, [String]$Password ) if($Path -and ![System.IO.Path]::IsPathRooted($Path)) { $LocalPath = Get-Location $Path = "$LocalPath\$Path" } if($Path) { $bytes = [System.IO.File]::ReadAllBytes("$path") } if($Base64String) { # Sometimes a Base64Encoded Cert has been Base64Encoded again (Chained Certs) if(-not $Base64String.StartsWith("MII") -and -not $Base64String.StartsWith("-----BEGIN")) { $Base64String = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($Base64String)); } $bytes = [System.Text.Encoding]::UTF8.GetBytes($Base64String) } $cert = new-object System.Security.Cryptography.X509Certificates.X509Certificate2 -ArgumentList @($bytes,$Password) $kid = ConvertFrom-AadThumbprintToBase64String -Thumbprint $cert.Thumbprint $Properties = @{ Kid = $kid; Thumbprint = $cert.Thumbprint; NotAfter = $cert.NotAfter; NotBefore = $cert.NotBefore; Subject = $cert.Subject; Issuer = $cert.Issuer; Certificate = $cert; } $Object = new-object PSObject -Property $Properties return $Object } <# .SYNOPSIS Converts a Base64Encoded Thumbprint or also known as Key Identifier (Kid) back to its original Thumbprint value .DESCRIPTION Converts a Base64Encoded Thumbprint or also known as Key Identifier (Kid) back to its original Thumbprint value .PARAMETER Base64String Base64Encoded version of the Thumbprint .EXAMPLE ConvertFrom-AadBase64StringToThumbprint -Base64String 'z79RnGljTQa9Zh4ZjLq6UaB4eUM=' Output... CF-BF-51-9C-69-63-4D-06-BD-66-1E-19-8C-BA-BA-51-A0-78-79-43 .NOTES #> Function ConvertFrom-AadBase64String { [cmdletbinding()] param( [parameter(Mandatory=$true, Position=0, ValueFromPipeline = $true)] [String] $Base64String ) while($Base64String.Length % 4 -ne 0) { $Base64String += "=" } #$Bytes =[Convert]::FromBase64String($Base64String.Replace("-","+").Replace("_","/")) return [Text.Encoding]::Utf8.GetString([Convert]::FromBase64String($Base64String)) } <# .SYNOPSIS Converts a Base64Encoded Thumbprint or also known as Key Identifier (Kid) back to its original Thumbprint value .DESCRIPTION Converts a Base64Encoded Thumbprint or also known as Key Identifier (Kid) back to its original Thumbprint value .PARAMETER Base64String Base64Encoded version of the Thumbprint .EXAMPLE ConvertFrom-AadBase64StringToThumbprint -Base64String 'z79RnGljTQa9Zh4ZjLq6UaB4eUM=' Output... CF-BF-51-9C-69-63-4D-06-BD-66-1E-19-8C-BA-BA-51-A0-78-79-43 .NOTES #> Function ConvertFrom-AadBase64StringToThumbprint { [cmdletbinding()] param( [parameter(Mandatory=$true, Position=0, ValueFromPipeline = $true)] [String] $Base64String ) while($Base64String.Length % 4 -ne 0) { $Base64String += "=" } $Bytes =[Convert]::FromBase64String($Base64String.Replace("-","+").Replace("_","/")) $Thumbprint = [BitConverter]::ToString($Bytes); return $Thumbprint } function ConvertFrom-AadImmutableId { [CmdletBinding()] param ( [Parameter(Mandatory = $true, HelpMessage="` Input a ImmutableID-string, Azure CS DN value, GUID-ObjectGuid/ms-DS-ConsistencyGuid, Hex-ObjectGuid/ms-DS-ConsistencyGuid or Decimal-ObjectGuid/ms-DS-ConsistencyGuid` ` ImmutableID: 2bRnBQ6D80uTz6T14srMPw==` Decimal: 217 180 103 5 14 131 243 75 147 207 164 245 226 202 204 63` HEX: D9 B4 67 05 0E 83 F3 4B 93 CF A4 F5 E2 CA CC 3F` DN: CN={3262526e42513644383075547a3654313473724d50773d3d}` GUID: 0567b4d9-830e-4bf3-93cf-a4f5e2cacc3f` ` ")] [string]$Value ) # identification helper functions function isGUID ($data) { try { $guid = [GUID]$data return 1 } catch { return 0 } } function isBase64 ($data) { try { $decodedII = [system.convert]::frombase64string($data) return 1 } catch { return 0 } } function isHEX ($data) { try { $decodedHEX = "$data" -split ' ' | foreach-object { if ($_) {[System.Convert]::ToByte($_,16)}} return 1 } catch { return 0 } } function isDN ($data) { If ($data.ToLower().StartsWith("cn=")) { return 1 } else { return 0 } } # conversion functions function ConvertIItoDecimal ($data) { if (isBase64 $data) { $dec=([system.convert]::FromBase64String("$data") | ForEach-Object ToString) -join ' ' return $dec } } function ConvertIIToHex ($data) { if (isBase64 $data) { $hex=([system.convert]::FromBase64String("$data") | ForEach-Object ToString X2) -join ' ' return $hex } } function ConvertIIToGuid ($data) { if (isBase64 $data) { $guid=[system.convert]::FromBase64String("$data") return [guid]$guid } } function ConvertHexToII ($data) { if (isHex $data) { $bytearray="$data" -split ' ' | foreach-object { if ($_) {[System.Convert]::ToByte($_,16)}} $ImmID=[system.convert]::ToBase64String($bytearray) return $ImmID } } function ConvertIIToDN ($data) { if (isBase64 $data) { $enc = [system.text.encoding]::utf8 $result = $enc.getbytes($data) $dn=$result | foreach { ([convert]::ToString($_,16)) } $dn=$dn -join '' return $dn } } function ConvertDNtoII ($data) { if (isDN $data) { $hexstring = $data.replace("CN={","") $hexstring = $hexstring.replace("}","") $array = @{} $array = $hexstring -split "(..)" | ? {$_} $ImmID=$array | FOREACH { [CHAR][BYTE]([CONVERT]::ToInt16($_,16))} $ImmID=$ImmID -join '' return $ImmID } } function ConvertGUIDToII ($data) { if (isGUID $data) { $guid = [GUID]$data $bytearray = $guid.tobytearray() $ImmID=[system.convert]::ToBase64String($bytearray) return $ImmID } } # from byte string (converted to byte array) If ( ($value -replace ' ','') -match "^[\d\.]+$") { $bytearray=("$value" -split ' ' | foreach-object {[System.Convert]::ToByte($_)}) $HEXID=($bytearray| ForEach-Object ToString X2) -join ' ' $identified="1" Write-host "" $ImmID=ConvertHexToII $HEXID $dn=ConvertIIToDN $ImmID $GUIDImmutableID = ConvertIIToGuid $ImmID Write-Host "HEX: $HEXID" -foregroundColor Green Write-Host "ImmutableID: $ImmID" -foregroundColor Green Write-Host "DN: CN={$dn}" -foregroundColor Green Write-Host "GUID: $GUIDImmutableID " -foregroundColor Green } # from hex If ($value -match " ") { If ( ($value -replace ' ','') -match "^[\d\.]+$") { Return } $identified="1" Write-host "" $ImmID=ConvertHexToII $value $dec=ConvertIItoDecimal $ImmID $dn=ConvertIIToDN $ImmID $GUIDImmutableID = ConvertIIToGuid $ImmID Write-Host "Decimal: $dec" -foregroundColor Green Write-Host "ImmutableID: $ImmID" -foregroundColor Green Write-Host "DN: CN={$dn}" -foregroundColor Green Write-Host "GUID: $GUIDImmutableID " -foregroundColor Green } # from immutableid If ($value.EndsWith("==")) { $identified="1" Write-host "" $dn=ConvertIIToDn $Value $HEXID=ConvertIIToHex $Value $GUIDImmutableID = ConvertIIToGuid $Value $dec=ConvertIItoDecimal $value Write-Host "Decimal: $dec" -foregroundColor Green Write-Host "HEX: $HEXID" -foregroundColor Green Write-Host "DN: CN={$dn}" -foregroundColor Green Write-Host "GUID: $GUIDImmutableID " -foregroundColor Green } # from dn If ($value.ToLower().StartsWith("cn=")) { $identified="1" Write-host "" $ImmID=ConvertDNToII $Value $HEXID=ConvertIIToHex $ImmID $GUIDImmutableID = ConvertIIToGuid $ImmID $dec=ConvertIItoDecimal $ImmID Write-Host "Decimal: $dec" -foregroundColor Green Write-Host "ImmutableID: $ImmID" -foregroundColor Green Write-Host "HEX: $HEXID" -foregroundColor Green Write-Host "GUID: $GUIDImmutableID " -foregroundColor Green } # from guid if ( isGuid $Value) { $identified="1" Write-host "" $ImmID=ConvertGUIDToII $Value $dn=ConvertIIToDN $ImmID $HEXID=ConvertIIToHex $ImmID $dec=ConvertIItoDecimal $ImmID Write-Host "Decimal: $dec" -foregroundColor Green Write-Host "ImmutableID: $ImmID" -foregroundColor Green Write-Host "HEX: $HEXID" -foregroundColor Green Write-Host "DN: CN={$dn}" -foregroundColor Green } If (-not($identified)) { Write-host -fore red "You provided a value that was neither an ImmutableID (ended with ==), a DN (started with CN=), a GUID, a HEX-value nor a Decimal-value, please try again." } <# Examples ImmutableID: 2bRnBQ6D80uTz6T14srMPw== Decimal: 217 180 103 5 14 131 243 75 147 207 164 245 226 202 204 63 HEX: D9 B4 67 05 0E 83 F3 4B 93 CF A4 F5 E2 CA CC 3F DN: CN={3262526e42513644383075547a3654313473724d50773d3d} GUID: 0567b4d9-830e-4bf3-93cf-a4f5e2cacc3f #> } <# .SYNOPSIS Convert the long number format from JWT tokens to UTC .DESCRIPTION Convert the long number format from JWT tokens to UTC For example convert '1557162946' to '2019-05-06T22:15:46.0000000Z' .PARAMETER JwtNumberDateTime This is the JWT number format (i.e. 1557162946) .EXAMPLE ConvertFrom-AadJwtTime 1557162946 .NOTES General notes #> function ConvertFrom-AadJwtTime { Param ( [Parameter(ValueFromPipeline = $true,Mandatory=$true)] [string] $JwtDateTime ) $date = (Get-Date -Date "1/1/1970").AddSeconds($JwtDateTime) return $date } <# .SYNOPSIS Convert a base64Encoded Json Web Token to a PowerShell object. # .DESCRIPTION Convert a base64Encoded Json Web Token to a PowerShell object. .PARAMETER Token Parameter description .EXAMPLE EXAMPLE 1 "eyJ***" | ConvertFrom-AadJwtToken EXAMPLE 2 ConvertFrom-AadJwtToken -Token "eyJ***" .NOTES General notes #> function ConvertFrom-AadJwtToken { [cmdletbinding()] param([Parameter(ValueFromPipeline = $true,Mandatory=$true)][string]$Token) #Validate as per https://tools.ietf.org/html/rfc7519 #Access and ID tokens are fine, Refresh tokens will not work if (!$token.Contains(".") -or !$token.StartsWith("eyJ")) { Write-Error "Invalid token" -ErrorAction Stop } $jwtClaims = [ordered]@{} #Header $tokenheader = $token.Split(".")[0] #Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0 while ($tokenheader.Length % 4) { $tokenheader += "=" } #Convert from Base64 encoded string to PSObject all at once $jwtHeader = ([System.Text.Encoding]::ASCII.GetString([system.convert]::FromBase64String($tokenheader)) | ConvertFrom-Json) $jwtHeader.psobject.properties | Foreach { $jwtClaims[$_.Name] = $_.Value } #Payload $tokenPayload = $token.Split(".")[1] #Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0 while ($tokenPayload.Length % 4) { $tokenPayload += "=" } #Convert from Base64 encoded string to PSObject all at once $jwtPayload = ([System.Text.Encoding]::ASCII.GetString([system.convert]::FromBase64String($tokenPayload)) | ConvertFrom-Json) $claims = $jwtPayload.psobject.properties Foreach ($claim in $claims) { if($claim.Name -eq "iat" -or $claim.Name -eq "exp" -or $claim.Name -eq "nbf") { $jwtClaims.Add($claim.Name, ($claim.Value | ConvertFrom-AadJwtTime) ) } else { try { $jwtClaims.Add($claim.Name,$claim.Value) } catch { Write-Verbose "Duplicate claim $($claim.Name)" } } } $Object = New-Object -TypeName psobject -Property $jwtClaims Write-Verbose "Done converting JWT to PSObject." return $Object } <# .SYNOPSIS Converts a Thumbprint to a Base64Encoded Thumbprint or also known as Key Identifier (Kid) .DESCRIPTION Converts a Thumbprint to a Base64Encoded Thumbprint or also known as Key Identifier (Kid) .PARAMETER Thumbprint Provide the Thumbprint to be converted to a Base64Encoded value .EXAMPLE ConvertFrom-AadThumbprintToBase64String -Base64String 'CF-BF-51-9C-69-63-4D-06-BD-66-1E-19-8C-BA-BA-51-A0-78-79-43' ConvertFrom-AadThumbprintToBase64String -Base64String 'CFBF519C69634D06BD661E198CBABA51A0787943' Output... z79RnGljTQa9Zh4ZjLq6UaB4eUM= .NOTES #> Function ConvertFrom-AadThumbprintToBase64String { [cmdletbinding()] param( [parameter(Mandatory=$true, Position=0, ValueFromPipeline = $true)] [String] $Thumbprint ) $Bytes = Convert-AadThumbprintToByteArray -Thumbprint ($Thumbprint.Replace("-","")) $hashedString =[Convert]::ToBase64String($Bytes) $hashedString = $hashedString.Split('=')[0] $hashedString = $hashedString.Replace('+', '-') $hashedString = $hashedString.Replace('/', '_') return $hashedString } <# .SYNOPSIS Utility to convert strings to Base 64 Encoded Strings. By default this will also remove formatting from 'Carriage Return', 'New Line', 'Tab' .DESCRIPTION Utility to convert strings to Base 64 Encoded Strings. By default this will also remove formatting from 'Carriage Return', 'New Line', 'Tab' ConvertTo-AadBase64EncodedString -string 'your-string-here' -StripFormatting $true .PARAMETER String String to encode to Base 64. .PARAMETER StripFormatting Boolean to determine if formatting items like ('Carriage Return', 'New Line', 'Tab') should be removed. Default is TRUE. Formatting items will be removed. .EXAMPLE ConvertTo-AadBase64EncodedString -string 'your-string-here' -StripFormatting $true .NOTES General notes #> function ConvertTo-AadBase64EncodedString { Param( [Parameter( mandatory=$true, Position=0, ValueFromPipeline = $true )] [string]$String, [bool]$StripFormatting=$true ) if($StripFormatting) { $String = $String.Replace("`r", "").Replace("`n", "").Replace("`t", "") } $EncodedString = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($String)) return $EncodedString } <# .SYNOPSIS Exports all Azure Role Assignments from all subscriptions in which you have read access to. .DESCRIPTION Exports all Azure Role Assignments from all subscriptions in which you have read access to. This will output a series of files... * Separate CSV for each group and their Group Memberships * Separate CSV for each Azure subscription and their Azure Role Assignments * Single CSV that contains all subscriptions and all Azure ROle Assignments * Single HTML that contains all subscriptions and all Azure ROle Assignments Output of running this command will look something like this... Skipping 'Access to Azure Active Directory'. This is not going to have Role Assignments. Analyzing Subscription 'Pay-As-You-Go (Id:92aa81c9-af09-4ea2-ade0-72a1b4073dbe)' Exported Group Memberships > C:\temp\GroupMembers--DynamicGroup.csv Exported Subscription Role Assignments > C:\temp\Subscription--Pay-As-You-Go-Roles.csv Analyzing Subscription 'Windows Azure MSDN - Visual Studio Ultimate (Id:955107ad-af96-475e-a9e9-0b0474e83982)' Exported Group Memberships > C:\temp\GroupMembers--Application Access - 4.csv Exported Group Memberships > C:\temp\GroupMembers--Group 1.csv Exported Subscription Role Assignments > C:\temp\Subscription--Windows Azure MSDN - Visual Studio Ultimate-Roles.csv Analyzing Subscription 'Microsoft Azure Internal Consumption (Id:ef8110a7-ab02-4b82-a4d1-4126dcda86e0)' Exported Subscription Role Assignments > C:\temp\Subscription--Microsoft Azure Internal Consumption-Roles.csv Exported All Role Assignments > C:\temp\Subscription--All-Roles.csv Exported HTML > C:\temp\Subscription--All-Roles.html Please verify the contents of the exported files. You can use either the 'Subscription--All-Roles.csv' or one of the subscription files to import the Azure Role Assignments into another tenant or into another Azure subscription when running... Import-AadAzureRoleAssignments .EXAMPLE Export-AadAzureRoleAssignments #> function Export-AadAzureRoleAssignments { $RoleAssignments = @() #Traverse through each Azure subscription user has access to $subscriptions = Invoke-AzureCommand -Command { Param($TenantId) Get-AzSubscription -TenantId $TenantId } -Parameters $Global:AadSupport.Session.TenantId Foreach ($sub in $subscriptions) { $SubName = $sub.Name if ($sub.Name -ne "Access to Azure Active Directory") { # You can't assign roles in Access to Azure Active Directory subscriptions Write-Host "Analyzing Subscription '$($sub.Name) (Id:$($sub.id))' " Invoke-AzureCommand -Command { Param($SubscriptionId) Set-AzContext -SubscriptionId $SubscriptionId | Out-Null } -Parameters $sub.id -SubscriptionId $sub.id Try { $SubRoleAssignments = Invoke-AzureCommand -Command { Get-AzRoleAssignment -IncludeClassicAdministrators } -SubscriptionId $sub.id $RoleAssignments += $SubRoleAssignments } Catch { Write-Output "Failed to collect RBAC permissions for $subname" } #Custom Roles do not display their Name in these results. We are forcing this behavior for improved reporting Foreach ($role in $RoleAssignments) { $ObjectId = $role.ObjectId $DisplayName = $role.DisplayName If (-not $role.RoleDefinitionName) { $role.RoleDefinitionName = Invoke-AzureCommand { Param($RoleDefinition) (Get-AzRoleDefinition -Id $RoleDefinition).Name } -Parameters $role.RoleDefinitionId -SubscriptionId $sub.id } if ($role.ObjectType -eq "Group" -and !(Test-Path -path "GroupMembers--$DisplayName.csv")) { $Members = Invoke-AadCommand -Command { Param($ObjectId) Get-AzureADGroupMember -ObjectId $ObjectId } -Parameters $ObjectId $Path = Get-Location $FilePath = "$Path\GroupMembers--$DisplayName.csv" $Members | Export-CSV $FilePath -NoTypeInformation -Force Write-Host "Exported Group Memberships" Write-Host " > $FilePath" } } #Export the Role Assignments to a CSV file labeled by the subscription name $Path = Get-Location $FilePath = "$Path\Subscription--$SubName-Roles.csv" $SubRoleAssignments | Export-CSV $FilePath -NoTypeInformation -Force Write-Host "Exported Subscription Role Assignments" Write-Host " > $FilePath" } else { Write-Host "Skipping 'Access to Azure Active Directory'. This is not going to have Role Assignments." } } #Export All Role Assignments in to a single CSV file $Path = Get-Location $FilePath = "$Path\Subscription--All-Roles.csv" $RoleAssignments | Export-CSV ".\Subscription--All-Roles.csv" -NoTypeInformation -Force Write-Host "Exported All Role Assignments" Write-Host " > $FilePath" # HTML report $a = "<style>" $a = $a + "TABLE{border-width: 1px;border-style: solid;border-color: black;border-collapse: collapse;font-family:arial}" $a = $a + "TH{border-width: 1px;padding: 5px;border-style: solid;border-color: black;}" $a = $a + "TD{border-width: 1px;padding: 5px;border-style: solid;border-color: black;}" $a = $a + "</style>" $Path = Get-Location $FilePath = "$Path\Subscription--All-Roles.html" $RoleAssignments | ConvertTo-Html -Head $a| Out-file $FilePath -Force Write-Host "Exported HTML" Write-Host " > $FilePath" } <# .SYNOPSIS Gets the admin roles assigned to the specified object (User or ServicePrincipal) .DESCRIPTION Gets the admin roles assigned to the specified object (User or ServicePrincipal) Example 1: Get Admin Roles for a User or Object based on its ObjectId Get-AadAdminRolesByObject -ObjectId Example 2: Get Admin Roles for a ServicePrincipal Get-AadAdminRolesByObject -ServicePrincipalId 'Contoso Web App' Example 3: Get Admin Roles for a user Get-AadAdminRolesByObject -UserId 'john@contoso.com' .PARAMETER ObjectId Lookup user or service principal by its ObjectId .PARAMETER ServicePrincipalId Lookup service principal by any of its Ids (DisplayName, AppId, ObjectId, or SPN) .PARAMETER UserId Lookup user by any of its Ids ObjectId or UserPrincipalName .NOTES General notes #> function Get-AadAdminRolesByObject { param( [Parameter( ValueFromPipeline = $true, ParameterSetName = "ByObjectId")] [parameter(ValueFromPipeline=$true)] $ObjectId, [Parameter(ParameterSetName = "ByServicePrincipalId")] $ServicePrincipalId, [Parameter(ParameterSetName = "ByUserId")] $UserId ) # REQUIRE AadSupport Session RequireConnectAadSupport # END REGION # Search for ServicePrincipal if($ServicePrincipalId) { $sp = Get-AadServicePrincipal -Id $ServicePrincipalId If(-not $sp) { return } $ObjectId = $sp.ObjectId } # Search for User if($UserId) { $user = Invoke-AadCommand -Command { Param($UserId) Get-AzureADUser -ObjectId $UserId } -Parameters $UserId If(-not $user) { return } $ObjectId = $user.ObjectId } $roles = Invoke-AadCommand -Command { Get-AzureADDirectoryRole } $AdminRoleList = @() $AadAdminCount = 0 foreach ($role in $roles) { $members = Invoke-AadCommand -Command { Param($role) Get-AzureADDirectoryRoleMember -ObjectId $role.ObjectId } -parameters $role foreach ($member in $members) { if($member.ObjectId -eq $ObjectId) { $AdminRoleList += [PSCustomObject]@{ RoleDisplayName = $role.DisplayName; RoleId = $role.ObjectId; } } } } # Output Admin Roles $ReturnObject = $AdminRoleList | Select-Object RoleDisplayName, RoleId return $ReturnObject } Set-Alias -Name Get-AadApp -Value Get-AadApplication <# .SYNOPSIS Intelligence to return the Application object by looking up using any of its identifiers. .DESCRIPTION Intelligence to return the Application object by looking up using any of its identifiers. .PARAMETER Id Either specify Application Name, Display Name, Object ID, Application/Client ID, or Application Object ID .EXAMPLE Get-AadApplication -Id 'Contoso Web App' .NOTES Returns the Application object using Get-AzureAdApplication and filter based on the Id parameter #> function Get-AadApplication { [CmdletBinding(DefaultParameterSetName='ByAnyId')] param( [Parameter( mandatory=$true, Position=0, ValueFromPipeline = $true, ParameterSetName = 'ByAnyId' )] $Id, [Parameter( mandatory=$true, ParameterSetName = 'ByAppId' )] $AppId, [Parameter( mandatory=$true, ParameterSetName = 'ByDisplayName' )] $DisplayName, [Parameter( mandatory=$true, ParameterSetName = 'ByAppUriId' )] $AppUriId, [Parameter( mandatory=$true, ParameterSetName = 'ByReplyAddress' )] $ReplyAddress ) # REQUIRE AadSupport Session RequireConnectAadSupport # END REGION $Global:Applications = @() $app = $null $isGuid = $null # Search By AppId if ($AppId) { Write-Verbose "Looking for '$AppId'" $app = GetAadAppByAppId $AppId } # Search By ReplyAddress if ($ReplyAddress) { Write-Verbose "Looking for '$ReplyAddress'" $app = GetAadAppByReplyAddress $ReplyAddress } # Search By AppUriId if ($AppUriId) { Write-Verbose "Looking for '$AppUriId'" $app = GetAadAppByAppUriId $AppUriId } # Search By DisplayName if ($DisplayName) { Write-Verbose "Looking for '$DisplayName'" $sp = GetAadAppyDisplayName -Id $DisplayName } try { $isGuid = [System.Guid]::Parse($Id) } catch { } # Search By All (Any ID) if($Id) { # Search for app based on AppId or ObjectId if ($isGuid -and -not $app) { # Search for app based on ObjectId $app = $null $app = try { Invoke-AadCommand -Command { Param($Id) Get-AzureADObjectByObjectId -ObjectId $Id } -Parameters $Id } catch {} if ($app.ObjectType -eq "Application") { Write-Verbose "Application found using ObjectId" } $appid = $Id if ($app.ObjectType -eq "ServicePrincipal") { Write-Verbose "Service Principal found! Looking for Application..." $appid = $app.AppId $app = $null } # Search for app based on AppId if(-not $app) { $app = GetAadAppByAppId -Id $appid if ($app) { return $app } } } # Search for app based on AppUriId or DisplayName if(-not $app) { $app = @() $app += GetAadAppByDisplayName $Id $app += GetAadAppByAppUriId $Id } } $Global:Applications = $null # Exit script! Application Not found if (-not $app) { throw "Azure AD Application '$Id' not found!" } # Application(s) Found > Only return One result if($app.count -gt 1) { $app = $app | Out-GridView -PassThru -Title "Select Application" } return $app } function GetAadAppByAppId { param( [Parameter( mandatory=$true, ValueFromPipeline = $true)] $Id ) Write-Verbose "Searching by AppId" try { $isGuid = [System.Guid]::Parse($Id) } catch { throw "Invalid App Id" } $app = Invoke-AadCommand { Param($Id) Get-AzureAdApplication -filter "AppId eq '$Id'" } -Parameters $Id if ($app) { Write-Verbose "Application found using AppId" return $app } return } function GetAadAppByReplyAddress { param( [Parameter( mandatory=$true, ValueFromPipeline = $true)] $Id ) Write-Verbose "Searching by ReplyUrls" $apps = LookupAllApplications $app = @() $app += $apps | Where-Object {$_.ReplyUrls -contains "$Id"} if ($app) { Write-verbose "Application '$Id' found using Reply Address" } return $app } function GetAadAppByAppUriId { param( [Parameter( mandatory=$true, ValueFromPipeline = $true)] $Id ) Write-Verbose "Searching by AppUriId" $apps = LookupAllApplications $app = @() $app += $apps | Where-Object { $_.IdentifierUris -match $Id } if ($app) { Write-verbose "Application(s) '$Id' found using AppUriId" } return $app } function GetAadAppByDisplayName { param( [Parameter( mandatory=$true, ValueFromPipeline = $true)] $Id ) Write-Verbose "Searching by DisplayName" $app = Invoke-AadCommand { Param($Id) Get-AzureADApplication -filter "DisplayName eq '$Id'" } -Parameters $Id # Application Not found yet using DisplayName (Do wider search) if(-not $app) { $apps = LookupAllApplications $app = @() $app += $apps | Where-Object {$_.DisplayName -match "$Id"} } if ($app) { Write-Verbose "Application(s) '$Id' found using DisplayName" } return $app } function LookupAllApplications() { if(-not $Global:Applications) { Write-Host "Looking at all Applications to find your app. This might take awhile..." $Global:Applications = Invoke-AadCommand { Get-AzureADApplication -All $true } } return $Global:Applications } <# .SYNOPSIS Easily find the value or id of a permission based on the servicePrincipals AppRoles or Oauth2Permissions .DESCRIPTION Long descriptionEasily find the value or id of a permission based on the servicePrincipals AppRoles or Oauth2Permissions .PARAMETER ResourceId Provide the Resource Identifier .PARAMETER Permission Provide the permission you want to look up. EXAMPLES # Lookup Scope/Role Value Get-AadAppPermissionInfo "Microsoft Graph" -Permission User.Read.All # Lookup Scope Id > This is the id for User.Read Scope Get-AadAppPermissionInfo "Microsoft Graph" -Permission a154be20-db9c-4678-8ab7-66f6cc099a59 # Lookup Role Id > This is the id for User.Read Role Get-AadAppPermissionInfo "Microsoft Graph" -Permission df021288-bdef-4463-88db-98f22de89214 .NOTES General notes #> function Get-AadAppPermissionInfo { [CmdletBinding(DefaultParameterSetName="DefaultSet")] param ( [Parameter(mandatory=$true, Position=0, ValueFromPipeline = $true)] [string]$ResourceId, [Parameter(mandatory=$true)] [string]$Permission ) # Get the servicePrincipal for the resource $sp = Get-AadServicePrincipal -Id $ResourceId # Lookup the permission in AppRoles $Roles = $sp.AppRoles | where {$_.Value -eq $Permission -or $_.id -eq $Permission} | Select Id, Value, Type # Role found so add the 'Type' property if($Roles) { $Roles.Type = "Role" } # Lookup the permission in Oauth2Permissions $Scopes = $sp.Oauth2Permissions | where {$_.Value -eq $Permission -or $_.id -eq $Permission} | Select Id, Value, Type # Scope found so add the 'Type' property if($Scopes) { $Scopes.Type = "Scope" } # Build our results $results = @() $results += $Roles $results += $Scopes return $results } <# .SYNOPSIS Get the App roles assigned to the specified object .DESCRIPTION Get the App roles assigned to the specified object .PARAMETER ServicePrincipalId Specify by the Service Principal .PARAMETER ObjectId Specify by any object ID .PARAMETER ObjectType Specify the Object type based on Object id specified .PARAMETER UserId Specify the User .EXAMPLE Get-AadAppRolesByObject -ServicePrincipalId 'Contoso App' ResourceDisplayName : Microsoft Graph ResourcePermission : User.ReadWrite.All DirectAssignment : True GetsAssignmentBy : Id : ef7d1fa9-1e37-48fd-bb58-ad10a78cbd18 .NOTES General notes #> function Get-AadAppRolesByObject { param( [Parameter(mandatory=$true, ParameterSetName="ByServicePrincipalId")] $ServicePrincipalId, [Parameter(mandatory=$true, ParameterSetName="ByObjectId")] [parameter(ValueFromPipeline=$true)] $ObjectId, [Parameter(ParameterSetName="ByObjectId")] [parameter(ValueFromPipeline=$true)] [ValidateSet("User","ServicePrincipal")] $ObjectType, [Parameter(mandatory=$true, ParameterSetName="ByUserId")] $UserId ) if($ObjectId -and -not $ObjectType) { $ObjectType = ( Invoke-AadCommand -Command { Param($ObjectId) Get-AzureADObjectByObjectId -ObjectIds $ObjectId } -Parameters $ObjectId ).ObjectType } if($ServicePrincipalId) { $sp = (Get-AadServicePrincipal -Id $ServicePrincipalId) $ObjectId = $sp.ObjectId if($sp.count -gt 1) { throw "'$ServicePrincipalId' query returned more than one result. Please provide a unique Service Principal Identifier" } if(-not $ObjectId) { throw "'$ServicePrincipalId' not found in '$TenantDomain'" } $ObjectType = "ServicePrincipal" } $TenantDomain = $Global:AadSupport.Session.TenantId $AppRoleList = @() if($ObjectType -eq "ServicePrincipal") { $AppRoles = Invoke-AadCommand -Command { Param($ObjectId) Get-AzureADServiceAppRoleAssignedTo -ObjectId $ObjectId } -Parameters $ObjectId } if($UserId) { $User = ( Invoke-AadCommand -Command { Param($UserId) Get-AzureADUser -ObjectId $UserId } -Parameters $UserId ) $ObjectId = $User.ObjectId if(-not $ObjectId) { throw "'$UserId' not found in '$TenantDomain'" } $ObjectType = "User" } if($ObjectType -eq "User") { $AppRoles = Invoke-AadCommand -Command { Param($ObjectId) Get-AzureADUserAppRoleAssignment -ObjectId $ObjectId } -Parameters $ObjectId } foreach ($AppRole in $AppRoles) { if($ObjectId -eq $AppRole.PrincipalId) { $DirectAssignment = $true } else { $DirectAssignment = $false $GetsAssignmentBy = "$($AppRole.PrincipalDisplayName) ($($AppRole.PrincipalId))" } $resource = ( Invoke-AadCommand -Command { Param($AppRole) Get-AzureADServicePrincipal -ObjectId $AppRole.ResourceId } -Parameters $AppRole ).AppRoles | Where-Object { $_.Id -eq $AppRole.Id } $AppRoleList += [PSCustomObject]@{ ResourceDisplayName = $AppRole.ResourceDisplayName; ResourcePermission = $resource.Value DirectAssignment = $DirectAssignment GetsAssignmentBy = $GetsAssignmentBy Id = $AppRole.PrincipalId } } # Output App Roles return $AppRoleList } <# .SYNOPSIS Get the Azure Roles assigned to the specified object .DESCRIPTION Get the Azure Roles assigned to the specified object .PARAMETER ServicePrincipalId Specify by the Service Principal .PARAMETER ObjectId Specify by any object ID .PARAMETER ObjectType Specify the Object type based on Object id specified .PARAMETER UserId Specify the User .PARAMETER ServicePrincipalName Specify the ServicePrincipalName .PARAMETER SigninName Specify the SigninName .EXAMPLE Get-AadAzureRoleAssignments -ServicePrincipalId 'Contoso App' ResourceDisplayName : Microsoft Graph ResourcePermission : User.ReadWrite.All DirectAssignment : True GetsAssignmentBy : Id : ef7d1fa9-1e37-48fd-bb58-ad10a78cbd18 .NOTES General notes #> function Get-AadAzureRoleAssignments { # THIS FUNCTION IS STANDALONE [CmdletBinding(DefaultParameterSetName="ByObject")] param( [Parameter(ParameterSetName="ByObject",Mandatory=$true)] [Parameter(ParameterSetName="ByServicePrincipalObject",Mandatory=$true)] [parameter(ValueFromPipeline=$true)] [string]$ObjectId, [Parameter(ParameterSetName="ByObject")] [Parameter(ParameterSetName="ByServicePrincipalObject",Mandatory=$false)] [parameter(ValueFromPipeline=$true)] [string]$ObjectType, [Parameter(ParameterSetName="ByServicePrincipalId",Mandatory=$true)] [string]$ServicePrincipalId, [Parameter(ParameterSetName="ByUserId",Mandatory=$true)] [string]$UserId, [Parameter(ParameterSetName="ByServicePrincipalName",Mandatory=$true)] [Parameter(ParameterSetName="ByServicePrincipalObject",Mandatory=$false)] [string]$ServicePrincipalName, [Parameter(ParameterSetName="BySigninName",Mandatory=$true)] [string]$SigninName ) # REQUIRE AadSupport Session RequireConnectAadSupport # END REGION $TenantId = $Global:AadSupport.Session.TenantId # If ObjectId is used, need to determine what object type it is and set appropraite variables if($ObjectId -and (-not $ObjectType -or $ServicePrincipalName)) { $Object = Invoke-AadCommand -Command { Param($ObjectId) Get-AzureADObjectByObjectId -ObjectIds $ObjectId } -Parameters $ObjectId # If no object found throw error if(-not $Object) { throw "'$ObjectId' not found in '$TenantId'" } } elseif($ServicePrincipalName) { $Object = Get-AadServicePrincipal -ServicePrincipalName $ServicePrincipalName } if(-not $ObjectType) { # Get Object Type $ObjectType = $Object.ObjectType if ($ObjectType -eq "ServicePrincipal") { $ServicePrincipalName = $Object.ServicePrincipalNames[0] } if ($ObjectType -eq "User") { $SigninName = $Object.UserPrincipalName } } if($ServicePrincipalId) { $sp = (Get-AadServicePrincipal -Id $ServicePrincipalId) $ServicePrincipalName = $sp.ServicePrincipalNames[0] $ObjectId = $sp.ObjectId if($sp.count -gt 1) { throw "'$ServicePrincipalId' query returned more than one result. Please provide a unique Service Principal Identifier" } if(-not $ServicePrincipalName) { throw "'$ServicePrincipalId' not found in '$TenantId'" } } if($UserId) { $User = ( Invoke-AadCommand -Command { Param($UserId) Get-AzureADUser -ObjectId $UserId } -Parameters $UserId ) $SigninName = $User.UserPrincipalName if(-not $UserId) { throw "'$UserId' not found in '$TenantId'" } } $subscriptions = Invoke-AzureCommand -Command { Param($TenantId) Get-AzSubscription -TenantId $TenantId } -Parameters $Global:AadSupport.Session.TenantId $result = @() foreach($sub in $subscriptions) { if($sub.Name -ne "Access to Azure Active Directory") { $context = Invoke-AzureCommand -Command { Param($Params) #$context = Get-AzSubscription -Subscription $Params.SubscriptionId -TenantId $Params.TenantId Set-AzContext -Tenant $Params.TenantId -Subscription $Params.SubscriptionId | Out-Null } -Parameters @{ SubscriptionId = $sub.id TenantId = $TenantId } -SubscriptionId $sub.id # Get Role Assignments if($ServicePrincipalName) { $RoleAssignments = @() $AzRoleAssignments = Invoke-AzureCommand -Command { Get-AzRoleAssignment } -SubscriptionId $sub.id # Get Direct Assignments $RoleAssignments += Invoke-AzureCommand -Command { Param($ServicePrincipalName) Get-AzRoleAssignment -ServicePrincipalName $ServicePrincipalName } -Parameters $ServicePrincipalName -SubscriptionId $sub.id # Get groups assigned to Azure RBAC (we need to check each group) $GroupIdsCheck = ($AzRoleAssignments | where {$_.ObjectType -eq "Group"}).ObjectId $Groups = New-Object Microsoft.Open.AzureAD.Model.GroupIdsForMembershipCheck $Groups.GroupIds = $GroupIdsCheck # Check if ServicePrincipal is a member of any of those groups if($GroupIdsCheck) { $IsMemberOf = Invoke-AadCommand -Command { Param($Params) Select-AzureADGroupIdsServicePrincipalIsMemberOf -ObjectId $Params.ObjectId -GroupIdsForMembershipCheck $Params.Groups } -Parameters @{ ObjectId = $ObjectId Groups = $Groups } # Get Azure RBAC for the groups in which the Service Principal is a member of foreach($GroupId in $IsMemberOf) { $RoleAssignments += $AzRoleAssignments | Where-Object { $_.ObjectId -eq $GroupId } } } } if($SigninName) { $RoleAssignments = Invoke-AzureCommand -Command { Param($SigninName) Get-AzRoleAssignment -SignInName $SigninName -ExpandPrincipalGroups } -Parameters $SigninName -SubscriptionId $sub.id } if($IsMemberOf) { $DirectAssignment = $false } else { $DirectAssignment = $true } # If Role Assignment > generate output if($RoleAssignments) { foreach($Role in $RoleAssignments) { if($IsMemberOf) { $AssignedDisplayName = ( Invoke-AadCommand -Command { Param($Role) Get-AzureADObjectByObjectId -ObjectIds $Role.ObjectId } -Parameters $Role ).DisplayName $GetsAssignmentBy = "$($AssignedDisplayName) ($($Role.ObjectId))" } $ResourceName = "" if($Role.Scope) { $Split = $Role.Scope.Split("/") $ResourceName = $Split[$Split.Lenth-1] } $CustomResult = [pscustomobject]@{ Scope = $Role.Scope RoleName = $Role.RoleDefinitionName ResourceName = $ResourceName DirectAssignment = $DirectAssignment GetsAssignmentBy = $GetsAssignmentBy } $result += $CustomResult } } } } return $result } <# .SYNOPSIS Get a list of consented permissions based using the specified parameters to filter .DESCRIPTION Get a list of consented permissions based using the specified parameters to filter Get-AadConsent Returns the following Object with properties PermissionType | Expected values: Role, Scope | Role if Application permission, Scope if Delegated permission ClientName | Name of the client ClientId | Service Principal Object ID of the client ResourceName | Name of the resource ResourceId | Service Principal Object ID of the resource PrincipalId | Service Principal Object ID of the user ClaimValue | List of scopes or role claim values Id | Id of the OAuth2PermissionGrant/AppRole ConsentType | Expected values: AdminConsent, UserConsent .PARAMETER ClientId Filter based on the ClientId. This is the Enterprise App (Client app) in which the consented permissions are applied on. .PARAMETER ResourceId Filter based on the ResourceId. This is the resource in which the client has permissions on. .PARAMETER UserId Filter based on the UserId. User in which that has consented to the app. .PARAMETER ClaimValue Filter based on the scope or role value. .PARAMETER ConsentType Filter based on the Consent Type. Available options... 'Admin','User', 'All' .PARAMETER PermissionType Filter based on the Permission Type. Available options... 'Delegated','Application', 'All' .EXAMPLE Example 1: See a list of all consents for a app PS C:\> Get-AadConsent -ClientId 'Contoso App' | Format-List .EXAMPLE Example 2: See a list of User Consents for a app PS C:\> Get-AadConsent -ClientId 'Contoso App' -UserId john@contoso.com | Format-List #> function Get-AadConsent { [CmdletBinding(DefaultParameterSetName='Default')] Param( [string]$ClientId, [string]$ResourceId, [string]$UserId, [ValidateSet('Admin','User', 'All')] $ConsentType = 'All', [ValidateSet('Delegated','Application', 'All')] $PermissionType = 'All', [string]$ClaimValue ) $PrincipalId = $UserId # Parameter Validations if(!$ClientId -and !$ResourceId -and !$PrincipalId) { throw "You must specify at least one of the properties : ClientId or ResourceId or PrincipalId" } if( ($Principald -and $PermissionType -eq 'Application') -or ($PermissionType -eq 'Application' -and $ConsentType -eq 'User') ) { throw "You can't have PermissionType 'Application' and specify a user." } if($ClaimValue -and -not $ResourceId) { throw "You must provide a 'ResoureId' when using 'ClaimValue'" } # Start building the OAuth2PermissionGrant Filter $GrantFilterBuilder = @() if($ConsentType -eq "User" -and !$UserId) { $GrantFilterBuilder += "consentType eq 'Principal'" } elseif($ConsentType -eq "Admin") { $GrantFilterBuilder += "consentType eq 'AllPrincipals'" } $GrantUri = "$($Global:AadSupport.Resources.MsGraph)/beta/oauth2PermissionGrants?`$top=999" $Resource = $null if($ResourceId) { $Resource = Get-AadServicePrincipal -Id $ResourceId if(!$Resource) { throw "$ResourceId not found!" } $RoleUri = "$($Global:AadSupport.Resources.MsGraph)/beta/servicePrincipals/$($Resource.ObjectId)/appRoleAssignedTo?`$top=999" $GrantFilterBuilder += "resourceId eq '$($Resource.ObjectId)'" } $Client = $null if($ClientId) { $Client = Get-AadServicePrincipal -Id $ClientId if(!$Client) { throw "$ClientId not found!" } $RoleUri = "$($Global:AadSupport.Resources.MsGraph)/beta/servicePrincipals/$($Client.ObjectId)/appRoleAssignments?`$top=999" $GrantFilterBuilder += "clientId eq '$($Client.ObjectId)'" } $Principal = $null if($PrincipalId) { $Principal = Invoke-AadCommand -Command { Param($PrincipalId) Get-AzureAdUser -ObjectId $PrincipalId } -Parameters $PrincipalId if(!$Principal) { throw "$PrincipalId not found!" } $GrantFilterBuilder += "principalId eq '$($Principal.ObjectId)'" } # CREATE FILTER FOR OAUTH2PERMISSION GRANTS if($GrantFilterBuilder) { $GrantFilter = "`&`$filter=" } foreach($item in $GrantFilterBuilder) { if($item -ne $GrantFilterBuilder[0]) { $GrantFilter += " and " } $GrantFilter += $item } # ------------------------------------------------ # GET OAUTH2PERMISSIONGRANT <# { "@odata.context": "https://graph.microsoft.com/beta/$metadata#oauth2PermissionGrants", "value": [ { "clientId": "7f5c5913-b683-4b71-a57b-008cd3de72e5", "consentType": "AllPrincipals", "expiryTime": "2021-02-11T22:51:28.9886344Z", "id": "E1lcf4O2cUulewCM095y5Yb6Xxl6wYJDqt2ei2ygkbw", "principalId": null, "resourceId": "195ffa86-c17a-4382-aadd-9e8b6ca091bc", "scope": "user_impersonation", "startTime": "0001-01-01T00:00:00Z" } ] } #> if($PermissionType -eq 'All' -or $PermissionType -eq "Delegated") { $Grants = Invoke-AadProtectedApi ` -Client $Global:AadSupport.Clients.AzureAdPowershell.ClientId ` -Resource $Global:AadSupport.Resources.MsGraph ` -Endpoint $GrantUri+$GrantFilter -Method "GET" } $Permissions = [PSCustomObject]@() if($Grants) { foreach($item in $Grants) { Show-AadSupportStatusBar # Skip condition if we only want to see UserConsent if($ConsentType -eq "User" -and $item.consentType -eq "AllPrincipals") { continue } # Skip condition if we only want to see AdminConsent if($ConsentType -eq "Admin" -and $item.consentType -eq "Principal") { continue } $Permission = @{ PermissionType = "Delegated" } $ItemClient = $null if($item.clientId -and $Client.ObjectId -ne $item.clientId) { $ItemClient = Get-AadServicePrincipal -Id $item.clientId } else { $ItemClient = $Client } $ItemResource = $null if($item.resourceId -and $Resource.ObjectId -ne $item.resourceId) { $ItemResource = Get-AadServicePrincipal -Id $item.resourceId } else { $ItemResource = $Resource } $ItemPrincipal = $null if(!$item.principalId) { $Permission.ConsentType = "AdminConsent" } else { if($item.principalId -and $Principal.ObjectId -ne $item.principalId) { $ItemPrincipal = Invoke-AadCommand -Command { Param($PrincipalId) Get-AzureAdUser -ObjectId $PrincipalId } -Parameters $item.principalId } else { $ItemPrincipal = $Principal } $Permission.ConsentType = "UserConsent" } $Permission.ClientName = $ItemClient.DisplayName $Permission.ClientId = $ItemClient.ObjectId $Permission.ResourceName = $ItemResource.DisplayName $Permission.ResourceId = $ItemResource.ObjectId $Permission.PrincipalId = $ItemPrincipal.userPrincipalName $Permission.ClaimValue = $item.scope $Permission.Id = $item.id $PermissionObject = New-Object -TypeName 'PSObject' -Property $Permission $Permissions += $PermissionObject } } # ------------------------------------------------ # GET ROLES # NOTE: AppRoleAssignments in MS Graph does not support filtering if(($PermissionType -eq 'All' -or $PermissionType -eq "Application") -and $ConsentType -ne 'User' -and !$PrincipalId) { $RoleAssignments = Invoke-AadProtectedApi ` -Client $Global:AadSupport.Clients.AzureAdPowershell.ClientId ` -Resource $Global:AadSupport.Resources.MsGraph ` -Endpoint $RoleUri -Method "GET" } if($RoleAssignments) { if($Client) { $RoleAssignments = $RoleAssignments | where-object {$_.principalId -eq $Client.ObjectId } } if($Resource) { $RoleAssignments = $RoleAssignments | where-object {$_.resourceId -eq $Resource.ObjectId } } foreach($item in $RoleAssignments) { Show-AadSupportStatusBar $Permission = @{ PermissionType = "Application" } $ItemClient = $null if($item.clientId -and $Client.ObjectId -ne $item.principalId) { $ItemClient = Get-AadServicePrincipal -Id $item.principalId } else { $ItemClient = $Client } $ItemResource = $null if($item.resourceId -and $Resource.ObjectId -ne $item.resourceId) { $ItemResource = Get-AadServicePrincipal -Id $item.resourceId } else { $ItemResource = $Resource } $Permission.ClientName = $ItemClient.DisplayName $Permission.ClientId = $ItemClient.ObjectId $Permission.ResourceName = $ItemResource.DisplayName $Permission.ResourceId = $ItemResource.ObjectId $RoleName = ($ItemResource.AppRoles | Where-Object {$_.id -eq $item.appRoleId }).Value $Permission.ClaimValue = $RoleName $Permission.Id = $item.id $Permission.ConsentType = "AdminConsent" $Permission.PrincipalId = $null $PermissionObject = New-Object -TypeName 'PSObject' -Property $Permission $Permissions += $PermissionObject } } if($ClaimValue) { return $Permissions | where {(" "+$_.ClaimValue+" ") -match (" "+$ClaimValue+" ")} } return $Permissions } <# .SYNOPSIS Azure AD uses the UTC time (Coordinated Universal Time) and Universal Sortable DateTime Pattern. .DESCRIPTION Azure AD uses the UTC time (Coordinated Universal Time) and Universal Sortable DateTime Pattern. UTC is the worlds synchronized time and set to the GMT time zone. Universal Sortable DateTime Pattern looks like this: yyyy-MM-dd'T'HH:mm:ss.SSSZ .PARAMETER DateTime Specify DateTime you want to convert to UTC .PARAMETER AddDays Add or subtract days from current DateTime or specified DateTime .PARAMETER AddHours Add or subtract hours from current DateTime or specified DateTime .PARAMETER AddMinutes Add or subtract minutes from current DateTime or specified DateTime .EXAMPLE Get-AadDateTime Get-AadDateTime -DateTime "01/20/2019" Get-AadDateTime -AddDays 7 -AddHours 12 -AddMinutes 30 Get-AadDateTime -DateTime "01/20/2019" -AddDays -7 .NOTES General notes #> function Get-AadDateTime { param ( [Parameter( mandatory=$false, ValueFromPipeline = $true)] $DateTime, $AddDays, $AddHours, $AddMinutes, $AddYears, $AddMonths ) if ($DateTime) { $date = (Get-Date $DateTime).ToUniversalTime() } else { $date = (Get-Date).ToUniversalTime() } if ($AddDays) { $date = $date.AddDays($AddDays) } if ($AddHours) { $date = $date.AddHours($AddHours) } if ($AddMinutes) { $date = $date.AddMinutes($AddMinutes) } if ($AddYears) { $date = $date.AddYears($AddYears) } if ($AddMonths) { $date = $date.AddMonths($AddMonths) } return (Get-Date $date -Format o) } <# .SYNOPSIS Gets the Azure AD Discovery Keys .DESCRIPTION Gets the Azure AD Discovery Keys PS C:\>Get-AadDiscoveryKeys Downloading configuration from 'https://login.microsoftonline.com/aa00d1fa-5269-4e1c-b06d-30868371d2c5/.well-known/openid-configuration' Downloading signing keys from 'https://login.microsoftonline.com/common/discovery/keys' ApplicationId : Kid : HlC0R12skxNZ1WQwmjOF_6t_tDE Use : sig x5t : HlC0R12skxNZ1WQwmjOF_6t_tDE kty : RSA Modulus : vq_3TOSbrUzpGPHFEwjmeoE_Zu3-wU4vaeEvjQzUHXwIefy8bDuMav6OzUiEXhjLX5JRkGhds3lNGR3CSZgartIKWv5Vrc7F2YcBcgz rpO06kVcewRMjdhrPYfUfO6QklAOSCcPq4RUhEvkGEwbAw3awclve1KuhpX6fOIInP8Gp8hrFDd_neBR3AY03JrZpezBdQoE24UHgAl HGb2UZ2KKjl3rLDMPh9HecjTiga3SbdcrhTAOYHYb4LwCSrThrHSyZFBxzTwQMS0NEyKV7_-ADrFunf9cuVcGpQZkvdwODl4tY-l2sd 3WpoD_gMDpoFJVojjzF07ovrfntM4o8Bw Exponent : AQAB Certificate : @{Subject=CN=accounts.accesscontrol.windows.net; Kid=HlC0R12skxNZ1WQwmjOF_6t_tDE; NotAfter=12/24/2024 6:00:00 PM; Issuer=CN=accounts.accesscontrol.windows.net; Certificate=[Subject] CN=accounts.accesscontrol.windows.net [Issuer] CN=accounts.accesscontrol.windows.net [Serial Number] 19BE4B61B2A8DC874CD0742C8EFFA612 [Not Before] 12/25/2019 6:00:00 PM [Not After] 12/24/2024 6:00:00 PM [Thumbprint] 1E50B4475DAC931359D564309A3385FFAB7FB431 ; Thumbprint=1E50B4475DAC931359D564309A3385FFAB7FB431; NotBefore=12/25/2019 6:00:00 PM} Thumbprint : 1E50B4475DAC931359D564309A3385FFAB7FB431 x5c : MIIDBTCCAe2gAwIBAgIQGb5LYbKo3IdM0HQsjv+mEjANBgkqhkiG9w0BAQsFADAtMSswKQYDVQQDEyJhY2NvdW50cy5hY2Nlc3Njb25 0cm9sLndpbmRvd3MubmV0MB4XDTE5MTIyNjAwMDAwMFoXDTI0MTIyNTAwMDAwMFowLTErMCkGA1UEAxMiYWNjb3VudHMuYWNjZXNzY2 9udHJvbC53aW5kb3dzLm5ldDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAL6v90zkm61M6RjxxRMI5nqBP2bt/sFOL2nhL 40M1B18CHn8vGw7jGr+js1IhF4Yy1+SUZBoXbN5TRkdwkmYGq7SClr+Va3OxdmHAXIM66TtOpFXHsETI3Yaz2H1HzukJJQDkgnD6uEV IRL5BhMGwMN2sHJb3tSroaV+nziCJz/BqfIaxQ3f53gUdwGNNya2aXswXUKBNuFB4AJRxm9lGdiio5d6ywzD4fR3nI04oGt0m3XK4Uw DmB2G+C8Akq04ax0smRQcc08EDEtDRMile//gA6xbp3/XLlXBqUGZL3cDg5eLWPpdrHd1qaA/4DA6aBSVaI48xdO6L6357TOKPAcCAw EAAaMhMB8wHQYDVR0OBBYEFMGZU4IfHXk8nigJzTMM45KzMjeVMA0GCSqGSIb3DQEBCwUAA4IBAQAMJF5kk0gj119v4wbQTr9sQr9SS 7ALfmIBQaeWjWRZvmXbEnMMA46y9nShV+d3cFrIrxuz7ynd3PU0+2HP4217VHO3rFyNbNnp4IB+BJa+hW/Hi54X+m/QPztDFCdiP1zY Wr7DNEvnebuAMAJ+W0I08h5yIcX6Z0TTZcrWc72Qyi2Y2MuYDN+AVvQ1WZWsU4gbnUK7oj8bYnLfzWWuhfks2vC5Sbx9+79j+36HtsQ nYe9ouxQ5vfNxm7wcLTQQulU16lnD0yObvr1hfteKfuW2/Ynoy5Z2ntIyCbGxiulaPLrFTW4gUhYgnteB5CwGw1C5vhv0Aa+XZouHVh oOLhWF .PARAMETER Tenant Specify the tenant. This would be required if getting specific information about an app .PARAMETER AadInstance Specify the Azure AD Instance i.e. https://login.microsoftonline.com or https://login.microsoftonline.us .PARAMETER Issuer You can specify the full Issuer. This would be required to correctly get discovery keys for Azure AD B2C .PARAMETER ApplicationId Specify the Application ID .EXAMPLE Get-AadDiscoveryKeys .EXAMPLE Get-AadDiscoveryKeys -Tenant contoso.onmicrosoft.com -ApplicationId bcdeb54f-733b-4657-8948-0f39934c2a53 .EXAMPLE Get-AadDiscoveryKeys -Issuer "https://williamfiddesb2c.b2clogin.com/tfp/williamfiddesb2c.onmicrosoft.com/B2C_1_V2_SUSI_DefaultPage/v2.0/" .NOTES General notes #> function Get-AadDiscoveryKeys { [CmdletBinding(DefaultParameterSetName='Default')] param( [Parameter(ParameterSetName = 'SetTenantAndInstance')] [string]$Tenant, [Parameter(ParameterSetName = 'SetTenantAndInstance')] [string]$AadInstance, [Parameter(ParameterSetName = 'SetIssuer')] [string]$Issuer, [string]$ApplicationId ) if($Issuer) { $Configuration = (Get-AadOpenIdConnectConfiguration -Issuer $Issuer -ApplicationId $ApplicationId) } else { $Configuration = (Get-AadOpenIdConnectConfiguration -Tenant $Tenant -AadInstance $AadInstance -ApplicationId $ApplicationId) } if(!$Configuration) { throw "$Issuer is not valid" } $KeyUrl = $Configuration.jwks_uri if(!$KeyUrl) { throw "$KeyUrl not found" } # Get the Discovery Keys Write-Host "Downloading signing keys from '$KeyUrl'" $Keys = (ConvertFrom-Json (Invoke-WebRequest $KeyUrl).Content).Keys if(!$Keys) { throw "$KeyUrl is not valid" } # Build the Output object $ReturnObject = @() foreach($Key in $Keys) { $Object = [pscustomobject]@{} $Object | Add-Member -NotePropertyName ApplicationId -NotePropertyValue $Configuration.ApplicationId $Object | Add-Member -NotePropertyName Kid -NotePropertyValue $Key.kid $Object | Add-Member -NotePropertyName Use -NotePropertyValue $Key.use $Object | Add-Member -NotePropertyName x5t -NotePropertyValue $Key.x5t $Object | Add-Member -NotePropertyName kty -NotePropertyValue $Key.kty $Object | Add-Member -NotePropertyName Modulus -NotePropertyValue $Key.n $Object | Add-Member -NotePropertyName Exponent -NotePropertyValue $Key.e if($Key.x5c) { $Certificate = ConvertFrom-AadBase64Certificate -Base64String $Key.x5c[0] $Thumbprint = $Certificate.Thumbprint $Object | Add-Member -NotePropertyName Certificate -NotePropertyValue $Certificate $Object | Add-Member -NotePropertyName Thumbprint -NotePropertyValue $Thumbprint $Object | Add-Member -NotePropertyName x5c -NotePropertyValue $Key.x5c[0] } $ReturnObject += $Object } return $ReturnObject } function Test-Get-AadDiscoveryKeys { # Provide no info Get-AadDiscoveryKeys # Provide a tenant Get-AadDiscoveryKeys -Tenant "williamfiddesb2c.onmicrosoft.com" # Provide a instance Get-AadDiscoveryKeys -AadInstance "https://login.microsoftonline.us" # Provide a Issuer with a appid Get-AadDiscoveryKeys -Issuer https://login.microsoftonline.com/williamfiddes.onmicrosoft.com/.well-known/openid-configuration?appid=bcdeb54f-733b-4657-8948-0f39934c2a53 # Provide a appid Get-AadDiscoveryKeys -Tenant "williamfiddes.onmicrosoft.com" -ApplicationId bcdeb54f-733b-4657-8948-0f39934c2a53 # Provide a B2C Issuer Get-AadDiscoveryKeys -Issuer "https://williamfiddesb2c.b2clogin.com/tfp/williamfiddesb2c.onmicrosoft.com/B2C_1_V2_SUSI_DefaultPage/v2.0/.well-known/openid-configuration" } function Get-AadKeyVaultAccessByObject { param( [Parameter( ValueFromPipeline = $true, ParameterSetName = "ByObjectId")] $ObjectId, [Parameter(ParameterSetName = "ByObjectId")] [parameter(ValueFromPipeline=$true)] [ValidateSet("User","ServicePrincipal")] [string]$ObjectType, [Parameter(ParameterSetName = "ByServicePrincipalId")] $ServicePrincipalId, [Parameter(ParameterSetName = "ByUserId")] $UserId ) if($ObjectId -and -not $ObjectType) { $ObjectType = Invoke-AadCommand -Command { Param($ObjectId) (Get-AzureADObjectByObjectId -ObjectIds $ObjectId).ObjectType } -Parameters $ObjectId } # Search for ServicePrincipal if($ServicePrincipalId) { $ObjectType = "ServicePrincipal" $sp = Get-AadServicePrincipal -Id $ServicePrincipalId If(-not $sp) { return } $ObjectId = $sp.ObjectId } # Search for User if($UserId) { $ObjectType = "User" $user = $ObjectType = Invoke-AadCommand -Command { Param($UserId) Get-AzureADUser -ObjectId $UserId } -Parameters $UserId If(-not $user) { return } $ObjectId = $user.ObjectId } $subscriptions = Invoke-AzureCommand -Command { Param($TenantId) Get-AzSubscription -TenantId $TenantId } -Parameters $Global:AadSupport.Session.TenantId $result = @() foreach($sub in $subscriptions) { if($sub.Name -ne "Access to Azure Active Directory") { Write-Verbose "Checking Subscription '$($sub.Name) (Id:$($sub.id))' " <# Invoke-AzureCommand -Command { Param($SubscriptionId) Select-AzSubscription -SubscriptionId $SubscriptionId | Out-Null } -Parameters $sub.id -SubscriptionId $sub.id #> $KeyVaults = Invoke-AzureCommand -Command { Get-AzKeyVault } -SubscriptionId $sub.id foreach($KeyVaultItem in $KeyVaults) { $KeyVaultName = $KeyVaultItem.VaultName Write-Verbose "Checking Key Vault '$KeyVaultName'" $kv = Invoke-AzureCommand -Command { Param($KeyVaultName) Get-AzKeyVault -VaultName $KeyVaultName } -Parameters $KeyVaultName -SubscriptionId $sub.id foreach($policy in $kv.AccessPolicies) { $PolicyAssignedObject = Invoke-AadCommand -Command { Param($ObjectIds) (Get-AzureADObjectByObjectId -ObjectIds $ObjectIds) } -Parameters $policy.ObjectId if($PolicyAssignedObject.ObjectType -eq "Group") { # Check if User/ServicePrincipal is a member of the group $Groups = New-Object Microsoft.Open.AzureAD.Model.GroupIdsForMembershipCheck $Groups.GroupIds = $policy.ObjectId if($ObjectType -eq "User") { $IsMemberOf = Invoke-AadCommand -Command { Param($Params) Select-AzureADGroupIdsUserIsMemberOf -ObjectId $Params.ObjectId -GroupIdsForMembershipCheck $Params.Groups } -Parameters @{ ObjectId = $ObjectId Groups = $Groups } } if($ObjectType -eq "ServicePrincipal") { $IsMemberOf = Invoke-AadCommand -Command { Param($Params) Select-AzureADGroupIdsServicePrincipalIsMemberOf -ObjectId $Params.ObjectId -GroupIdsForMembershipCheck $Params.Groups } -Parameters @{ ObjectId = $ObjectId Groups = $Groups } } # I only want to show group info if the object is assigned through group membership if($IsMemberOf) { $DirectAssignment = $false $GroupDisplayName = $PolicyAssignedObject.DisplayName $GroupObjectId = $PolicyAssignedObject.ObjectId } else { $DirectAssignment = $true } } if($policy.ObjectId -eq $ObjectId -or $IsMemberOf) { $CustomResult = [ordered]@{} $CustomResult.KeyVaultName = $KeyVaultName $CustomResult.PermissionsToSecrets = $policy.PermissionsToSecrets $CustomResult.PermissionsToKeys = $policy.PermissionsToKeys $CustomResult.PermissionsToCertificates = $policy.PermissionsToCertificates $CustomResult.PermissionsToStorage = $policy.PermissionsToStorage $CustomResult.DirectAssignment = $DirectAssignment $CustomResult.GetsAssignmentBy = "$GroupDisplayName ($GroupObjectId)" $result += $CustomResult } } } } } return $result } function Get-AadObjectCount { $MsGraphEndpoint = $Global:AadSupport.Resources.MsGraph Write-Host "Getting User Count." $UserCount = Invoke-AadCommand -Command { (Get-AzureADUser -All $true).Count } $UserDeletedCount = 0 $UserDeletedCount = (Invoke-AadProtectedApi -Client $Global:AadSupport.Clients.AzureAdPowershell.ClientId -Resource $MsGraphEndpoint -Endpoint "$MsGraphEndpoint/v1.0/directory/deletedItems/Microsoft.Graph.User").Count Write-Host "Getting Group Count." $GroupCount = Invoke-AadCommand -Command { (Get-AzureADGroup -All $true).Count } $GroupDeletedCount = 0 $GroupDeletedCount = (Invoke-AadProtectedApi -Client $Global:AadSupport.Clients.AzureAdPowershell.ClientId -Resource $MsGraphEndpoint -Endpoint "$MsGraphEndpoint/v1.0/directory/deletedItems/Microsoft.Graph.Group").Count Write-Host "Getting Device Count." $DeviceCount = Invoke-AadCommand -Command { (Get-AzureADDevice -All $true).Count } $DeviceDeletedCount = 0 $DeviceDeletedCount = (Invoke-AadProtectedApi -Client $Global:AadSupport.Clients.AzureAdPowershell.ClientId -Resource $MsGraphEndpoint -Endpoint "$MsGraphEndpoint/v1.0/directory/deletedItems/Microsoft.Graph.Device").Count Write-Host "Getting Contact (Organizational) Count." $ContactCount = Invoke-AadCommand -Command { (Get-AzureADContact -All $true).Count } $ContactDeletedCount = 0 $ContactDeletedCount = (Invoke-AadProtectedApi -Client $Global:AadSupport.Clients.AzureAdPowershell.ClientId -Resource $MsGraphEndpoint -Endpoint "$MsGraphEndpoint/beta/directory/deletedItems/Microsoft.Graph.orgContact").Count Write-Host "Getting Application Count." $AppCount = Invoke-AadCommand -Command { (Get-AzureADApplication -All $true).Count } $AppDeletedCount = 0 $AppDeletedCount = (Invoke-AadProtectedApi -Client $Global:AadSupport.Clients.AzureAdPowershell.ClientId -Resource $MsGraphEndpoint -Endpoint "$MsGraphEndpoint/beta/directory/deletedItems/Microsoft.Graph.Application").Count Write-Host "Getting ServicePrincipal Count. This one might take a while." $SpCount = Invoke-AadCommand -Command { (Get-AzureADServicePrincipal -All $true).Count } $SpDeletedCount = 0 $SpDeletedCount = (Invoke-AadProtectedApi -Client $Global:AadSupport.Clients.AzureAdPowershell.ClientId -Resource $MsGraphEndpoint -Endpoint "$MsGraphEndpoint/beta/directory/deletedItems/Microsoft.Graph.ServicePrincipal").Count Write-Host "Getting OAuth2PermissionGrant Count. This one might take a while." $OAuth2PermissionGrantCount = Invoke-AadCommand -Command { (Get-AzureADOAuth2PermissionGrant -All $true).Count } Write-Host "Getting Directory Role Count. This one might take a while." $DirectoryRoleCount = Invoke-AadCommand -Command { (Get-AzureADDirectoryRole).Count } $PolicyCount = 0 if($Global:AadSupport.Powershell.Modules.AzureAd.Name -eq "AzureADPreview") { Write-Host "Getting Azure AD Policy Count." $PolicyCount = Invoke-AadCommand -Command { (Get-AzureADPolicy -All $true).Count } if($PolicyCount.Exception) { $PolicyCount = 0 } } $TotalActiveCount = $UserCount +$GroupCount +$DeviceCount +$ContactCount +$AppCount +$SpCount +$OAuth2PermissionGrantCount +$DirectoryRoleCount +$PolicyCount $TotalDeletedCount = $UserDeletedCount +$GroupDeletedCount +$DeviceDeletedCount +$ContactDeletedCount + $AppDeletedCount +$SpDeletedCount $TotalCount = $TotalActiveCount + $TotalDeletedCount $TotalValidCount = $TotalActiveCount + [System.Math]::Ceiling($TotalDeletedCount/4) Write-Host "" Write-Host "Summary..." Write-Host " - Users: $UserCount" Write-Host " - Deleted Users: $UserDeletedCount" Write-Host " - Groups: $GroupCount" Write-Host " - Deleted Groups: $GroupDeletedCount" Write-Host " - Devices: $DeviceCount" Write-Host " - Deleted Devices: $DeviceDeletedCount" Write-Host " - Contacts: $ContactCount" Write-Host " - Deleted Contacts: $ContactDeletedCount" Write-Host " - Applications: $AppCount" Write-Host " - Deleted Applications: $AppDeletedCount" Write-Host " - ServicePrincipals: $SpCount" Write-Host " - Deleted ServicePrincipals: $SpDeletedCount" Write-Host " - OAuth2 Permission Grants: $OAuth2PermissionGrantCount" Write-Host " - Directory Roles: $DirectoryRoleCount" Write-Host " - Azure AD Policies: $PolicyCount (AzureAdPreview Required)" Write-Host "" Write-Host "Total Count: $TotalCount" Write-Host "" Write-Host "Note: Deleted objects only count for 1/4 against Object Quota." Write-Host "Total Count against Directory Quota: $TotalValidCount" Write-Host "" } <# .SYNOPSIS Gets the Azure AD Open Id Connect Configuration .DESCRIPTION Gets the Azure AD Open Id Connect Configuration PS C:\>Get-AadOpenIdConnectConfiguration Downloading configuration from 'https://login.microsoftonline.com/common/.well-known/openid-configuration' token_endpoint : https://login.microsoftonline.com/common/oauth2/token token_endpoint_auth_methods_supported : {client_secret_post, private_key_jwt, client_secret_basic} jwks_uri : https://login.microsoftonline.com/common/discovery/keys response_modes_supported : {query, fragment, form_post} subject_types_supported : {pairwise} id_token_signing_alg_values_supported : {RS256} response_types_supported : {code, id_token, code id_token, token id_token...} scopes_supported : {openid} issuer : https://sts.windows.net/{tenantid}/ microsoft_multi_refresh_token : True authorization_endpoint : https://login.microsoftonline.com/common/oauth2/authorize http_logout_supported : True frontchannel_logout_supported : True end_session_endpoint : https://login.microsoftonline.com/common/oauth2/logout claims_supported : {sub, iss, cloud_instance_name, cloud_instance_host_name...} check_session_iframe : https://login.microsoftonline.com/common/oauth2/checksession userinfo_endpoint : https://login.microsoftonline.com/common/openid/userinfo tenant_region_scope : cloud_instance_name : microsoftonline.com cloud_graph_host_name : graph.windows.net msgraph_host : graph.microsoft.com rbac_url : https://pas.windows.net ApplicationId : .PARAMETER Tenant Specify the tenant. This would be required if getting specific information about an app .PARAMETER AadInstance Specify the Azure AD Instance i.e. https://login.microsoftonline.com or https://login.microsoftonline.us .PARAMETER Issuer You can specify the full Issuer. This would be required to correctly get Open Id Connect Configuration for Azure AD B2C .PARAMETER ApplicationId Specify the Application ID .EXAMPLE Get-AadOpenIdConnectConfiguration .EXAMPLE Get-AadOpenIdConnectConfiguration -Tenant contoso.onmicrosoft.com -ApplicationId bcdeb54f-733b-4657-8948-0f39934c2a53 .EXAMPLE Get-AadOpenIdConnectConfiguration -Issuer "https://williamfiddesb2c.b2clogin.com/tfp/williamfiddesb2c.onmicrosoft.com/B2C_1_V2_SUSI_DefaultPage/v2.0/" .NOTES General notes #> function Get-AadOpenIdConnectConfiguration { [CmdletBinding(DefaultParameterSetName='Default')] param( [Parameter(ParameterSetName = 'SetTenantAndInstance')] [string]$Tenant, [Parameter(ParameterSetName = 'SetTenantAndInstance')] [string]$AadInstance, [Parameter(ParameterSetName = 'SetIssuer')] [string]$Issuer, [string]$ApplicationId ) # Populate Tenant info if($Global:AadSupport.Session.Active -and -not $Tenant) { $Tenant = $Global:AadSupport.Session.TenantId } if(-not $Tenant) { $Tenant = "common" } # Populate AadInstance info if($Global:AadSupport.Session -and -not $AadInstance) { $AadInstance = $Global:AadSupport.Session.AadInstance } if(-not $AadInstance) { $AadInstance = "https://login.microsoftonline.com" } # Set the Open ID Connect Configuration Endpoint if(!$Issuer) { $Issuer = "$AadInstance/$Tenant" } elseif($Issuer.LastIndexOf("/") -eq $Issuer.Length-1) { $Issuer = $Issuer.Substring(0,$Issuer.Length-1) } $Url = $Issuer if(!$Issuer.Contains("/.well-known/openid-configuration")) { $Url += "/.well-known/openid-configuration" } if($ApplicationId -and !$Issuer.Contains("appid=")) { $Url += "?appid=$ApplicationId" } elseif($ApplicationId -and $Issuer.Contains("appid=")) { Write-Warning "Using Application ID provided in Issuer" } elseif($Issuer.Contains("appid=")) { $ApplicationId = ($Issuer.Split("?")[1].Split("&") | where {$_ -match 'appid'}).Split("=")[1] } # Get the Discovery Keys Write-Host "Downloading configuration from '$Url'" $Configuration = (ConvertFrom-Json (Invoke-WebRequest $Url).Content) $Configuration | Add-Member -Type NoteProperty -Name "ApplicationId" -Value $ApplicationId return $Configuration } function Test-Get-AadOpenIdConnectConfiguration { # Provide no issuer Get-AadOpenIdConnectConfiguration # Provide a tenant Get-AadOpenIdConnectConfiguration -Tenant "williamfiddesb2c.onmicrosoft.com" # Provide a instance Get-AadOpenIdConnectConfiguration -AadInstance "https://login.microsoftonline.us" # Provide a AAD Issuer Get-AadOpenIdConnectConfiguration -Issuer "https://login.microsoftonline.com/williamfiddes.onmicrosoft.com" # Provide a B2C Issuer Get-AadOpenIdConnectConfiguration -Issuer "https://williamfiddesb2c.b2clogin.com/tfp/williamfiddesb2c.onmicrosoft.com/B2C_1_V2_SUSI_DefaultPage/v2.0/.well-known/openid-configuration" # Provide a Issuer with a appid Get-AadOpenIdConnectConfiguration -Issuer https://login.microsoftonline.com/williamfiddes.onmicrosoft.com/.well-known/openid-configuration?appid=bcdeb54f-733b-4657-8948-0f39934c2a53 # Provide a appid Get-AadOpenIdConnectConfiguration -Tenant "williamfiddes.onmicrosoft.com" -ApplicationId bcdeb54f-733b-4657-8948-0f39934c2a53 # Show Warning Get-AadOpenIdConnectConfiguration -Issuer https://login.microsoftonline.com/williamfiddes.onmicrosoft.com/.well-known/openid-configuration?appid=bcdeb54f-733b-4657-8948-0f39934c2a53 -ApplicationId bcdeb54f-733b-4657-8948-0f39934c2a53 } function Get-AadReportCredentialsExpiringSoon { param ( [string]$Days = 45 ) # REQUIRE AadSupport Session RequireConnectAadSupport # END REGION $apps = @() Write-Host "This might take a while." Write-Host "Getting Service Principals..." $apps += Invoke-AadCommand -Command { Get-AzureADServicePrincipal -All $true } Write-Host "Getting Applications..." $apps += Invoke-AadCommand -Command { Get-AzureADApplication -All $true } $ReturnObject = @() foreach($app in $apps) { foreach($AppCredential in $app.PasswordCredentials) { $Object = [pscustomobject]@{ ObjectType = $app.ObjectType DisplayName = $app.DisplayName ObjectId = $app.ObjectId AppId = $app.AppId CredentialType = "PasswordCredentials" CustomKeyIdentifier = $AppCredential.CustomKeyIdentifier EndDate = $AppCredential.EndDate StartDate = $AppCredential.StartDate KeyId = $AppCredential.KeyId } $ReturnObject += $Object } foreach($AppCredential in $app.KeyCredentials) { $Object = [pscustomobject]@{ DisplayName = $app.DisplayName EndDate = $AppCredential.EndDate ObjectType = $app.ObjectType CredentialType = "KeyCredentials" StartDate = $AppCredential.StartDate ObjectId = $app.ObjectId KeyId = $AppCredential.KeyId CustomKeyIdentifier = $AppCredential.CustomKeyIdentifier } $ReturnObject += $Object } } return $ReturnObject | Sort-Object EndDate | Where-Object {$_.EndDate -lt (Get-Date).AddDays($Days) } } function Get-AadReportMfaEnabled { [CmdletBinding()] param() $ScriptBlock = { Get-MsolUser -All | where {$_.StrongAuthenticationRequirements.Count -eq 1} | Select-Object -Property UserPrincipalName } Invoke-MSOnlineCommand -Command $ScriptBlock } function Get-AadReportMfaEnrolled { [CmdletBinding()] param() $ScriptBlock = { Get-MsolUser -All | where {$_.StrongAuthenticationMethods -ne $null} | Select-Object -Property UserPrincipalName } Invoke-MSOnlineCommand -Command $ScriptBlock } function Get-AadReportMfaNotEnrolled { [CmdletBinding()] param() $ScriptBlock = { Get-MsolUser -All | where {$_.StrongAuthenticationMethods.Count -eq 0} | Select-Object -Property UserPrincipalName } Invoke-MSOnlineCommand -Command $ScriptBlock } <# .SYNOPSIS Intelligence to return the service principal object by looking up using any of its identifiers. .DESCRIPTION Intelligence to return the service principal object by looking up using any of its identifiers. .PARAMETER Id Either specify Service Principal (SP) Name, SP Display Name, SP Object ID, Application/Client ID, or Application Object ID .EXAMPLE Get-AadServicePrincipal -Id 'Contoso Web App' .NOTES Returns the Service Pricpal object using Get-AzureAdServicePradmin@wiincipal and filter based on the Id parameter #> function Get-AadServicePrincipal { [CmdletBinding(DefaultParameterSetName='ByAnyId')] param( [Parameter( mandatory=$true, Position=0, ValueFromPipeline = $true, ParameterSetName = 'ByAnyId' )] $Id, [Parameter( mandatory=$true, ParameterSetName = 'ByAppId' )] $AppId, [Parameter( mandatory=$true, ParameterSetName = 'ByDisplayName' )] $DisplayName, [Parameter( mandatory=$true, ParameterSetName = 'ByServicePrincipalName' )] $ServicePrincipalName, [Parameter( mandatory=$true, ParameterSetName = 'ByReplyAddress' )] $ReplyAddress ) $Global:ServicePrincipals = @() $sp = $null $isGuid = $null # Search By AppId if ($AppId) { Write-Verbose "Looking for AppId '$AppId'" $sp = GetAadSpByAppId $AppId } # Search By ReplyAddress if ($ReplyAddress) { Write-Verbose "Looking for '$ReplyAddress'" $sp = GetAadSpByReplyAddress $ReplyAddress } # Search By ServicePrincipalName if ($ServicePrincipalName) { Write-Verbose "Looking for '$ServicePrincipalName'" $sp = GetAadSpByServicePrincipalName -Id $ServicePrincipalName } # Search By DisplayName if ($DisplayName) { Write-Verbose "Looking for '$DisplayName'" $sp = GetAadSpByDisplayName -Id $DisplayName } try { $isGuid = [System.Guid]::Parse($Id) } catch { } # Search By All (Any ID) if($Id) { Write-Verbose "Looking for '$Id'" # Search for app based on AppId or ObjectId if ($isGuid -and -not $sp) { # Search for app based on ObjectId $sp = $null $sp = try { Invoke-AadCommand -Command { Param($Id) Get-AzureADObjectByObjectId -ObjectId $Id } -Parameters $Id } catch {} if ($sp.ObjectType -eq "ServicePrincipal") { Write-Verbose "Service Principal found using ObjectId" return $sp } $appid = $Id if ($sp.ObjectType -eq "Application") { Write-Verbose "Application found! Looking for Service Principal..." $appid = $sp.AppId $sp = $null } # Search for app based on AppId $sp = GetAadSpByAppId -Id $appid if ($sp) { return $sp } } # Search for app based on ServicePrincipalName if(-not $sp) { $sp = GetAadSpByServicePrincipalName $Id } # Search for app based on DisplayName if(-not $sp) { $sp = GetAadSpByDisplayName $Id } } $Global:ServicePrincipals = $null # Exit script! Service Principal Not found if (-not $sp) { throw "$Id Service Principal not found!" } # Service Principal(s) Found > Only return One result if($sp.count -gt 1) { $sp = $sp | Out-GridView -PassThru -Title "Select Enterprise App" } return $sp } function GetAadSpByAppId { param( [Parameter( mandatory=$true, ValueFromPipeline = $true)] $Id ) Write-Verbose "Searching by AppId" try { $isGuid = [System.Guid]::Parse($Id) } catch { throw "Invalid App Id" } $sp = Invoke-AadCommand -Command { Param($AppId) Get-AzureADServicePrincipal -filter "AppId eq '$AppId'" } -Parameters $Id if ($sp) { Write-Verbose "Service Principal found using AppId" return $sp } return } function GetAadSpByReplyAddress { param( [Parameter( mandatory=$true, ValueFromPipeline = $true)] $Id ) Write-Verbose "Searching by ReplyUrls" $sps = LookupAllServicePrincipals $sp = @() $sp += $sps | Where-Object {$_.ReplyUrls -contains "$Id"} if ($sp) { Write-verbose "Service Principal '$Id' found using Reply Address" } return $sp } function GetAadSpByServicePrincipalName { param( [Parameter( mandatory=$true, ValueFromPipeline = $true)] $Id ) Write-Verbose "Searching by ServicePrincipalName" $sp = Invoke-AadCommand -Command { Param($Id) Get-AzureADServicePrincipal -filter "servicePrincipalNames/any(x:x eq '$Id')" } -Parameters $Id # Service Principal Not found yet using ServicePrincipalName (Do wider search) if(-not $sp) { $sps = LookupAllServicePrincipals $sp = @() $sp += $sps | Where-Object { $_.ServicePrincipalNames -match $Id } } if ($sp) { Write-verbose "Service Principal(s) '$Id' found using ServicePrincipalName" } return $sp } function GetAadSpByDisplayName { param( [Parameter( mandatory=$true, ValueFromPipeline = $true)] $Id ) Write-Verbose "Searching by DisplayName" $sp = Invoke-AadCommand -Command { Param($Id) Get-AzureADServicePrincipal -filter "DisplayName eq '$Id'" } -Parameters $Id # Service Principal Not found yet using DisplayName (Do wider search) if(-not $sp) { $sps = LookupAllServicePrincipals $sp = @() $sp += $sps | Where-Object {$_.DisplayName -match "$Id"} } if ($sp) { Write-Verbose "Service Principal(s) '$Id' found using DisplayName" } else { Write-Verbose "Service Principal(s) '$Id' NOT found using DisplayName" } return $sp } function LookupAllServicePrincipals() { if(-not $Global:ServicePrincipals) { Write-Verbose "Looking at all Service Principals to find your app. This might take awhile..." $Global:ServicePrincipals = Invoke-AadCommand { Get-AzureADServicePrincipal -All $true } } return $Global:ServicePrincipals } <# .SYNOPSIS Gets information for what access a Service Principal/Application has access to. .DESCRIPTION Gets information for what access a Service Principal/Application has access to. Gets Azure AD Directory Roles assigned to Service Principal Gets App Roles assigned to Service Principal Gets Consented Permissions assigned to Service Principal Gets Azure Role Assignments assigned to Service Principal (This one may take a while) Gets Key Vault Access Policies assigned to Service Principal (This one may take a while) .PARAMETER Id Provide the Service Principal ID .PARAMETER SkipAzureRoleAssignments Enable switch to skip lookup of Azure Role Assignments. .PARAMETER SkipKeyVaultAccess Enable switch to skip lookup of Azure Key Vault Access policies. .EXAMPLE Get-AadServicePrincipalAccess -Id 'Your Application Name, AppId, or Service Principal Object Id' .NOTES General notes #> function Get-AadServicePrincipalAccess { param( [Parameter( mandatory=$true, ValueFromPipeline = $true)] $Id, [switch]$SkipAzureRoleAssignments, [switch]$SkipKeyVaultAccess ) # REQUIRE AadSupport Session RequireConnectAadSupport # END REGION $TenantDomain = $Global:AadSupport.Session.TenantId $sp = (Get-AadServicePrincipal -Id $Id) if(-not $sp) { throw "'$Id' not found in '$TenantDomain'" } Write-Host "" Write-Host "Enterprise App (ServicePrincipal)" -ForegroundColor Yellow Write-Host "$($sp.DisplayName) | AppId:$($sp.AppId) | ObjectId:$($sp.ObjectId)" Write-Host "" if($sp.count -gt 1) { throw "'$Id' query returned more than one result. Please provide a unique Service Principal Identifier" } Write-Host "Getting Azure AD Directory Roles assigned to Service Principal..." $AdminRoles = Get-AadAdminRolesByObject -ObjectId $sp.ObjectId | ConvertTo-Json Write-Host "Getting App Roles (Application Permissions) assigned to Service Principal..." $AppRoles = Get-AadAppRolesByObject -ObjectId $sp.ObjectId -ObjectType $sp.ObjectType | ConvertTo-Json Write-Host "Getting OAuth2PermissionGrants (Delegated Permissions) assigned to Service Principal..." $Grants = Get-AadConsent -ClientId $sp.ObjectId -PermissionType Delegated | ConvertTo-Json if(-not $SkipKeyVaultAccess) { Write-Host "Getting Key Vault Access assigned to Service Principal..." $KeyVaultAccess = Get-AadKeyVaultAccessByObject -ObjectId $sp.ObjectId | ConvertTo-Json } if(-not $SkipAzureRoleAssignments) { Write-Host "Getting Azure Roles assigned to Service Principal..." $AzureRoles = Get-AadAzureRoleAssignments -ServicePrincipalName $sp.ServicePrincipalNames[0] -ObjectId $sp.ObjectId | ConvertTo-Json } $Report = [pscustomobject]@{ PrincipalType = $sp.ObjectType PrincipalId = $sp.AppId PrincipalDisplayName = $sp.DisplayName PrincipalObjectId = $sp.ObjectId AzureAdAdminRoles = $AdminRoles; ApplicationRoles = $AppRoles; ConsentedPermissions = $Grants; KeyVaultAccess = $KeyVaultAccess; AzureRoleAssignments = $AzureRoles; } #$ReturnObject = New-Object -TypeName psobject -Property $Report return $Report } <# .SYNOPSIS Gets a list of Service Principals assigned to a Administrator role in Azure AD .DESCRIPTION Gets a list of Service Principals assigned to a Administrator role in Azure AD .EXAMPLE Get-AadServicePrincipalAdmins .NOTES General notes #> function Get-AadServicePrincipalAdmins() { # REQUIRE AadSupport Session RequireConnectAadSupport # END REGION $roles = Invoke-AadCommand -Command { Get-AzureADDirectoryRole | Sort-Object DisplayName } $servicePrincipalAdmins = $null $list = @() foreach ($role in $roles) { if($role.DisplayName -eq "Directory Readers" -or $role.DisplayName -eq "Directory Writers"){ continue } $servicePrincipalAdmins = Invoke-AadCommand -Command { Param($RoleObjectId) Get-AzureADDirectoryRoleMember -ObjectId $RoleObjectId | where-object {$_.ObjectType -eq 'ServicePrincipal'} } -Parameters $role.ObjectId foreach ($sp in $servicePrincipalAdmins) { $item = [PSCustomObject]@{ DisplayName = $sp.DisplayName Id = $sp.ObjectId Role = $role.DisplayName } $list += $item } } Write-Host "Service Pricipals with Azure AD Admin Roles ($($list.count) Found)." -ForegroundColor Yellow $list | Sort-Object DisplayName, Role } Set-Alias -Name Get-AadSpAdmins -Value Get-AadServicePrincipalAdmins <# .SYNOPSIS Gets a list of tenant admins. .DESCRIPTION Gets a list of tenant admins. .PARAMETER Role Provide the role to lookup By Default 'Global Administrator' is used .EXAMPLE Get-AadTenantAdmins ...See a list of company admins... .EXAMPLE Get-AadTenantAdmins -Role 'Helpdesk Administrator' ...See a list of password or helpdesk admins... .EXAMPLE Get-AadTenantAdmins -Role 8da6f8d3-ef75-42e4-961e-8fba79c29048 ...You can also use Role Ids... .EXAMPLE Get-AadTenantAdmins -All ...You can see a list of all Admin Roles... .NOTES General notes #> function Get-AadTenantAdmins { [CmdletBinding(DefaultParameterSetName='All')] param ( [Parameter(Position=0,ParameterSetName="UseRole")] $Role = 'Global Administrator', [Parameter(ParameterSetName="GetAll")] [switch] $All ) begin { # REQUIRE AadSupport Session RequireConnectAadSupport # END REGION try { $isGuid = [System.Guid]::Parse($Id) } catch { } } process { # Get All Roles $AadDirectoryRoles = Invoke-AadCommand -Command { Get-AzureADDirectoryRole } if($All) { $_role = $AadDirectoryRoles } # Look up role based on ObjectID elseif($isGuid) { $_role = $AadDirectoryRoles | Where-Object {$_.ObjectId -eq $role} } # Look up role based on DisplayName else { $_role = $AadDirectoryRoles | Where-Object {$_.displayName -eq $role} } # Role not found if(-not $_role) { Write-Host "'$Role' role not found." -ForegroundColor Red Write-Host "To get a list of valid roles, run..." -ForegroundColor Red Write-Host "Get-AzureADDirectoryRole | Sort DisplayName" -ForegroundColor Red throw "'$Role' role not found." } if($_role.Count -gt 1) { foreach ($role in $_role) { $members = Invoke-AadCommand { Param($RoleId) Get-AzureADDirectoryRoleMember -ObjectId $RoleId } -Parameters $role.ObjectId $admins = $members | Select-Object DisplayName, UserPrincipalName, ObjectType | Sort-Object DisplayName $count = $admins.Count if ($admins.Count -eq $null) { $count = 1 } if ($count -gt 0) { Write-Host "========================================" Write-Host "$($role.DisplayName) ($count)" -ForegroundColor Yellow $admins | Format-Table -AutoSize -HideTableHeaders } } return } else { $members = Invoke-AadCommand { Param($RoleId) Get-AzureADDirectoryRoleMember -ObjectId $RoleId } -Parameters $role.ObjectId $admins = $members | Select-Object DisplayName, UserPrincipalName # Output list of admins Write-Host "" Write-Host "Users assigned to the '$Role' role" -ForegroundColor Yellow $admins | Sort-Object DisplayName | format-table } } end { } } <# .SYNOPSIS Get Access Token from Azure AD token endpoint. .DESCRIPTION Get Access Token from Azure AD token endpoint. .PARAMETER ClientId Provide Client ID or App ID required to get a access token .PARAMETER ResourceId Provide App URI ID or Service Principal Name you want to get an access token for. .PARAMETER Scopes Provide Scopes for the request. .PARAMETER ClientSecret Provide Client Secret if this is a Web App client. .PARAMETER Redirect Provide Redirect URI or Redirect URL. .PARAMETER Username Provide username if using Resource Owner Password grant flow. .PARAMETER Password Provide Password of resource owner if using Resource Owner Password Credential grant flow. .PARAMETER Tenant Provide Tenant ID you are authenticating to. .PARAMETER Instance Provide Azure AD Instance you are connecting to. .PARAMETER UseV2 By default, Azure AD V1 authentication endpoints will be used. Use this if you want to use the V2 Authentication endpoints. .PARAMETER UseResourceOwner Enable this switch if you want to use the Resource Owner Password Credential grant flow. .PARAMETER UseClientCredential Enable this switch if you want to use the Client Credential grant flow. .PARAMETER UseRefreshToken Enable this switch if you want to use the Refresh Token grant flow. .PARAMETER GrantType Specify the 'grant_type' you want to use. - password - client_credentials - refresh_token - authorization_code - urn:ietf:params:oauth:grant-type:jwt-bearer - urn:ietf:params:oauth:grant-type:saml1_1-bearer - urn:ietf:params:oauth:grant-type:saml2-bearer .PARAMETER Code Specify the 'code' for authorization code flow .PARAMETER Assertion Specify the 'assertion' you want to use for user assertion .PARAMETER ClientAssertionType Specify the 'client_assertion_type' you want to use. Available options... - urn:ietf:params:oauth:client-assertion-type:jwt-bearer .PARAMETER ClientAssertion Specify the 'client_assertion' you want to use. This is used for Certificate authentication where the client assertion is signed by a private key. .PARAMETER RequestedTokenUse Specify the 'requested_token_use' you want to use. For Azure AD, the only acceptable value is 'on_behalf_of'. This is used to identify the on-behalf-of flow is to be used. .PARAMETER RequestedTokenType Specify the 'requested_token_type'. This is either... - urn:ietf:params:oauth:token-type:saml2 - urn:ietf:params:oauth:token-type:saml1 .EXAMPLE Use Resource Owner Password Credential grant flow Get-AadToken -UseResourceOwner -ResourceId "https://graph.microsoft.com" -ClientId 5567ba8a-e608-4219-97d8-3d3ea63718e7 -Redirect "https://login.microsoftonline.com/common/oauth2/nativeclient" -Username john@contoso.com -Password P@$$w0rd! To get client only access token Get-AadToken -UseClientCredential -ResourceId "https://graph.microsoft.com" -ClientId 5567ba8a-e608-4219-97d8-3d3ea63718e7 -ClientSecret "98iwjdc-098343js=" .NOTES General notes #> function Get-AadToken { [CmdletBinding(DefaultParameterSetName="All")] param( [Parameter(Mandatory=$true, Position=0)] [string]$ClientId, [Parameter(ParameterSetName="UseResourceOwner", Mandatory=$false)] [switch]$UseResourceOwner, [Parameter(ParameterSetName="UseClientCredential", Mandatory=$false)] [switch]$UseClientCredential, [Parameter(ParameterSetName="UseRefreshToken", Mandatory=$false)] [switch]$UseRefreshToken, [Parameter(ParameterSetName="UseResourceOwner", Mandatory=$true)] [string]$Username = $null, [Parameter(ParameterSetName="UseResourceOwner", Mandatory=$true)] [string]$Password = $null, [string]$ClientSecret = $null, [Parameter(ParameterSetName="UseRefreshToken", Mandatory=$true)] [string]$RefreshToken, [string]$ResourceId = $($Global:AadSupport.Resources.MsGraph), [Parameter(ParameterSetName="UseClientCredential", Mandatory=$true)] [Parameter(ParameterSetName="UseRefreshToken", Mandatory=$false)] [Parameter(ParameterSetName="UseResourceOwner", Mandatory=$false)] [string]$Tenant, [string]$Scopes = "openid email profile offline_access $($Global:AadSupport.Resources.MsGraph)/.default", [string]$Redirect = $null, [string]$Instance = "https://login.microsoftonline.com", [switch]$UseV2 = $false, [string]$GrantType, [string]$Code, [string]$Assertion, [string]$ClientAssertionType, [string]$ClientAssertion, [string]$RequestedTokenUse, [ValidateSet("urn:ietf:params:oauth:token-type:saml2", "urn:ietf:params:oauth:token-type:saml1")] [string]$RequestedTokenType ) # REQUIRE AadSupport Session RequireConnectAadSupport # END REGION $error.Clear() $scriptError = $null # Get Service Principal try { $sp = Get-AadServicePrincipal -Id $ClientId # Get real Client ID $ClientId = $sp.AppId } catch { Write-Host "App '$ClientId' not found" -ForegroundColor Yellow } if($sp.count -gt 1) { throw "Found too many results for '$ClientId'. Please specify a unique ClientId." } # Get Redirect Uri if not one specified if(-not $Redirect -and $sp) { $Redirect = $sp.ReplyUrls[0] } # Set Grant_Type if (-not $UseResourceOwner -and -not $UseClientCredential -and -not $UseRefreshToken -and -not $GrantType) { do { $isValidChoice = $true Write-Host "" Write-Host "Do you want to..." -ForegroundColor Yellow Write-Host "1: Resource Owner (Provice user name and password)" Write-Host "2: Client Credential (Provice Client ID and Client Secret)" Write-Host "3: Refresh (Provide Refresh Token)" Write-Host "Type 'exit' to quit." Write-Host "" $choice = Read-Host -Prompt "Enter your choice (#)" Write-Host "" switch ($choice) { "1" { $UseResourceOwner = $true break } "2" { $UseClientCredential = $true break } "3" { $UseRefreshToken = $true break } "exit" {return} "default" { $isValidChoice = $false; break } } } while (-not $isValidChoice) } # Requirements for Resource Owner if ($UseResourceOwner) { $GrantType = "password" if (-not $Username) { $Username = Read-Host -Prompt "Username" } if (-not $Password) { $Password = Read-Host -Prompt "Password" } } # Requirements for ClientCredentials if($UseClientCredential) { $GrantType = "client_credentials" if (-not $Tenant -or $Tenant -eq "common") { $Tenant = Read-Host -Prompt "Tenant (Do not use 'common')" } if (-not $ClientSecret) { $ClientSecret = Read-Host -Prompt "ClientSecret" } } # Requirements for Refresh Token if($UseRefreshToken) { $GrantType = "refresh_token" if (-not $RefreshToken) { $RefreshToken = Read-Host -Prompt "Refresh Token" } } # Set default Tenant if (-not $Tenant) { $Tenant = "common" } # Start initializing the Token result object $token = @{} # Start initializing the token endpoint POST content $body = @{} # Lookup Resource try { $resource = Get-AadServicePrincipal -Id $ResourceId $ResourceId - $resource.AppId } catch { Write-Host "Resource '$ResourceId' not found" -ForegroundColor Yellow } # Set up if AAD V1 or V2 authentication is used... if ($UseV2) { # Use V2 endpoint $authUrl = "$Instance/$Tenant/oauth2/v2.0/token" } else { # Use V1 endpoint $authUrl = "$Instance/$Tenant/oauth2/token" $body.resource = "$ResourceId" } $token.authUrl = $authUrl $body.client_id = $ClientID $body.grant_type = $GrantType $body.scope = $scopes $body.nonce = 1234 $body.state = 5678 if ($Redirect) { $body.redirect_uri = $Redirect } if ($RefreshToken) { $body.refresh_token = $RefreshToken } if($Code) { $body.code = $Code } if ($ClientSecret) { $body.client_secret = $ClientSecret } if ($Username -and $Password) { $body.username = $Username $body.password = $Password $body.grant_type = "password" } if($ClientAssertionType) { $body.client_assertion_type = $ClientAssertionType } if($ClientAssertion) { $body.client_assertion = $ClientAssertion } if($Assertion) { $body.assertion = $Assertion } if($RequestedTokenUse) { $body.requested_token_use = $RequestedTokenUse } if($RequestedTokenType) { $body.requested_token_type = $RequestedTokenType } $token.PostContent = $body # Sign-in to Azure AD & Get Access Token try { Write-Verbose "Authenticating to '$authUrl'" $response = $null $response = Invoke-WebRequest -Method Post -Uri $authUrl -Body $body -verbose $content = $null if($response.content) { $content = $response.content | ConvertFrom-Json } elseif($response.access_token) { $content = $response } if ($content.access_token) { $token.AccessToken = $content.access_token } if ($content.id_token) { $token.IdToken = $content.id_token } if ($content.refresh_token) { $token.RefreshToken = $content.refresh_token } if ($content.Type) { $token.Type = $content.Type } if ($content.scope) { $token.Scopes = $content.scope } if ($content.expires_in) { $token.expires_in = $content.expires_in } if ($content.ext_expires_in) { $token.ext_expires_in = $content.ext_expires_in } if ($content.expires_on) { $token.expires_on = $content.expires_on } if ($content.not_before) { $token.not_before = $content.not_before } if ($content.resource) { $token.resource = $content.resource } if($token.AccessToken) { if($token.AccessToken.StartsWith("eyJ")) { $token.AccessTokenClaims = $token.AccessToken | ConvertFrom-AadJwtToken } else { $token.SamlDecoded = ConvertFrom-Base64String -base64String ( Base64UrlDecode($token.AccessToken) ) } } if($token.IdToken) { $token.IdTokenClaims = $token.IdToken | ConvertFrom-AadJwtToken } } # Acquire token failed Catch { $scriptError = $_ $scriptError = $scriptError | ConvertFrom-Json $token.Error = $scriptError $scriptError | Out-Host # AADSTS65001 if ($scriptError -match "AADSTS65001") { $consentUrl = "$Instance/$Tenant/oauth2/authorize?client_id=$ClientId&response_type=code&redirect=$RedirectUri&prompt=admin_consent" Write-Host "" Write-Host "Consent Required" -ForegroundColor Yellow Write-Host "" Write-Host "Go to the following url '$consentUrl'" -ForegroundColor Yellow } # AADSTS50079 if ($scriptError -match "AADSTS50079") { $claims = $scriptError.claims | ConvertFrom-Json $capolids = $claims.access_token.capolids.values Write-Host "" Write-Host "User Interaction Required due to Conditional Access Policy Id '$capolids'" -ForegroundColor Yellow } return $token } $Object = New-Object PSObject -Property $token if($Object.AccessTokenClaims -and -not $HideOutput) { Write-Host "" Write-Host "Access Token" -ForegroundColor Yellow Write-ObjectToHost $($Object.AccessTokenClaims) } return $Object } # ############################################################################################## # CLIENT ASSERTION # ############################################################################################## function ClientAssertion { param($ClientId, $ClientSecret, $ClientAssertion) $body = @{} $body.client_id = $ClientId $body.client_secret = $ClientSecret $body.refresh_token = [System.Web.HttpUtility]::UrlEncode($RefreshToken) $body.grant_type = "refresh_token" try { Write-Host "Authenticating to '$authUrl'" -ForegroundColor Yellow $body | ft $response = $null $response = Invoke-WebRequest -Method Post -Uri $authUrl -Body $body -verbose $content = $null if($response.content) { $content = $response.content | ConvertFrom-Json } elseif($response.access_token) { $content = $response } $AccessToken = $null $AccessToken = $content.access_token if($AccessToken) { Write-Host "Acquired Access Token successfull!" } } Catch { $scriptError = $_ if ($response) { $scriptError = $scriptError | ConvertFrom-Json $scriptError } else { throw $scriptError } } } # ---------------------------------------------------------------------------- # INFORMATION # * Used the follow resource as a guide to write this script # https://github.com/kenakamu/Microsoft.ADAL.PowerShell/blob/master/Microsoft.ADAL.PowerShell/Microsoft.ADAL.PowerShell.psm1 # * At the time of writing this... # > AzureAD Module Version (AzureAdPreview version 2.0.1.18) # > Active Directory Authentication Library version 3.19.7 # ---------------------------------------------------------------------------- <# # SAMPLE USAGE # CONFIGURATION $TenantName = "williamfiddes.onmicrosoft.com" $RedirectURI = "urn:ietf:wg:oauth:2.0:oob" $ResourceId = "https://graph.microsoft.com" # If using Native app type $ClientID = "" # If using Web app type $ClientID = "" $ClientSecret = "" # ACQUIRE TOKEN $aadContext = [AadContext]::new() $aadContext.TenantName = $TenantName $aadContext.AcquireToken($ResourceId,$ClientId,$RedirectURI,$PromptBehavior) #$aadContext.AcquireToken($Resource, $ClientId, $ClientSecret) $Headers = $aadContext.CreateAuthenticationHeaders() #> <# .SYNOPSIS Gets a token using ADAL installed with Azure AD PowerShell Module .DESCRIPTION Gets a token using ADAL installed with Azure AD PowerShell Module Prompts user to sign-in in order to acquire token. ClientID : "ApplicationId-of-the-client-application" TenantId : "tenant-id" AccessTokenClaims : @{ access-token-claims } Scopes : "scopes-in-access-token" ExtraQueryParams : "extra-query-parameters-used-to-get-token" UserId : "login_hint" PromptBehavior : "prompt-behavior-used-to-get-token" ExpiresOn : "when-the-token-expires" Authority : "authority" IdToken : "id-token" IdTokenClaims : @{ claims-in-the-id-token } UniqueId : "object-id-of-the-user" ReplyAddress : "reply-address-of-client-application" AccessToken : "access-token" DisplayableId : "user-principal-name-of-user" AppId : "ApplicationId-of-the-client-application" Audience : "ApplicationId-of-the-resource" Resource : "ApplicationId-of-the-resource" Roles : "roles-in-the-token" .PARAMETER ResourceId Set the resource in which you want to get a access token for .PARAMETER ClientId Set the Client ID/App Id in which you want to get a access token from .PARAMETER Redirect Set the Redirect URL/URI for which Azure AD will redirect back to your application .PARAMETER Prompt Sets the prompt behavior for ADAL 'Always','Auto','SelectAccount','RefreshSession','Never' .PARAMETER UserId Sets the user account to use when authenticating .PARAMETER Password Sets the password for the user account being used if using the Resource Owner Password Credential method .PARAMETER Tenant Sets the tenant in which your authenticating to Used in conjunction with Instance to form the Authority .PARAMETER Instance Sets the Azure AD Instance tenant in which your authenticating to Used in conjunction with Tenant to form the Authority .PARAMETER UseClientCredential Tell ADAL to use the Client Credential flow (Gets a client only access token) .PARAMETER UseResourceOwnerPasswordCredential Tell ADAL to use the UserPasswordCredential method. This is actually the Windows Integrated Authentication extension method in ADAL and can also be used for the Resource Owner Password Credential flow for non-federated users. .PARAMETER ClientSecret Sets the Client Secret for ADAL to use during Client Credential flow .EXAMPLE Prompt user to sign-in to get access token Get-AadTokenUsingAdal -ResourceId "https://graph.microsoft.com" -ClientId 'Contoso Native App' -Redirect "https://login.microsoftonline.com/common/oauth2/nativeclient" To get client only access token Get-AadTokenUsingAdal -UseClientCredential -ResourceId "https://graph.microsoft.com" -ClientId 'Contoso Service App' -ClientSecret "98iwjdc-098343js=" Use the Resource Owner Password Credential Flow or us WIA (Password required) Get-AadTokenUsingAdal -UseResourceOwnerPasswordCredential -ResourceId "https://graph.microsoft.com" -ClientId 'Contoso Service App' -UserId 'your-user-name' -Password "your-secret-password" .NOTES General notes #> function Get-AadTokenUsingAdal { [CmdletBinding(DefaultParameterSetName="All")] param( [Parameter(Mandatory=$true,Position=0)] $ClientId, $ResourceId = $($Global:AadSupport.Resources.MsGraph), [Parameter(ParameterSetName="UserAccessToken", Mandatory=$false)] [Parameter(ParameterSetName="ClientAccessToken", Mandatory=$false)] $Redirect, [Parameter(ParameterSetName="UserAccessToken", Mandatory=$false)] [Parameter(ParameterSetName="RopcFlow", Mandatory=$true)] $UserId, [Parameter(ParameterSetName="UserAccessToken", Mandatory=$false)] [Parameter(ParameterSetName="RopcFlow", Mandatory=$true)] $Password, [Parameter(ParameterSetName="UserAccessToken", Mandatory=$false)] $DomainHint, [Parameter(Mandatory=$false)] [ValidateSet('Always','Auto','SelectAccount','RefreshSession','Never')] $Prompt, $Tenant, $Instance, [Parameter(ParameterSetName="ClientAccessToken", Mandatory=$true)] [switch] $UseClientCredential, [Parameter(ParameterSetName="RopcFlow", Mandatory=$true)] [switch] $UseResourceOwnerPasswordCredential, [Parameter(ParameterSetName="ClientAccessToken", Mandatory=$true)] $ClientSecret, [switch] $SkipServicePrincipalSearch, [switch] $HideOutput ) # Convert Password string to SecuredString if($Password) { [SecureString]$SecuredPassword = ConvertTo-SecureString $Password -AsPlainText -Force } "Get-AadTokenUsingAdal::Params:Instance:$Instance" | Log-AadSupport if(-not $Instance) { if($Global:AadSupport.Session.AadInstance) { $Instance = $Global:AadSupport.Session.AadInstance } else { $Instance = "https://login.microsoftonline.com" } } if(-not $Tenant) { if($Global:AadSupport.Session.TenantId) { $Tenant = $Global:AadSupport.Session.TenantId } else { $Tenant = "common" } } # Get Service Principal if(-not $SkipServicePrincipalSearch -and $Global:AadSupport.Session.Active) { try{ $sp = Get-AadServicePrincipal -Id $ClientId $ClientId = $sp.AppId } catch { Write-Host "App '$ClientId' not found" -ForegroundColor Yellow } if(-not $Redirect -and $sp) { $Redirect = $sp.ReplyUrls[0] } # Lookup Resource try { $resource = Get-AadServicePrincipal -Id $ResourceId $ResourceId = $resource.AppId } catch { Write-Host "Resource '$ResourceId' not found" -ForegroundColor Yellow } } if($sp.count -gt 1) { throw "Found too many results for '$ClientId'. Please specify a unique ClientId." } if(-not $UserId -and -not $Prompt) { $UserId = $Global:AadSupport.Session.AccountId } $ExtraQueryParams = "" if ($UserId) { $ExtraQueryParams += "&login_hint=$UserId" } if ($DomainHint) { $ExtraQueryParams += "&domain_hint=$DomainHint" } # Use the Client Credential Flow if ($UseClientCredential) { if($Tenant -eq "common") { throw "Invalid Tenant. When using Client Credentials, Tenant is required." } $result = Invoke-AdalCommand -Command { Param($Params) $result = Get-AadSupportTokenForClient -Authority $Params.Authority -ClientId $Params.ClientId -ResourceId $Params.ResourceId -ClientSecret $Params.ClientSecret return $result } -Parameters @{ Authority = "$Instance/$Tenant/" ResourceId = $ResourceId ClientId = $ClientId ClientSecret = $ClientSecret } } # Use the Resource Owner Password Credential Flow if($UseResourceOwnerPasswordCredential) { $result = Invoke-AdalCommand -Command { Param($Params) $result = Get-AadSupportTokenForUserWithPassword -Authority $Params.Authority -ClientId $Params.ClientId -ResourceId $Params.ResourceId -UserId $Params.UserId -Password $Params.Password return $result } -Parameters @{ ClientId = $ClientId ResourceId = $ResourceId UserId = $UserId Password = $SecuredPassword Authority = "$Instance/$Tenant/" } } # Determine and use a Interactive flow if (-not $UseClientCredential -and -not $UseResourceOwnerPasswordCredential) { Write-Verbose "Use Interactive flow." if($Prompt -ne "Always" -and $Prompt -ne "SelectAccount") { $result = Invoke-AdalCommand -Command { Param($Params) $result = Get-AadSupportTokenForUser -Authority $Params.Authority -ClientId $Params.ClientId -ResourceId $Params.ResourceId -RedirectURI $Params.RedirectURI -PromptBehavior $Params.Prompt -ExtraQueryParameters $Params.ExtraQueryParameters -UserId $Params.UserId return $result } -Parameters @{ ClientId = $ClientId ResourceId = $ResourceId RedirectURI = $Redirect UserId = $UserId ExtraQueryParameters = $ExtraQueryParams Prompt = $Prompt Authority = "$Instance/$Tenant/" } } else { Write-Host "Authenticating user..." -ForegroundColor Yellow $result = Invoke-AdalCommand -Command { Param($Params) $result = Get-AadSupportTokenForUser -Authority $Params.Authority -ClientId $Params.ClientId -ResourceId $Params.ResourceId -RedirectURI $Params.RedirectURI -PromptBehavior $Params.Prompt return $result } -Parameters @{ Authority = "$Instance/$Tenant/" ClientId = $ClientId ResourceId = $ResourceId RedirectURI = $Redirect Prompt = $Prompt } } } $details = @{} $details.AppId = $ClientId $details.ReplyAddress = $Redirect $details.Audience = $ResourceId if($result.Error -or $result.Exception) { $details.Error = $result.ErrorDetails.Exception.Message if (-not $HideOutput) { Write-Host "" Write-Host "Error" -ForegroundColor Yellow $AadError = $details.Error.Message Write-Host $AadError -ForegroundColor Red # If Output is allowed, lets return the error return $details } #Otherwise throw the error to stop the program return $result.Exception } foreach($member in $result | Get-member) { if ($member.MemberType -eq 'NoteProperty') { $details[$member.Name] = $result.($member.Name) } } $Headers = @{ "Authorization" = "Bearer $($details.AccessToken)" } $details.Headers = $Headers $details.AccessTokenClaims = $details.AccessToken | ConvertFrom-AadJwtToken $details.Scopes = $details.AccessTokenClaims.scp $details.Roles = $details.AccessTokenClaims.Roles if($details.IdToken) { $details.IdTokenClaims = $details.IdToken | ConvertFrom-AadJwtToken } $Object = New-Object -TypeName PSObject -Property $details if($Object.AccessToken -and -not $HideOutput) { Write-Host "" Write-Host "Access Token" -ForegroundColor Yellow $AccessTokenClaims = $Object.AccessTokenClaims Write-ObjectToHost $AccessTokenClaims } return $Object #} //Write-Host "RUNNING Invoke-AdalCommand" //Invoke-AdalCommand -Command $ScriptBlock -Parameters $Params Write-Host "END Invoke-AdalCommand" } function Test-GetAadTokenUsingAdal { Get-AadTokenUsingAdal -UseClientCredential -ClientId 'aadsupport unittest' -ResourceId 'Microsoft Graph' -ClientSecret 'iu?6uN35Mp9b?]]KqMwnpElTBy.S:/5-' -Tenant williamfiddes.onmicrosoft.com } # ############################################################################ # FEATURE REQUESTS # # $AdalAssembly = Allow to set custom Adal Assembly path # Implement AcquireToken using user credentials # Implement AcquireToken using OBO # Implement Options to set additional parameters # > AAD Instance # > Set login_hint # > Set domain_hint # > Set Prompt Behavior <# .SYNOPSIS Convert a base64Encoded Json Web Token to a PowerShell object. # .DESCRIPTION Convert a base64Encoded Json Web Token to a PowerShell object. .PARAMETER Token Parameter description .EXAMPLE EXAMPLE 1 "eyJ***" | ConvertFrom-AadJwtToken EXAMPLE 2 ConvertFrom-AadJwtToken -Token "eyJ***" .NOTES General notes #> function Get-AadTrustedSites { $CurrentLocation = Get-Location $Key = "HKCU:\Software\Microsoft\Windows\CurrentVersion\Internet Settings\ZoneMap\domains" TraverseKey -Key $Key Set-Location -Path $CurrentLocation } function TraverseKey { Param( [parameter(Mandatory=$true, Position=0, ValueFromPipeline = $true)] [string]$Key ) $Key = $Key.Replace("HKEY_CURRENT_USER","HKCU:") $items = Get-ChildItem -Path $Key $items $Sites = @() foreach($item in $items) { $itemName = $item.Name.Replace("HKEY_CURRENT_USER","HKCU:") if($Key -eq $itemName) { continue } TraverseKey -Key $item.Name } } <# .SYNOPSIS Gets information for what access a user has access to. .DESCRIPTION Gets information for what access a user has access to. .PARAMETER Id Provide the User Principal Name or User Object ID .EXAMPLE Get-AadUserAccess -Id 'UserPrincipalName or User Object ID' .NOTES General notes #> function Get-AadUserAccess { param( [Parameter( mandatory=$true, ValueFromPipeline = $true)] $Id, [switch]$SkipAzureRoleAssignments, [switch]$SkipKeyVaultAccess ) # REQUIRE AadSupport Session RequireConnectAadSupport # END REGION $TenantDomain = $Global:AadSupport.Session.TenantId $user = Invoke-AadCommand -Command { Param($Id) Get-AzureAdUser -ObjectId $Id } -Parameters $Id if(-not $user) { throw "'$Id' not found in '$TenantDomain'" } Write-Host "" Write-Host "User Account" -ForegroundColor Yellow Write-Host "$($user.UserPrincipalName) | ObjectId:$($user.ObjectId)" Write-Host "" Write-Host "Getting Azure AD Directory Roles assigned to User..." $AdminRoles = Get-AadAdminRolesByObject -ObjectId $user.ObjectId | ConvertTo-Json Write-Host "Getting App Roles (Application Permissions) assigned to User..." $AppRoles = Get-AadAppRolesByObject -ObjectId $user.ObjectId -ObjectType $user.ObjectType | ConvertTo-Json Write-Host "Getting OAuth2PermissionGrants (Delegated Permissions) assigned to User..." $Grants = Get-AadConsent -UserId $user.ObjectId | ConvertTo-Json if(-not $SkipKeyVaultAccess) { Write-Host "Getting Key Vault Access assigned to User..." $KeyVaultAccess = Get-AadKeyVaultAccessByObject -ObjectId $user.ObjectId | ConvertTo-Json } if(-not $SkipAzureRoleAssignments) { Write-Host "Getting Azure Roles assigned to User..." $AzureRoles = Get-AadAzureRoleAssignments -SigninName $user.UserPrincipalName | ConvertTo-Json } $Report = [pscustomobject]@{ PrincipalType = $user.ObjectType PrincipalId = $user.UserPrincipalName PrincipalDisplayName = $user.DisplayName PrincipalObjectId = $user.ObjectId AzureAdAdminRoles = $AdminRoles; ApplicationRoles = $AppRoles; ConsentedPermissions = $Grants; KeyVaultAccess = $KeyVaultAccess; AzureRoleAssignments = $AzureRoles; } #$ReturnObject = New-Object -TypeName psobject -Property $Report return $Report } function Get-AadUserInfo { param ( [Parameter(Position=0, Mandatory=$true)] [string]$Token ) $response = Invoke-AadProtectedApi -Endpoint "https://login.microsoftonline.com/common/openid/userinfo" -AccessToken $Token return $response } function Get-AadUserRealm { param ( [Parameter(Mandatory=$true)] [string]$UserPrincipalName, [string]$AadInstance ) if(-not $AadInstance) { $AadInstance = $Global:AadSupport.Session.AadInstance } # If AadInstance is still null, lets set a default if(-not $AadInstance) { $AadInstance = "https://login.microsoftonline.com" } return (Invoke-WebRequest -Uri "$AadInstance/getuserrealm.srf?login=$UserPrincipalName" -UseBasicParsing).Content | ConvertFrom-Json } <# .SYNOPSIS Import Azure RBAC Role Assignments from a CSV exported using Export-AadAzureRoleAssignments .DESCRIPTION Import Azure RBAC Role Assignments from a CSV exported using Export-AadAzureRoleAssignments We also try to map external guest accounts to accounts that exist in the tenant you are importing to. When running this cmdlet, you may see the following messages... * Assigning to scope '/' level not allowed > This is a Azure AD tenant setting where the user has enabled 'Access management for Azure resources' > https://docs.microsoft.com/en-us/azure/role-based-access-control/elevate-access-global-admin * This is a Unknown Role Assignment. > This object probably does not exist anymore in the Azure AD tenant. .PARAMETER ImportCsv Provide the Azure subscription CSV file exported from 'Export-AadAzureRoleAssignments' .PARAMETER SubId Provide the Azure subscription ID you want to import into. .EXAMPLE Import-AadAzureRoleAssignments -SubId 'efb4bb0c-e454-4530-8753-753f22c8f901' -ImportCsv '.\Subscription--Pay-As-You-Go-Roles.csv' .NOTES General notes #> function Import-AadAzureRoleAssignments { # Ensure you are signed in to Az Account (Connect-AzAccount) # Ensure you are signed in to AzureAD (Connect-AzureAD) # $ImportCSV : Location of CSV file for role assignments # $SubId : Azure subscription ID to re-apply permissions to param ( [Parameter(Mandatory=$true)] [string]$ImportCsv, [Parameter(Mandatory=$true)] [string]$SubId ) # REQUIRE AadSupport Session if($Global:AadSupportModule) { RequireConnectAadSupport } # END REGION $domains = Invoke-AadCommand -Command { Get-AzureADDomain } $InitialDomain = ($domains | where {$_.IsInitial -eq $true}).Name $HaveAccess = 0 try { Invoke-AzureCommand { Param($Params) Set-AzContext -Subscription $Params.Subscription -Tenant Params.Tenant | Out-Null } -Parameters @{ Subscription = $SubId Tenant = $Global:AadSupport.Session.TenantId } -SubscriptionId $SubId $HaveAccess = $True } Catch { $HaveAccess = $False Write-Host "$SubId does not exist or You don't have access to it" -ForegroundColor Red } If ($HaveAccess) { $roles = Import-CSV $ImportCsv if(-not $roles) { return } # Start assigning roles foreach ($role in $roles) { $RoleDefinitionName = $role.RoleDefinitionName $RoleDisplayName = $role.DisplayName $RoleObjectId = $role.ObjectId $RoleScope = $role.scope $RoleSignInName = $role.SignInName $RoleDefId = $role.RoleDefinitionId if($RoleDisplayName) { Write-Host "Assigning role for '$RoleDisplayName' to '$RoleDefinitionName' @ '$RoleScope'" } else { Write-Host "Assigning role for '$RoleObjectId' to '$RoleDefinitionName' @ '$RoleScope'" } if($RoleDefinitionName -match "ServiceAdministrator" -or $RoleDefinitionName -match "AccountAdministrator") { Write-Host " -- Skipping assignment for '$RoleDisplayName' to '$RoleDefinitionName'. Not possible with this script." Continue } if ($role.Scope -ne "/") { # Skip Azure AD scope assignments as it is not possible to assign to this scope # Unknown Role Assignments if($role.ObjectType -eq "Unknown") { Write-Host " -- This is a Unknown Role Assignment. Most likely the Azure AD Object assigned to this was deleted." Continue } # Group Role Assignment elseif ($role.ObjectType -eq "Group") { $group = Invoke-AadCommand { Param($RoleDisplayName) Get-AzureAdGroup -Filter "DisplayName eq '$RoleDisplayName'" } -Parameters $role.DisplayName if ($group.count -eq 1) { $GroupId = $group.id try{ $assignment = $null $assignment = Invoke-AzureCommand -Command { Param($Params) Get-AzRoleAssignment -scope $Params.Scope -ObjectId $Params.ObjectId -RoleDefinitionId $Params.RoleDefinitionId } -Parameters @{ Scope = $rolescope ObjectId = $GroupId RoleDefinitionId = $RoleDefId } -SubscriptionId $SubId if(-not $assignment) { Invoke-AzureCommand -Command { Param($Params) New-AzRoleAssignment -scope $Params.Scope -ObjectId $Params.ObjectId -RoleDefinitionId $Params.RoleDefinitionId } -Parameters @{ Scope = $rolescope ObjectId = $GroupId RoleDefinitionId = $RoleDefId } -SubscriptionId $SubId } } catch{ Write-Host " -- The role assignment already exists." -ForegroundColor Yellow } } elseif ($group.count -gt 1) { write-Host " -- Could not assign Access Control to Group $roleDisplayName" -ForegroundColor Yellow write-Host " -- Multiple groups exist with the same DisplayName; unable to identify which group to assign Access Control" -ForegroundColor Yellow } else { write-Host " -- Could not assign Access Control to Group '$roleDisplayName'" -ForegroundColor Yellow write-Host " -- No groups exist with that name" -ForegroundColor Yellow } Continue } # User Role Assignment elseif ($role.ObjectType -eq "User") { $isExternal = $false # User is external and SignInName has underscore to be replaced by @ #Modify SignInName if external user $i = $role.SignInName.indexOf("#EXT#") if ($i -eq -1) { $i = $role.SignInName.length } else { $isExternal = $true } $role.SignInName = $role.SignInName.Substring(0,$i) if ($isExternal -eq $true) { $ati = $role.SignInName.lastindexOf("_") $part1 = $role.SignInName.Substring(0,$ati) $part2 = $role.SignInName.Substring(($ati+1)) $role.SignInName = $part1 + "@" + $part2 } # Look for UPN suffix $ati = $role.SignInName.indexOf("@") $suffix = $role.SignInName.Substring(($ati+1)) # Check if user domain is verified, If not then this user is still external user $domains = Invoke-AadCommand -Command { Get-AzureAdDomain } $DomainExists = ($domains | where {$suffix -match $_.Name}).Count if (!$DomainExists) { $role.SignInName = $role.SignInName.Replace("@","_") $role.SignInName = $role.SignInName + "#EXT#@" + $InitialDomain } $SignInName = $role.SignInName $user = Invoke-AadCommand { Param($ObjectId) Get-AzureADUser -ObjectId $ObjectId } -Parameters $role.SignInName $UserObjectId = $user.ObjectId if ($RoleDefinitionName -eq "CoAdministrator") { $rolescope = "/subscriptions/$subid" $RoleDefId = "8e3af657-a8ff-443c-a75c-2fe8c4bcb635" } if ($user.count -eq 1) { try { $assignment = $null $assignment = Invoke-AzureCommand -Command { Param($Params) Get-AzRoleAssignment -Scope $Params.Scope -ObjectId $Params.ObjectId -RoleDefinitionId $Params.RoleDefinitionId } -Parameters @{ Scope = $RoleScope ObjectId = $UserObjectId RoleDefinitionId = $RoleDefId } -SubscriptionId $SubId if(-not $assignment) { Invoke-AzureCommand -Command { Param($Params) New-AzRoleAssignment -Scope $Params.Scope -ObjectId $Params.ObjectId -RoleDefinitionId $Params.RoleDefinitionId } -Parameters @{ Scope = $RoleScope ObjectId = $UserObjectId RoleDefinitionId = $RoleDefId } -SubscriptionId $SubId } else { Write-Host " -- Role Assignment already exists." } } catch { } } elseif ($user.count -eq 0) { write-Host " -- Could not assign Access Control to User $roleSignInName" -ForegroundColor Yellow write-Host " -- User does not exist or can not be found!" -ForegroundColor Yellow } Continue } # Service Principal Role Assignment elseif ($role.ObjectType -match "ServicePrincipal") { $sp = Invoke-AadCommand -Command { Param($RoleDisplayName) Get-AzureAdServicePrincipal -Filter "DisplayName eq '$RoleDisplayName'" } -Parameters $RoleDisplayName if($sp) { $assignment = $null $assignment = Invoke-AzureCommand -Command { Param($Params) Get-AzRoleAssignment -scope $Params.Scope -ObjectId $Params.ObjectId -RoleDefinitionId $Params.RoleDefinitionId } -Parameters @{ Scope = $RoleScope ObjectId = $sp.ObjectId RoleDefinitionId = $RoleDefId } -SubscriptionId $SubId if(-not $assignment) { Invoke-AzureCommand -Command { Param($Params) New-AzRoleAssignment -scope $Params.Scope -ObjectId $Params.ObjectId -RoleDefinitionId $Params.RoleDefinitionId } -Parameters @{ Scope = $RoleScope ObjectId = $sp.ObjectId RoleDefinitionId = $RoleDefId } -SubscriptionId $SubId } else { Write-Host " -- Role Assignment already exists." } } else { write-host " -- Did not find Service Principal: $RoleDisplayName" -ForegroundColor Yellow } Continue } } else { write-host "Assigning to scope '/' level not allowed" -ForegroundColor Yellow} } } } <# .SYNOPSIS Make a API request to a protected AAD resource using a Accesss Token. .DESCRIPTION Make a API request to a protected AAD resource using a Accesss Token. .PARAMETER Endpoint This is the API url you are making a request to. .PARAMETER AccessToken Pass the access token to the protected API .PARAMETER GET Make a GET request to the protected API .PARAMETER PATCH Use the PATCH Http Method .PARAMETER POST Use the POST Http Method .PARAMETER DELETE Use the DELETE Http Method .PARAMETER PUT Use the PUT Http Method .PARAMETER Body Include a body content with your API request .PARAMETER ContentType Specify a ContentType to use. (Default application/json) .EXAMPLE Invoke-AadProtectedApi -GET -Endpoint "https://graph.microsoft.com/v1.0/me" -AccessToken "eyJ***" .NOTES General notes #> function Invoke-AadProtectedApi { [CmdletBinding(DefaultParameterSetName="All")] Param( [Parameter(Mandatory=$true)] [string]$Endpoint, [Parameter(ParameterSetName="ProvideToken_Get", Mandatory=$True)] [string]$AccessToken, [Parameter(ParameterSetName="GetToken_Get", Mandatory=$True)] [string]$Client, [Parameter(ParameterSetName="GetToken_Get", Mandatory=$True)] [string]$Resource, [ValidateSet('GET','PATCH','POST','PUT','DELETE')] $Method = "GET", [string] $Body, $Headers = $null, $ContentType = "application/json", [string]$RerunTimes = 1 ) #end param # Parameter requirements if ( ($Method -eq "POST" -or $Method -eq "PATCH" -or $Method -eq "PUT") -and -not $Body ) { throw "Body required when using POST, PATCH, or PUT" } if(-not $AccessToken -and -not $Client) { throw "You must specify either -AccessToken or -Client" } # Check if Body is file if($Body) { if(Test-Path -Path $Body) { $content = Get-Content -Path $Body -Raw $Body = $content } } if($Client -and $Resource) { if($Client -eq $Global:AadSupport.Clients.AzureAdPowerShell.ClientId -or $Client -eq $Global:AadSupport.Clients.AzurePowerShell.ClientId ) { $ClientId = $Client } else { $ClientId = (Get-AadServicePrincipal -Id $Client).AppId } $ResourceId = (Get-AadServicePrincipal -Id $Resource).AppId if(!$ClientId) { $ClientId = $Client } if(!$ResourceId) { $ResourceId = $Client } write-verbose "Getting token for $Client and $Resource" $token = Get-AadTokenUsingAdal -ClientId $ClientId -ResourceId $ResourceId -UserId $Global:AadSupport.Session.AccountId -Prompt Auto -HideOutput -SkipServicePrincipalSearch if($token["Error"]) { return $token["Error"] } write-verbose "$($token)" $AccessToken = $token.AccessToken } # # BEGIN # Re-run logic # Used to help repro Throttling errors or just simply call the API # of times # if($RerunTimes -gt 0) { $Params = @{ Endpoint = $Endpoint AccessToken = $AccessToken Method = $Method Body = $Body ContentType = $ContentType Headers = $Headers } $JobGuids = @() for ($i=1; $i -le $RerunTimes; $i++) { $JobGuid = New-Guid $JobGuids += $JobGuid $job = { Param( $Params ) $Method = $Params.Method $Endpoint = $Params.Endpoint $AccessToken = $Params.AccessToken $Body = $Params.Body $ContentType = $Params.ContentType $Headers = $Params.Headers $Result = @{} $Result.Content = @() $nextLink = $null $RetryAttempts = 0 $MaxRetryAttempts = 5 do { # # Start API # if($nextLink) { $endpoint = $nextLink $nextLink = $null } try { write-verbose "Making Graph call..." if(!$Headers) { $Headers = @{} } $Headers.Authorization = "Bearer $AccessToken" if ($Method -eq "GET") { $request = Invoke-WebRequest -Headers $Headers -Uri $endpoint -Method GET -ContentType $ContentType } if ($Method -eq "POST" -or $Method -eq "PATCH" -or $Method -eq "PUT") { $request = Invoke-WebRequest -Headers $Headers -Uri $endpoint -Method $Method -Body $Body -ContentType $ContentType } if ($Method -eq "DELETE") { $request = Invoke-WebRequest -Headers $Headers -Uri $endpoint -Method $Method -ContentType $ContentType } } catch{ $RetryAttempts++ Start-Sleep -Milliseconds (1000*$RetryAttempts*2) Write-Host "Exception calling API." -ForegroundColor Red if($request.Response.Content) { $String = $request.Response.Content } elseif($_.Exception.Response) { $reqstream = $_.Exception.Response.GetResponseStream() $reqstream.Position = 0 $stream = [System.IO.StreamReader]::new($reqstream) $String = $stream.ReadToEnd() $stream.Close() $reqstream.Close() } Write-Host $String -ForegroundColor Yellow if($_.Exception.Response.StatusCode -eq "429") { Write-Host "Throttling limit hit: StatusCode 429: Waiting 1 minute. It may take up to 5 minutes" -ForegroundColor Yellow Start-Sleep -Seconds 61 Continue } elseif($_.Exception.Response.StatusCode -eq "Unauthorized") { Write-Host "Invalid Access Token." -ForegroundColor Yellow throw $_ } elseif($_.Exception.Response.StatusCode -eq "Forbidden") { Write-Host "Missing Permissions." -ForegroundColor Yellow Start-Sleep -Seconds 300 throw $_ } if($RetryAttempts -gt $MaxRetryAttempts) { throw $_ } Write-Host "" Write-Host "Retrying request!" -ForegroundColor Yellow continue } try{ $JsonObject = $request.Content | ConvertFrom-Json if($JsonObject.Value) { $Result.Content += $JsonObject.Value if($JsonObject.'@odata.nextLink') { $nextLink = $JsonObject.'@odata.nextLink' } if($JsonObject.'odata.nextLink') { if(-not $nextLink -match "https") { $nextLink = $JsonObject.'odata.nextLink' Write-Host "nextLink is not a valid web address." Write-Host $nextLink } } } else{ $Result.Content += $JsonObject } } catch{ $Result.Content += $request.Content } } while ($nextLink) $Result.Headers = $request.Headers $Result.StatusCode = $request.StatusCode $Result.Response = $request if($Result) { Write-Verbose $($request) } $ReturnObject = New-Object -TypeName PsCustomObject -Property $Result if($ReturnObject.Content) { $members = $ReturnObject.Content | Get-Member | where {$_.MemberType -eq "NoteProperty"} $ValuePropertyExist = $members.Name.Contains("value") } if(!$ReturnObject.Content.Value -and $ValuePropertyExist) { return $null } return $ReturnObject.Content } Start-Job -ScriptBlock $job -Name $JobGuid -ArgumentList $Params | out-null } $CompletedJobs = @() foreach($JobId in $JobGuids) { Write-Verbose "Completing JobId: $JobId" $CompletedJobs += Wait-Job -Name $JobId | Receive-Job $CompletedJobs } return $CompletedJobs } # END # } <# .SYNOPSIS Quickly create a Self-Signed Certificate to be used for authentication for the Azure AD application. .DESCRIPTION Quickly create a Self-Signed Certificate to be used for authentication for the Azure AD application. This will export a PFX that will contain the Private key. This will also export the CER that contains the Public Key which can be uploaded to the app in Azure AD. Once you have uploaded the CER file to Azure AD on the applicatication, you can use the PFX and run the New-AadClientAssertion cmdlet to generate a client assertion. .PARAMETER CertificatePassword Either specify Service Principal (SP) Name, SP Display Name, SP Object ID, Application/Client ID, or Application Object ID .PARAMETER ClientId Specify the Azure AD application this certificate is meant for. .PARAMETER CertificateName When the certificates are exported, you can use this to customize the file name of the certificates. .PARAMETER AddToApplication This is a switch. When used, it will add the public key certificate to the Application object in Azure AD. .EXAMPLE New-AadApplicationCertificate -ClientId ac4dff1b-b7d7-4453-a4e4-613210c686c9 -CertificatePassword "SomePassword" -AddToApplication .NOTES #> function New-AadApplicationCertificate { [CmdletBinding(DefaultParameterSetName='DefaultSet')] Param( [Parameter(mandatory=$true)] [string]$CertificatePassword, [Parameter(mandatory=$true, ParameterSetName = 'ClientIdSet')] [string]$ClientId, [string]$CertificateName, [Parameter(mandatory=$false, ParameterSetName = 'ClientIdSet')] [switch]$AddToApplication ) if($ClientId) { # Get Application object from Azure AD $app = Get-AadApplication -id $ClientId } else { $CliendId = "app" } # Create self-signed Cert $notAfter = (Get-Date).AddYears(2) try { $cert = (New-SelfSignedCertificate -DnsName "cert://$ClientId" -CertStoreLocation "cert:\LocalMachine\My" -KeyExportPolicy Exportable -Provider "Microsoft Enhanced RSA and AES Cryptographic Provider" -NotAfter $notAfter) Write-Verbose "Cert Hash: $($cert.GetCertHash())" Write-Verbose "Cert Thumbprint: $($cert.Thumbprint)" } catch { Write-Error "ERROR. Probably need to run as Administrator." Write-host $_ return } if(!$CertificateName) { $CertificateName = "aad-$ClientId" } # Export Public portion of Certificate $CerPath = "$CertificateName.cer" Export-Certificate -Cert $cert -FilePath $CerPath # Export PFC with private key $PfxPath = "$CertificateName.pfx" $SecuredPwd = ConvertTo-SecureString -String $CertificatePassword -Force -AsPlainText Export-PfxCertificate -cert $cert -FilePath "$PfxPath" -Password $SecuredPwd Write-Host "" Write-Host "Exported Public Key Certificate to..." -ForeGroundColor Yellow Write-Host "$CerPath" Write-Host "" Write-Host "Exported PFX with Private Key to..." -ForeGroundColor Yellow Write-Host "$PfxPath" if($AddToApplication) { $AppObjectId = $app.ObjectId $KeyValue = [System.Convert]::ToBase64String($cert.GetRawCertData()) Write-Verbose "App Object Id: $AppObjectId" Write-Verbose "Key Value: $KeyValue" invoke-AadCommand -Command { Param($Params) New-AzureADApplicationKeyCredential -ObjectId $Params.ObjectId -Type AsymmetricX509Cert -Usage Verify -Value $Params.Value } -Parameters @{ ObjectId = $AppObjectId Value = $KeyValue } } function GetX509Certificate($certThumbprint){ $x509cert = Get-ChildItem "Cert:\LocalMachine\My" | Where-Object { $_.Thumbprint -eq $certThumbprint } | Select-Object -First 1 if(!$x509cert){ $x509cert = Get-ChildItem "Cert:\CurrentUser\My" | Where-Object { $_.Thumbprint -eq $certThumbprint } | Select-Object -First 1 } Write-Host "Cert = {$x509cert}" Return $x509cert } } <# .SYNOPSIS Generates a client assertion for Azure AD. .DESCRIPTION Generates a client assertion for Azure AD. Requires a PFX with a Private Key. You can create one using "New-AadApplicationCertificate" .PARAMETER ClientId Specify the Azure AD application this client assertion is meant for. .PARAMETER CertificatePath Path to certificate (PFX) .PARAMETER CertificatePassword Password for the PFX .PARAMETER Tenant Specify the tenant name or ID this client assertion is meant for. .EXAMPLE New-AadClientAssertion -ClientId ac4dff1b-b7d7-4453-a4e4-613210c686c9 -CertificatePath "C:\path\to\certificate.pfx" -CertificatePassword "SomePassword" -Tenant contoso.onmicrosoft.com .NOTES .RELATED https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/Client-Assertions #> function New-AadClientAssertion { [CmdletBinding(DefaultParameterSetName='Default')] param( [Parameter(mandatory=$true)] $ClientId, [Parameter(mandatory=$true)] [string]$CertificatePath, [Parameter(mandatory=$true)] [string]$CertificatePassword, [Parameter(mandatory=$true)] [string]$Tenant ) if(![System.IO.Path]::IsPathRooted($CertificatePath)) { $LocalPath = Get-Location $CertificatePath = "$LocalPath\$CertificatePath" } Write-Verbose "Looking for $CertificatePath" $certificate = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($CertificatePath, $CertificatePassword) $ThumbprintBase64 = ConvertFrom-AadThumbprintToBase64String $certificate.Thumbprint $nbf = [int][double]::parse((Get-Date -Date $(Get-Date).ToUniversalTime() -UFormat %s)) $exp = [int][double]::parse((Get-Date -Date $((Get-Date).addseconds($ValidforSeconds).ToUniversalTime()) -UFormat %s)) # Grab Unix Epoch Timestamp and add desired expiration. $jti = New-Guid $aud = "https://login.microsoftonline.com/$Tenant/oauth2/token" $sub = $ClientId $iss = $ClientId [hashtable]$header = @{ alg = "RS256"; typ = "JWT"; x5t = $ThumbprintBase64; kid = $ThumbprintBase64 } [hashtable]$payload = @{ aud = $aud; iss = $iss; sub = $sub; jti = $jti; nbf = $nbf; exp = $exp; tid = "aa00d1fa-5269-4e1c-b06d-30868371d2c5"; } $headerjson = $header | ConvertTo-Json -Compress $payloadjson = $payload | ConvertTo-Json -Compress $headerjsonbase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($headerjson)).Split('=')[0].Replace('+', '-').Replace('/', '_') $payloadjsonbase64 = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($payloadjson)).Split('=')[0].Replace('+', '-').Replace('/', '_') $ToBeSigned = [System.Text.Encoding]::UTF8.GetBytes($headerjsonbase64 + "." + $payloadjsonbase64) $rsa = $certificate.PrivateKey if ($null -eq $rsa) { # Requiring the private key to be present; else cannot sign! throw "There's no private key in the supplied certificate - cannot sign" } else { # Overloads tested with RSACryptoServiceProvider, RSACng, RSAOpenSsl try { $Signature = [Convert]::ToBase64String($rsa.SignData([byte[]]$ToBeSigned,[Security.Cryptography.HashAlgorithmName]::SHA256,[Security.Cryptography.RSASignaturePadding]::Pkcs1)) -replace '\+','-' -replace '/','_' -replace '=' } catch { throw "Signing with SHA256 and Pkcs1 padding failed using private key $rsa >> " + $_ } } $token = "$headerjsonbase64.$payloadjsonbase64.$Signature" Write-Host "Generated Client Assertion..." return $token } <# .SYNOPSIS Get a list of consented permissions based using the specified parameters to filter .DESCRIPTION Revokes a consented permission based on the parameters provided to be used as a filter. At minimum, the ClientId is required. .PARAMETER ClientId Filter based on the ClientId. This is the Enterprise App (Client app) in which the consented permissions are applied on. .PARAMETER ResourceId Filter based on the ResourceId. This is the resource in which the client has permissions on. .PARAMETER UserId Filter based on the UserId. User in which that has consented to the app. .PARAMETER ClaimValue Filter based on the scope or role value. .PARAMETER ConsentType Filter based on the Consent Type. Available options... 'Admin','User', 'All' .PARAMETER PermissionType Filter based on the Permission Type. Available options... 'Delegated','Application', 'All' .EXAMPLE Example 1: Remove all consented permissions for a app (Removes All Admin and User Consents) PS C:\> Revoke-AadConsent -ClientId 'Contoso App' .EXAMPLE Example 2: Remove all user consented permissions leaving only the Admin consented permissions PS C:\> Revoke-AadConsent -ClientId 'Contoso App' -ConsentType User .EXAMPLE Example 3: Revoke a specific permission PS C:\> Revoke-AadConsent -ClientId 'Contoso App' -ResourceId 'Microsoft Graph' -ClaimValue Directory.ReadWrite.All .EXAMPLE Example 4: Revoke a specific user PS C:\> Revoke-AadConsent -ClientId 'Contoso App' -UserId 'john@contoso.com' #> function Revoke-AadConsent { [CmdletBinding(DefaultParameterSetName="All")] param ( [Parameter(mandatory=$true, Position=0, ValueFromPipeline = $true)] [string]$ClientId, [string]$ResourceId, [string]$ClaimValue, [Parameter(ParameterSetName = 'UserId')] [string]$UserId, [ValidateSet('Admin','User', 'All')] $ConsentType = 'All', [ValidateSet('Delegated','Application', 'All')] $PermissionType = 'All' ) # Parameter validations if($ClaimValue -and -not $ResourceId) { throw "You must provide a 'ResoureId' when using 'ClaimValue'" } if($ClaimValue -match " " -or $ClaimValue -match ";" -or $ClaimValue -match ",") { throw "Specifing only one 'ClaimValue' is supported" } $TenantDomain = $Global:AadSupport.Session.TenantId # -------------------------------------------------- # Check if signed in user is Global Admin (As only global admins can perform admin consent) $isGlobalAdmin = Invoke-AadCommand -Command { Param( $AccountId ) $SignedInUser = Get-AzureAdUser -ObjectId $AccountId $SignedInUserObjectId = $SignedInUser.ObjectId $GlobalAdminRoleIds = (Get-AzureAdDirectoryRole | where { $_.displayName -eq 'Global Administrator' -or $_.displayName -eq 'Application Administrator' }).ObjectId foreach($GlobalAdminRoleId in $GlobalAdminRoleIds) { if( (Get-AzureAdDirectoryRoleMember -ObjectId $GlobalAdminRoleId).ObjectId -contains $SignedInUserObjectId ) { return $true } } } -Parameters $Global:AadSupport.Session.AccountId if (-not $isGlobalAdmin) { Write-Host "Your account '$($Global:AadSupport.Session.AccountId)' is not a Global Admin in $TenantDomain." throw "Exception: 'Global Administrator' or 'Application Administrator' role REQUIRED" } $ConsentedPermissions = Get-AadConsent ` -ClientId $ClientId ` -ResourceId $ResourceId ` -ClaimValue $ClaimValue ` -ConsentType $ConsentType ` -PermissionType $PermissionType ` -UserId $UserId $CountRemovedPermissions = 0 # Get output ready, lets create a new line Write-Host "" foreach($Permission in $ConsentedPermissions) { $MsGraphUrl = "$($Global:AadSupport.Resources.MsGraph)/beta/oauth2PermissionGrants/$($Permission.Id)" if($Permission.PermissionType -eq "Delegated") { $RemoveConsent = $true if($ClaimValue -and $Permission.ClaimValue -ne $ClaimValue) { $RemoveConsent = $false } # Remove the OAuth2PermissionGrant Object if($RemoveConsent) { $User = $Permission.PrincipalId if(!$User) { $User = "AllPrincipals" } Write-Host "Removing $($Permission.ResourceName) | $($Permission.ConsentType) $($Permission.PermissionType) permission(s): $($Permission.ClaimValue) | User: $User" $CountRemovedPermissions++ Invoke-AadProtectedApi ` -Client $Global:AadSupport.Clients.AzureAdPowershell.ClientId ` -Resource $Global:AadSupport.Resources.MsGraph ` -Endpoint $MsGraphUrl -Method DELETE ` } # Update the OAuth2PermissionGrant Object to remove ClaimValue else { $CountRemovedPermissions++ $ClaimValues = $Permission.ClaimValue.Split(" ") $ClaimValues = $ClaimValues | where-object {$_ -ne $ClaimValue} $NewClaimValues = $ClaimValues -Join " " $JsonBody = @{ scope = $NewClaimValues } | ConvertTo-Json -Compress Write-Host "Removing $($Permission.ResourceName) | $($Permission.ConsentType) $($Permission.PermissionType) permission: $ClaimValue | $($Permission.PrincipalId)" Invoke-AadProtectedApi ` -Client $Global:AadSupport.Clients.AzureAdPowershell.ClientId ` -Resource $Global:AadSupport.Resources.MsGraph ` -Endpoint $MsGraphUrl -Method PATCH ` -Body $JsonBody } } if($Permission.PermissionType -eq "Application") { $CountRemovedPermissions++ Write-Host "Removing $($Permission.ResourceName) | $($Permission.ConsentType) $($Permission.PermissionType) permission: $($Permission.ClaimValue)" Invoke-AadCommand -Command { Param($Params) Remove-AzureADServiceAppRoleAssignment -ObjectId $Params.ObjectId -AppRoleAssignmentId $Params.AppRoleAssignmentId } -Parameters @{ ObjectId = $Permission.ClientId AppRoleAssignmentId = $Permission.Id } } } Write-Host "" Write-Host "Removed $CountRemovedPermissions permission(s)" } function Test-RevokeAadConsent { Remove-Module AadSupportPreview Import-Module AadSupportPreview Connect-AadSupport Add-AadConsent -ClientId 'AadSupport UnitTest' -ResourceId 'Microsoft Graph' -ClaimValue 'User.read' -UserId testuser@williamfiddes.onmicrosoft.com Add-AadConsent -ClientId 'AadSupport UnitTest' -ResourceId 'Microsoft Graph' -ClaimValue 'User.read' -UserId testuser2@williamfiddes.onmicrosoft.com Revoke-AadConsent -ClientId 'AadSupport UnitTest' Revoke-AadConsent -ClientId 'AadSupport UnitTest' -ConsentType User Revoke-AadConsent -ClientId 'AadSupport UnitTest' -UserId testuser@williamfiddes.onmicrosoft.com Revoke-AadConsent -ClientId 'AadSupport UnitTest' -ConsentType Admin Revoke-AadConsent -ClientId 'AadSupport UnitTest' -PermissionType Delegated Revoke-AadConsent -ClientId 'AadSupport UnitTest' -PermissionType Application Revoke-AadConsent -ClientId 'AadSupport UnitTest' -ResourceId 'Microsoft Graph' Revoke-AadConsent -ClientId 'AadSupport UnitTest' -ResourceId 'Microsoft Graph' -UserConsentOnly Revoke-AadConsent -ClientId 'AadSupport UnitTest' -ResourceId 'Microsoft Graph' -UserId testuser@williamfiddes.onmicrosoft.com Revoke-AadConsent -ClientId 'AadSupport UnitTest' -ResourceId 'Microsoft Graph' -AdminConsentOnly Revoke-AadConsent -ClientId 'AadSupport UnitTest' -ResourceId 'Microsoft Graph' -DelegatedOnly Revoke-AadConsent -ClientId 'AadSupport UnitTest' -ResourceId 'Microsoft Graph' -ApplicationOnly } <# .SYNOPSIS # Resolve Admin Consent Issues .DESCRIPTION # Resolve Admin Consent Issues when the application registration is in a external directory and it not configured correctly. .PARAMETER Id Identifier for the Enterprise App (ServicePrincipal) we will be consenting to. .PARAMETER ResourceId Identifier for the Resource (ServicePrincipal) we will be consenting permissions to. .PARAMETER UseMsGraph Set permission scopes for https://graph.microsoft.com .PARAMETER UseAadGraph Set permission scopes for https://graph.windows.net .PARAMETER UserId If you set a UserId, then it will use User Consent .PARAMETER Scopes scope permissions .PARAMETER Expires Set the date when these consent scope permissions (OAuth2PermissionGrants) expire. .EXAMPLE Set-AadConsent -Id 'Your App Name' -Scopes 'User.Read Directory.Read.All' -UseMsGraph Applies Admin Consent for the Microsoft Graph permissions User.Read & Directory.Read.All .EXAMPLE Set-AadConsent -Id 'Your App Name' -Scopes 'User.Read Directory.Read.All' -UseMsGraph -UserId john@contoso.com Applies User Consent on user john@contoso.com for the Microsoft Graph permissions User.Read & Directory.Read.All .EXAMPLE Set-AadConsent -Id 'Your App Name' -Scopes 'user_impersonation' -ResourceId 'Custom Api' You can also consent for custom API .NOTES General notes #> function Set-AadConsent { [CmdletBinding(DefaultParameterSetName="All")] param ( [Parameter(mandatory=$true, Position=0, ValueFromPipeline = $true)] [string]$ClientId, [Parameter(mandatory=$true, ParameterSetName = 'UseOtherResource')] [string]$ResourceId, [Parameter(mandatory=$true, ParameterSetName = 'UseMsGraph')] [switch]$UseMsGraph, [Parameter(mandatory=$true, ParameterSetName = 'UseAadGraph')] [switch]$UseAadGraph, [Parameter(mandatory=$false)] [string]$Scopes, [Parameter(mandatory=$false)] [string]$Roles, [string]$UserId, $Expires = (Get-AadDateTime -AddMonths 12) ) # This CmdLet shows output Write-Host "" Write-Host "WARNING - Do not use this as a permanent solution. This is a workaround. " -ForegroundColor Yellow Write-Host "WARNING - Please ensure the Application registration is correctly configured." -ForegroundColor Yellow Write-Host "WARNING - Do not use this within your own Scipts to programmatically consent" -ForegroundColor Yellow Read-Host -Prompt "Press any key to continue or CTRL+C to quit" Write-Host "" $TenantDomain = $Global:AadSupport.Session.TenantId # -------------------------------------------------- # Check if signed in user is Global Admin (As only global admins can perform admin consent) $SignedInUser = Invoke-AadCommand -Command { Param($ObjectId) Get-AzureAdUser -ObjectId $ObjectId } -Parameters $Global:AadSupport.Session.AccountId $SignedInUserObjectId = $SignedInUser.ObjectId $AllDirectoryRoles = Invoke-AadCommand -Command { Get-AzureAdDirectoryRole } $GlobalAdminRoleId = ($AllDirectoryRoles | where { $_.displayName -eq 'Global Administrator' }).ObjectId $CompanyAdmins = Invoke-AadCommand -Command { Param($GlobalAdminRoleId) Get-AzureAdDirectoryRoleMember -ObjectId $GlobalAdminRoleId } -Parameters $GlobalAdminRoleId $isGlobalAdmin = ($CompanyAdmins).ObjectId -contains $SignedInUserObjectId if (-not $isGlobalAdmin) { Write-Host "Your account '$authUserId' is not a Global Admin in $TenantDomain." throw "Exception: GLOBAL ADMIN REQUIRED" } # Set ConsentType $ConsentType = "Admin" if($UserId) { $ConsentType = "User" } # Require ResourceId if (-not $ResourceId -and -not $UseMsGraph -and -not $UseAadGraph) { $ResourceId = Read-Host -Prompt "ResourceId" } # -------------------------------------------------- # GET SERVICE PRINCIPAL Object for app we want to udpate $ClientObjectId = $null $sp = Get-AadServicePrincipal -Id $ClientId if(-not $sp) { throw "'$ClientId' not found in '$TenantDomain'" } Write-Host "Enterprise App (ServicePrincipal) we will be updating" -ForegroundColor Yellow $sp | Select-Object DisplayName, AppId, ObjectId | Format-Table if($sp.count -gt 1) { throw "'$ClientId' query returned more than one result. Please provide a unique Service Principal Identifier" } $ClientObjectId = $sp.ObjectId # ------------------------------------------------ # GET PRINCIPAL ID # Get User to be used as PrincipalId (If ConsentType = User) $Oauth2PrincipalId = $null if ($ConsentType -eq "User") { $User = Invoke-AadCommand -Command { Param($UserId) Get-AzureADUser -ObjectId $UserId } -Parameters $UserId $Oauth2PrincipalId = ($User).ObjectId if (-not $Oauth2PrincipalId) { throw "'$UserId' does not exist in '$TenantDomain'" } } # ------------------------------------------------ # GET RESOURCE ID $ResourceObjectId = $null if ($UseMsGraph) { $resource = Invoke-AadCommand -Command { Get-AadServicePrincipal -Id '00000003-0000-0000-c000-000000000000' } $ResourceObjectId = $resource.ObjectId } elseif ($UseAadGraph) { $resource = Invoke-AadCommand -Command { Get-AadServicePrincipal -Id '00000002-0000-0000-c000-000000000000' } $ResourceObjectId = $resource.ObjectId } elseif ($ResourceId) { $resource = (Get-AadServicePrincipal -Id $ResourceId) $ResourceObjectId = $resource.ObjectId } if (-not $resource) { throw "'$ResourceId' not found in '$TenantDomain'" } if($resource.count -gt 1) { throw "The Resource query returned more than one result. Please provide a unique Service Principal Identifier" } Write-Host "Resource we will be adding permissions from..." -ForegroundColor Yellow $resource | Select-Object DisplayName, AppId, ObjectId | Format-Table # ------------------------------------------------ # GET SCOPES # Get current OAuth2PermissionGrant for Service Principal & Resource $OAuth2PermissionGrant = $null $SpOAuth2PermissionGrants = Invoke-AadCommand -Command { Param($ClientObjectId) Get-AzureADServicePrincipalOAuth2PermissionGrant -All $true -ObjectId $ClientObjectId } -Parameters $ClientObjectId # User Consent if ($Oauth2PrincipalId) { $OAuth2PermissionGrant = $SpOAuth2PermissionGrants | Where-Object {$_.ResourceId -eq $ResourceObjectId -and $_.PrincipalId -eq $Oauth2PrincipalId} # Admin Consent } else { $OAuth2PermissionGrant = $SpOAuth2PermissionGrants | Where-Object {$_.ResourceId -eq $ResourceObjectId -and $_.ConsentType -eq 'AllPrincipals'} } # Out-put current permission grant if ($OAuth2PermissionGrant) { Write-Host "Showing current Permission grant" -ForegroundColor Yellow $OAuth2PermissionGrant | Format-List -property * } else { Write-Host "No OAuth2PermissionGrants for $ClientId" } # ------------------------------------------------ # ROLES SECTION if($Roles) { # ------------------------------------------------ # GET ROLES $CurrentAppRoles = GetAadSpAppRoles -ClientObjectId $ClientObjectId -ResourceObjectId $ResourceObjectId $CurrentAppRoles | Format-List # ------------------------------------------------ # REMOVE ROLES foreach($role in $CurrentAppRoles) { Invoke-AadCommand -Command { Param($Params) Remove-AzureADServiceAppRoleAssignment -ObjectId $Params.ObjectId -AppRoleAssignmentId $Params.AppRoleAssignmentId } -Parameters @{ ObjectId = $ClientObjectId AppRoleAssignmentId = $role.RoleAssignedId } } # ------------------------------------------------ # ADD ROLES $SortedAppRoles = $resource.AppRoles | Sort-Object Value $AddRoles = $Roles.Split(" ") foreach($role in ($AddRoles)) { $RoleId = ($SortedAppRoles | where {$_.Value -eq $role}).Id if($RoleId) { Invoke-AadCommand -Command { Param($Params) New-AzureADServiceAppRoleAssignment -ObjectId $Params.ClientObjectId -Id $Params.RoleId -ResourceId $Params.ResourceObjectId -PrincipalId $Params.ClientObjectId } -Parameters @{ ClientObjectId = $ClientObjectId RoleId = $RoleId ResourceObjectId = $ResourceObjectId } } else { Write-Host "'$RoleId' not found on $($resource.DisplayName)" -ForegroundColor Yellow } } # ------------------------------------------------ # VERIFY ROLES GetAadSpAppRoles -ClientObjectId $ClientObjectId -ResourceObjectId $ResourceObjectId | Format-List } # ------------------------------------------------ # BUILD NEW PERMISSION GRANT #Build the permission grant if($Scopes -ne $null) { $newPermissionGrant = @{} $newPermissionGrant.Add("startTime", [System.DateTime]::UtcNow.AddMinutes(-5).ToString("o")) $newPermissionGrant.Add("expiryTime", $Expires) $newPermissionGrant.Add("scope", [string]::Join(' ', $scopes)) if(!$OAuth2PermissionGrant) { $newPermissionGrant.Add("clientId", $ClientObjectId) $newPermissionGrant.Add("resourceId", $ResourceObjectId) if ($Oauth2PrincipalId) { $newPermissionGrant.Add("principalId", $Oauth2PrincipalId) $newPermissionGrant.Add("consentType", "Principal") } else { $newPermissionGrant.Add("consentType", "AllPrincipals") } } $newPermissionGrant = $newPermissionGrant | ConvertTo-Json # ------------------------------------------------ # GET ACCESS TOKEN FOR AAD GRAPH $AccessToken = GetTokenForAadGraph # ------------------------------------------------ #Create the admin permission grant via graph api if ($OAuth2PermissionGrant) { $uri = "https://graph.windows.net/$TenantDomain/oauth2PermissionGrants/$($OAuth2PermissionGrant.ObjectId)/?api-version=1.6" Invoke-WebRequest -Uri $uri -Headers @{ "Authorization" = "Bearer " + $AccessToken } -Method Patch -Body $newPermissionGrant -ContentType "application/json" -Verbose | Format-List -Force } else { $uri = "https://graph.windows.net/$TenantDomain/oauth2PermissionGrants?api-version=1.6" Invoke-WebRequest -Uri $uri -Headers @{ "Authorization" = "Bearer " + $AccessToken } -Method Post -Body $newPermissionGrant -ContentType "application/json" -Verbose | Format-List -Force } $AccessToken = $null } # ------------------------------------------------ # VERIFY UPDATE Write-Host "Now showing current permission grant" -ForegroundColor Yellow $newOAuth2PermissionGrantResponse = Invoke-AadCommand -Command { Param($ClientObjectId) Get-AzureADServicePrincipalOAuth2PermissionGrant -All $true -ObjectId $ClientObjectId } -Parameters $ClientObjectId $newOAuth2PermissionGrant = $newOAuth2PermissionGrantResponse | where {$_.ResourceId -eq $ResourceObjectId -and $_.PrincipalId -eq $Oauth2PrincipalId} $newOAuth2PermissionGrant | Format-List if ($OAuth2PermissionGrant.scope -ne $newOAuth2PermissionGrant.scope) { Write-Host "SUCCESS!" -ForegroundColor Yellow } else { Write-Host "No changes occurred!" -ForegroundColor RED } } function Set-AadOauth2PermissionsGrant { [CmdletBinding(DefaultParameterSetName='Default')] Param( [Parameter(Mandatory=$true)] [string]$Id, [Parameter(Mandatory=$true)] [string]$Scope, [ValidateSet('SET','ADD','REMOVE')] $Method = "SET" ) $Scopes = $Scope.Split(" ").Split(";").Split(",") $GraphApiUrl = "$($Global:AadSupport.Resources.MsGraph)/beta/oauth2PermissionGrants/$Id" # GET ACCESS TOKEN FOR AAD GRAPH $AccessToken = GetTokenForMsGraph if(!AccessToken) { throw "Unable to acquire token." } $Grant = @{} if($Method -eq "SET") { $Grant.scope = $Scope } else{ # ------------------------------------------------ # GET OAUTH2PERMISSIONGRANT $Grant = Invoke-AadProtectedApi ` -Client $Global:AadSupport.Clients.AzureAdPowershell.ClientId ` -Resource $MsGraphEndpoint ` -Endpoint $GraphApiUrl -Method "GET" if(!Grant) { throw "OAuth2PermissionGrant not found!" } if($Method -eq "ADD") { foreach($item in $Scopes) { if($item.Replace(" ","")) { if(!$item -match $Grant.scope) { $Grant.scope += $item } } } } if($Method -eq "REMOVE") { } } $Body = @{ scope = $Grant.scope } | ConvertTo-Json -Compress # ------------------------------------------------ #Create the admin permission grant via graph api Invoke-WebRequest -Uri $MsGraphEndpoint -Headers @{ "Authorization" = "Bearer " + $AccessToken } -Method Patch -Body $Body -ContentType "application/json" } <# .SYNOPSIS Updates the AadSupport PowerShell Module .DESCRIPTION Updates the AadSupport PowerShell Module .PARAMETER All Will also update AzureAd and Az PowerShell Modules .EXAMPLE Update-AadSupport .NOTES General notes #> function Update-AadSupport { param([switch]$All) if($All) { #Azure AD $AadRemoteModule = Find-Module -Name AzureAd $AadLocalModule = Get-Module -Name AzureAd -ListAvailable if($AadLocalModule) { if($AadRemoteModule.Version.ToString() -ne $AadLocalModule.Version.ToString()) { Uninstall-Module -Name AzureAd -AllVersions -Verbose Install-Module -Name AzureAd -AllowClobber -Verbose } } #Azure AD Preview $AadPreviewRemoteModule = Find-Module -Name AzureAdPreview $AadPreviewLocalModule = Get-Module -Name AzureAdPreview -ListAvailable if($AadPreviewLocalModule) { if($AadPreviewRemoteModule.Version.ToString() -ne $AadPreviewLocalModule.Version.ToString()) { Uninstall-Module -Name AzureAdPreview -AllVersions -Verbose Install-Module -Name AzureAdPreview -AllowClobber -Verbose } } #Az $AzRemoteModule = Find-Module -Name Az $AzLocalModule = Get-Module -Name Az -ListAvailable if($AzLocalModule) { if($AzRemoteModule.Version.ToString() -ne $AzLocalModule.Version.ToString()) { Uninstall-Module -Name Az -AllVersions -Verbose Install-Module -Name Az -AllowClobber -Verbose } } } $modules = Get-Module -Name AadSupport -ListAvailable #Ensure all AadSupport Modules are unloaded if($modules) { Remove-Module -Name AadSupport -Verbose #Remove all versions of AadSupport Uninstall-Module -Name AadSupport -AllVersions -Verbose } #Remove all versions of AadSupport Install-Module -Name AadSupport -AllowClobber Write-Host "Update Complete." -ForegroundColor Yellow } <# .SYNOPSIS Quickly validate tokens issued by Azure AD. .DESCRIPTION Quickly validate tokens issued by Azure AD. WARNING: Access token for Microsoft Graph can not be validated. This is expected and only Microsoft Graph will be able to validate its own token. When running this command while Connected to AadSupport (Connect-AadSupport) we will also lookup the Resource AppId and add the signing keys to the list of keys to be verified. We do this because sometimes the resource might use a different signing key that is not the standard Azure AD set of keys. The results will look something like this... Validate-AadToken -JwtToken $token.AccessToken Kid in token: xGbI8ASfxBGw3u14fNXYJhG-wlU Getting Keys based on Issuer 'https://sts.windows.net/aa00d1fa-5269-4e1c-b06d-30868371d2c5/'... Downloading configuration from 'https://sts.windows.net/aa00d1fa-5269-4e1c-b06d-30868371d2c5/.well-known/openid-configuration' Downloading signing keys from 'https://login.windows.net/common/discovery/keys' Keys Found... HlC0R12skxNZ1WQwmjOF_6t_tDE YMELHT0gvb0mxoSDoYfomjqfjYU M6pX7RHoraLsprfJeRCjSxuURhc Getting Keys for the Resource bcdeb54f-733b-4657-8948-0f39934c2a53... Downloading configuration from 'https://sts.windows.net/aa00d1fa-5269-4e1c-b06d-30868371d2c5/.well-known/openid-configuration?appid=bcdeb54f-733b-4657-8948-0f39934c2a53' Downloading signing keys from 'https://login.windows.net/aa00d1fa-5269-4e1c-b06d-30868371d2c5/discovery/keys?appid=bcdeb54f-733b-4657-8948-0f39934c2a53' Keys Found... xGbI8ASfxBGw3u14fNXYJhG-wlU Checking Discovery Key: HlC0R12skxNZ1WQwmjOF_6t_tDE | Signature validation failed Checking Discovery Key: YMELHT0gvb0mxoSDoYfomjqfjYU | Signature validation failed Checking Discovery Key: M6pX7RHoraLsprfJeRCjSxuURhc | Signature validation failed Checking Discovery Key: xGbI8ASfxBGw3u14fNXYJhG-wlU | Signature is verified .PARAMETER JwtToken Provide the token to be validated. .PARAMETER Issuer Issuer you want to use. We will use this to get the Open ID Connect Configuration based on the Issuer. .EXAMPLE Validate-AadToken -JwtToken $AccessToken Validate-AadToken -JwtToken $AccessToken -Issuer "https://login.microsoftonline.com/contoso.onmicrosoft.com" Validate-AadToken -JwtToken $AccessToken -Issuer "https://williamfiddesb2c.b2clogin.com/tfp/williamfiddesb2c.onmicrosoft.com/B2C_1_V2_SUSI_DefaultPage/v2.0/.well-known/openid-configuration" .NOTES General notes #> function Test-AadToken { Param( [Parameter(Mandatory=$true, Position=0, ValueFromPipeline = $true)] [string]$JwtToken, [string]$Issuer ) Write-Host "" # Get claims for OAuth2Token $TokenClaims = ConvertFrom-AadJwtToken $JwtToken if($TokenClaims.aud -eq $Global:AadSupport.Resources.MsGraph -or $TokenClaims.aud -eq "00000003-0000-0000-c000-000000000000") { Write-Host "WARNING! Microsoft Graph tokens can't be validated" -ForegroundColor Red } Write-Host "Kid in token: $($TokenClaims.Kid)" -Foreground Yellow # Ensure Issuer is set (We will use this for the Discovery key endpoint) if(!$Issuer) { $Issuer = $TokenClaims.Iss } Write-Host "" Write-Host "Getting Keys based on Issuer '$Issuer'..." -ForegroundColor Yellow $SigningKeys = @() $IssuerSigningKeys = Get-AadDiscoveryKeys -Issuer $Issuer Write-Host "" Write-Host "Keys Found..." -ForegroundColor Yellow $IssuerSigningKeys.Kid if($Global:AadSupport.Session.Active) { try { Write-Host "" Write-Host "Getting Keys for '$($TokenClaims.aud)' based from 'aud' claim..." -ForegroundColor Yellow $resource = Get-AadServicePrincipal -id $TokenClaims.aud $AppSigningKeys = Get-AadDiscoveryKeys -Issuer $Issuer -ApplicationId $resource.AppId Write-Host "" Write-Host "Keys Found..." -ForegroundColor Yellow $AppSigningKeys.Kid } catch { Write-Host "Unable to get keys for '$($TokenClaims.aud)' ServicePrincipal probably does not exist in tenant." } } $SigningKeys += $IssuerSigningKeys $SigningKeys += $AppSigningKeys Write-Host "" $ListOfKeysChecked = @() foreach($SigningKey in $SigningKeys) { if(!$ListOfKeysChecked.Contains($SigningKey.Kid) -and $SigningKey.Kid) { $ListOfKeysChecked += $SigningKey.Kid $tokenParts = $JwtToken.Split('.') $rsa = New-Object System.Security.Cryptography.RSACryptoServiceProvider $rsaParameters = [System.Security.Cryptography.RSAParameters]::new() $modulus = Base64UrlDecode -value $SigningKey.Modulus $exponent = Base64UrlDecode -value $SigningKey.Exponent $rsaParameters.Modulus = [System.Convert]::FromBase64String($modulus) $rsaParameters.Exponent = [System.Convert]::FromBase64String($exponent) $rsa.ImportParameters($rsaParameters) $sha256 = [System.Security.Cryptography.SHA256]::Create() $hash = $sha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($tokenParts[0] + '.' + $tokenParts[1])) $rsaDeformatter = [System.Security.Cryptography.RSAPKCS1SignatureDeformatter]::new($rsa) $rsaDeformatter.SetHashAlgorithm("SHA256") $TokenSignature = Base64UrlDecode -value $tokenParts[2] $TokenSignatureBytes = [System.Convert]::FromBase64String($TokenSignature) if ($rsaDeformatter.VerifySignature($hash, $TokenSignatureBytes)) { Write-Host "Checking Discovery Key: $($SigningKey.Kid) | Signature is verified" -Foreground Green } else { Write-Host "Checking Discovery Key: $($SigningKey.Kid) | Signature validation failed" -Foreground Red } } } } # SIG # Begin signature block # MIIjkgYJKoZIhvcNAQcCoIIjgzCCI38CAQExDzANBglghkgBZQMEAgEFADB5Bgor # BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG # KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCB0bRIzXx7rJNS5 # Bs/atVcpzusY5vLcRpt9JXodsrS/56CCDYEwggX/MIID56ADAgECAhMzAAAB32vw # LpKnSrTQAAAAAAHfMA0GCSqGSIb3DQEBCwUAMH4xCzAJBgNVBAYTAlVTMRMwEQYD # VQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMTH01pY3Jvc29mdCBDb2RlIFNpZ25p # bmcgUENBIDIwMTEwHhcNMjAxMjE1MjEzMTQ1WhcNMjExMjAyMjEzMTQ1WjB0MQsw # CQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9u # ZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMR4wHAYDVQQDExVNaWNy # b3NvZnQgQ29ycG9yYXRpb24wggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIB # AQC2uxlZEACjqfHkuFyoCwfL25ofI9DZWKt4wEj3JBQ48GPt1UsDv834CcoUUPMn # s/6CtPoaQ4Thy/kbOOg/zJAnrJeiMQqRe2Lsdb/NSI2gXXX9lad1/yPUDOXo4GNw # PjXq1JZi+HZV91bUr6ZjzePj1g+bepsqd/HC1XScj0fT3aAxLRykJSzExEBmU9eS # yuOwUuq+CriudQtWGMdJU650v/KmzfM46Y6lo/MCnnpvz3zEL7PMdUdwqj/nYhGG # 3UVILxX7tAdMbz7LN+6WOIpT1A41rwaoOVnv+8Ua94HwhjZmu1S73yeV7RZZNxoh # EegJi9YYssXa7UZUUkCCA+KnAgMBAAGjggF+MIIBejAfBgNVHSUEGDAWBgorBgEE # AYI3TAgBBggrBgEFBQcDAzAdBgNVHQ4EFgQUOPbML8IdkNGtCfMmVPtvI6VZ8+Mw # UAYDVR0RBEkwR6RFMEMxKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVyYXRpb25zIFB1 # ZXJ0byBSaWNvMRYwFAYDVQQFEw0yMzAwMTIrNDYzMDA5MB8GA1UdIwQYMBaAFEhu # ZOVQBdOCqhc3NyK1bajKdQKVMFQGA1UdHwRNMEswSaBHoEWGQ2h0dHA6Ly93d3cu # bWljcm9zb2Z0LmNvbS9wa2lvcHMvY3JsL01pY0NvZFNpZ1BDQTIwMTFfMjAxMS0w # Ny0wOC5jcmwwYQYIKwYBBQUHAQEEVTBTMFEGCCsGAQUFBzAChkVodHRwOi8vd3d3 # Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2NlcnRzL01pY0NvZFNpZ1BDQTIwMTFfMjAx # MS0wNy0wOC5jcnQwDAYDVR0TAQH/BAIwADANBgkqhkiG9w0BAQsFAAOCAgEAnnqH # tDyYUFaVAkvAK0eqq6nhoL95SZQu3RnpZ7tdQ89QR3++7A+4hrr7V4xxmkB5BObS # 0YK+MALE02atjwWgPdpYQ68WdLGroJZHkbZdgERG+7tETFl3aKF4KpoSaGOskZXp # TPnCaMo2PXoAMVMGpsQEQswimZq3IQ3nRQfBlJ0PoMMcN/+Pks8ZTL1BoPYsJpok # t6cql59q6CypZYIwgyJ892HpttybHKg1ZtQLUlSXccRMlugPgEcNZJagPEgPYni4 # b11snjRAgf0dyQ0zI9aLXqTxWUU5pCIFiPT0b2wsxzRqCtyGqpkGM8P9GazO8eao # mVItCYBcJSByBx/pS0cSYwBBHAZxJODUqxSXoSGDvmTfqUJXntnWkL4okok1FiCD # Z4jpyXOQunb6egIXvkgQ7jb2uO26Ow0m8RwleDvhOMrnHsupiOPbozKroSa6paFt # VSh89abUSooR8QdZciemmoFhcWkEwFg4spzvYNP4nIs193261WyTaRMZoceGun7G # CT2Rl653uUj+F+g94c63AhzSq4khdL4HlFIP2ePv29smfUnHtGq6yYFDLnT0q/Y+ # Di3jwloF8EWkkHRtSuXlFUbTmwr/lDDgbpZiKhLS7CBTDj32I0L5i532+uHczw82 # oZDmYmYmIUSMbZOgS65h797rj5JJ6OkeEUJoAVwwggd6MIIFYqADAgECAgphDpDS # AAAAAAADMA0GCSqGSIb3DQEBCwUAMIGIMQswCQYDVQQGEwJVUzETMBEGA1UECBMK # V2FzaGluZ3RvbjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0 # IENvcnBvcmF0aW9uMTIwMAYDVQQDEylNaWNyb3NvZnQgUm9vdCBDZXJ0aWZpY2F0 # ZSBBdXRob3JpdHkgMjAxMTAeFw0xMTA3MDgyMDU5MDlaFw0yNjA3MDgyMTA5MDla # MH4xCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdS # ZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKDAmBgNVBAMT # H01pY3Jvc29mdCBDb2RlIFNpZ25pbmcgUENBIDIwMTEwggIiMA0GCSqGSIb3DQEB # AQUAA4ICDwAwggIKAoICAQCr8PpyEBwurdhuqoIQTTS68rZYIZ9CGypr6VpQqrgG # OBoESbp/wwwe3TdrxhLYC/A4wpkGsMg51QEUMULTiQ15ZId+lGAkbK+eSZzpaF7S # 35tTsgosw6/ZqSuuegmv15ZZymAaBelmdugyUiYSL+erCFDPs0S3XdjELgN1q2jz # y23zOlyhFvRGuuA4ZKxuZDV4pqBjDy3TQJP4494HDdVceaVJKecNvqATd76UPe/7 # 4ytaEB9NViiienLgEjq3SV7Y7e1DkYPZe7J7hhvZPrGMXeiJT4Qa8qEvWeSQOy2u # M1jFtz7+MtOzAz2xsq+SOH7SnYAs9U5WkSE1JcM5bmR/U7qcD60ZI4TL9LoDho33 # X/DQUr+MlIe8wCF0JV8YKLbMJyg4JZg5SjbPfLGSrhwjp6lm7GEfauEoSZ1fiOIl # XdMhSz5SxLVXPyQD8NF6Wy/VI+NwXQ9RRnez+ADhvKwCgl/bwBWzvRvUVUvnOaEP # 6SNJvBi4RHxF5MHDcnrgcuck379GmcXvwhxX24ON7E1JMKerjt/sW5+v/N2wZuLB # l4F77dbtS+dJKacTKKanfWeA5opieF+yL4TXV5xcv3coKPHtbcMojyyPQDdPweGF # RInECUzF1KVDL3SV9274eCBYLBNdYJWaPk8zhNqwiBfenk70lrC8RqBsmNLg1oiM # CwIDAQABo4IB7TCCAekwEAYJKwYBBAGCNxUBBAMCAQAwHQYDVR0OBBYEFEhuZOVQ # BdOCqhc3NyK1bajKdQKVMBkGCSsGAQQBgjcUAgQMHgoAUwB1AGIAQwBBMAsGA1Ud # DwQEAwIBhjAPBgNVHRMBAf8EBTADAQH/MB8GA1UdIwQYMBaAFHItOgIxkEO5FAVO # 4eqnxzHRI4k0MFoGA1UdHwRTMFEwT6BNoEuGSWh0dHA6Ly9jcmwubWljcm9zb2Z0 # LmNvbS9wa2kvY3JsL3Byb2R1Y3RzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcmwwXgYIKwYBBQUHAQEEUjBQME4GCCsGAQUFBzAChkJodHRwOi8vd3d3Lm1p # Y3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dDIwMTFfMjAxMV8wM18y # Mi5jcnQwgZ8GA1UdIASBlzCBlDCBkQYJKwYBBAGCNy4DMIGDMD8GCCsGAQUFBwIB # FjNodHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpb3BzL2RvY3MvcHJpbWFyeWNw # cy5odG0wQAYIKwYBBQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AcABvAGwAaQBjAHkA # XwBzAHQAYQB0AGUAbQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAGfyhqWY # 4FR5Gi7T2HRnIpsLlhHhY5KZQpZ90nkMkMFlXy4sPvjDctFtg/6+P+gKyju/R6mj # 82nbY78iNaWXXWWEkH2LRlBV2AySfNIaSxzzPEKLUtCw/WvjPgcuKZvmPRul1LUd # d5Q54ulkyUQ9eHoj8xN9ppB0g430yyYCRirCihC7pKkFDJvtaPpoLpWgKj8qa1hJ # Yx8JaW5amJbkg/TAj/NGK978O9C9Ne9uJa7lryft0N3zDq+ZKJeYTQ49C/IIidYf # wzIY4vDFLc5bnrRJOQrGCsLGra7lstnbFYhRRVg4MnEnGn+x9Cf43iw6IGmYslmJ # aG5vp7d0w0AFBqYBKig+gj8TTWYLwLNN9eGPfxxvFX1Fp3blQCplo8NdUmKGwx1j # NpeG39rz+PIWoZon4c2ll9DuXWNB41sHnIc+BncG0QaxdR8UvmFhtfDcxhsEvt9B # xw4o7t5lL+yX9qFcltgA1qFGvVnzl6UJS0gQmYAf0AApxbGbpT9Fdx41xtKiop96 # eiL6SJUfq/tHI4D1nvi/a7dLl+LrdXga7Oo3mXkYS//WsyNodeav+vyL6wuA6mk7 # r/ww7QRMjt/fdW1jkT3RnVZOT7+AVyKheBEyIXrvQQqxP/uozKRdwaGIm1dxVk5I # RcBCyZt2WwqASGv9eZ/BvW1taslScxMNelDNMYIVZzCCFWMCAQEwgZUwfjELMAkG # A1UEBhMCVVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQx # HjAcBgNVBAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEoMCYGA1UEAxMfTWljcm9z # b2Z0IENvZGUgU2lnbmluZyBQQ0EgMjAxMQITMwAAAd9r8C6Sp0q00AAAAAAB3zAN # BglghkgBZQMEAgEFAKCBrjAZBgkqhkiG9w0BCQMxDAYKKwYBBAGCNwIBBDAcBgor # BgEEAYI3AgELMQ4wDAYKKwYBBAGCNwIBFTAvBgkqhkiG9w0BCQQxIgQgqrHXvLD4 # y1I0ptki8l6TuuNeX/gC+8Husc5s+DwzetIwQgYKKwYBBAGCNwIBDDE0MDKgFIAS # AE0AaQBjAHIAbwBzAG8AZgB0oRqAGGh0dHA6Ly93d3cubWljcm9zb2Z0LmNvbTAN # BgkqhkiG9w0BAQEFAASCAQApEQIRQ/ApXWfMN6YZg5T0fFCjs40hB4kZGjGp4Iul # Oj6SEx9MFmMmtO/vuufrCilwzLHlItJFSrhoAle3DYMK7kZfUjYFMrQNF5jgpUqM # HeX4HpV5WUaYqU0pZXs9AY12BwLoJiYlxGnKwWP/iZP2shFyUAhNiqaK9yCPVn79 # DzOAG/AkAGUSOaVs0jn5C9/SPsfJ+Bl9PsPgUueZSvftsFbg/hGRtNuD2VgKgICV # P1F4+/anlfX53K8UEQLY5J9eTvSu19NjQQ9Ls5xcI1bQEK3O1PVxryEkATUFIj8+ # yJWB1SEJzD5k/RiPTpQEzAshpK7eKRuwEO443IbTIUYOoYIS8TCCEu0GCisGAQQB # gjcDAwExghLdMIIS2QYJKoZIhvcNAQcCoIISyjCCEsYCAQMxDzANBglghkgBZQME # AgEFADCCAVUGCyqGSIb3DQEJEAEEoIIBRASCAUAwggE8AgEBBgorBgEEAYRZCgMB # MDEwDQYJYIZIAWUDBAIBBQAEIBmneKX1xy1MCYUxkdT4cQ4K6FRtJkqEU2WV7I/v # GX25AgZg057/j5MYEzIwMjEwNzA4MTczMTI3LjExOVowBIACAfSggdSkgdEwgc4x # CzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRt # b25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1p # Y3Jvc29mdCBPcGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMg # VFNTIEVTTjowQTU2LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUt # U3RhbXAgU2VydmljZaCCDkQwggT1MIID3aADAgECAhMzAAABW3ywujRnN8GnAAAA # AAFbMA0GCSqGSIb3DQEBCwUAMHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNo # aW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29y # cG9yYXRpb24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEw # MB4XDTIxMDExNDE5MDIxNloXDTIyMDQxMTE5MDIxNlowgc4xCzAJBgNVBAYTAlVT # MRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQK # ExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBPcGVy # YXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjowQTU2 # LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vydmlj # ZTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMgkf6Xs9dqhesumLltn # l6lwjiD1jh+Ipz/6j5q5CQzSnbaVuo4KiCiSpr5WtqqVlD7nT/3WX6V6vcpNQV5c # dtVVwafNpLn3yF+fRNoUWh1Q9u8XGiSX8YzVS8q68JPFiRO4HMzMpLCaSjcfQZId # 6CiukyLQruKnSFwdGhMxE7GCayaQ8ZDyEPHs/C2x4AAYMFsVOssSdR8jb8fzAek3 # SNlZtVKd0Kb8io+3XkQ54MvUXV9cVL1/eDdXVVBBqOhHzoJsy+c2y/s3W+gEX8Qb # 9O/bjBkR6hIaOwEAw7Nu40/TMVfwXJ7g5R/HNXCt7c4IajNN4W+CugeysLnYbqRm # W+kCAwEAAaOCARswggEXMB0GA1UdDgQWBBRl5y01iG23UyBdTH/15TnJmLqrLjAf # BgNVHSMEGDAWgBTVYzpcijGQ80N7fEYbxTNoWoVtVTBWBgNVHR8ETzBNMEugSaBH # hkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20vcGtpL2NybC9wcm9kdWN0cy9NaWNU # aW1TdGFQQ0FfMjAxMC0wNy0wMS5jcmwwWgYIKwYBBQUHAQEETjBMMEoGCCsGAQUF # BzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5jb20vcGtpL2NlcnRzL01pY1RpbVN0 # YVBDQV8yMDEwLTA3LTAxLmNydDAMBgNVHRMBAf8EAjAAMBMGA1UdJQQMMAoGCCsG # AQUFBwMIMA0GCSqGSIb3DQEBCwUAA4IBAQCnM2s7phMamc4QdVolrO1ZXRiDMUVd # gu9/yq8g7kIVl+fklUV2Vlout6+fpOqAGnewMtwenFtagVhVJ8Hau8Nwk+IAhB0B # 04DobNDw7v4KETARf8KN8gTH6B7RjHhreMDWg7icV0Dsoj8MIA8AirWlwf4nr8pK # H0n2rETseBJDWc3dbU0ITJEH1RzFhGkW7IzNPQCO165Tp7NLnXp4maZzoVx8PyiO # NO6fyDZr0yqVuh9OqWH+fPZYQ/YYFyhxy+hHWOuqYpc83Phn1vA0Ae1+Wn4bne6Z # GjPxRI6sxsMIkdBXD0HJLyN7YfSrbOVAYwjYWOHresGZuvoEaEgDRWUrMIIGcTCC # BFmgAwIBAgIKYQmBKgAAAAAAAjANBgkqhkiG9w0BAQsFADCBiDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEyMDAGA1UEAxMpTWljcm9zb2Z0IFJv # b3QgQ2VydGlmaWNhdGUgQXV0aG9yaXR5IDIwMTAwHhcNMTAwNzAxMjEzNjU1WhcN # MjUwNzAxMjE0NjU1WjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3Rv # bjEQMA4GA1UEBxMHUmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0 # aW9uMSYwJAYDVQQDEx1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDCCASIw # DQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKkdDbx3EYo6IOz8E5f1+n9plGt0 # VBDVpQoAgoX77XxoSyxfxcPlYcJ2tz5mK1vwFVMnBDEfQRsalR3OCROOfGEwWbEw # RA/xYIiEVEMM1024OAizQt2TrNZzMFcmgqNFDdDq9UeBzb8kYDJYYEbyWEeGMoQe # dGFnkV+BVLHPk0ySwcSmXdFhE24oxhr5hoC732H8RsEnHSRnEnIaIYqvS2SJUGKx # Xf13Hz3wV3WsvYpCTUBR0Q+cBj5nf/VmwAOWRH7v0Ev9buWayrGo8noqCjHw2k4G # kbaICDXoeByw6ZnNPOcvRLqn9NxkvaQBwSAJk3jN/LzAyURdXhacAQVPIk0CAwEA # AaOCAeYwggHiMBAGCSsGAQQBgjcVAQQDAgEAMB0GA1UdDgQWBBTVYzpcijGQ80N7 # fEYbxTNoWoVtVTAZBgkrBgEEAYI3FAIEDB4KAFMAdQBiAEMAQTALBgNVHQ8EBAMC # AYYwDwYDVR0TAQH/BAUwAwEB/zAfBgNVHSMEGDAWgBTV9lbLj+iiXGJo0T2UkFvX # zpoYxDBWBgNVHR8ETzBNMEugSaBHhkVodHRwOi8vY3JsLm1pY3Jvc29mdC5jb20v # cGtpL2NybC9wcm9kdWN0cy9NaWNSb29DZXJBdXRfMjAxMC0wNi0yMy5jcmwwWgYI # KwYBBQUHAQEETjBMMEoGCCsGAQUFBzAChj5odHRwOi8vd3d3Lm1pY3Jvc29mdC5j # b20vcGtpL2NlcnRzL01pY1Jvb0NlckF1dF8yMDEwLTA2LTIzLmNydDCBoAYDVR0g # AQH/BIGVMIGSMIGPBgkrBgEEAYI3LgMwgYEwPQYIKwYBBQUHAgEWMWh0dHA6Ly93 # d3cubWljcm9zb2Z0LmNvbS9QS0kvZG9jcy9DUFMvZGVmYXVsdC5odG0wQAYIKwYB # BQUHAgIwNB4yIB0ATABlAGcAYQBsAF8AUABvAGwAaQBjAHkAXwBTAHQAYQB0AGUA # bQBlAG4AdAAuIB0wDQYJKoZIhvcNAQELBQADggIBAAfmiFEN4sbgmD+BcQM9naOh # IW+z66bM9TG+zwXiqf76V20ZMLPCxWbJat/15/B4vceoniXj+bzta1RXCCtRgkQS # +7lTjMz0YBKKdsxAQEGb3FwX/1z5Xhc1mCRWS3TvQhDIr79/xn/yN31aPxzymXlK # kVIArzgPF/UveYFl2am1a+THzvbKegBvSzBEJCI8z+0DpZaPWSm8tv0E4XCfMkon # /VWvL/625Y4zu2JfmttXQOnxzplmkIz/amJ/3cVKC5Em4jnsGUpxY517IW3DnKOi # PPp/fZZqkHimbdLhnPkd/DjYlPTGpQqWhqS9nhquBEKDuLWAmyI4ILUl5WTs9/S/ # fmNZJQ96LjlXdqJxqgaKD4kWumGnEcua2A5HmoDF0M2n0O99g/DhO3EJ3110mCII # YdqwUB5vvfHhAN/nMQekkzr3ZUd46PioSKv33nJ+YWtvd6mBy6cJrDm77MbL2IK0 # cs0d9LiFAR6A+xuJKlQ5slvayA1VmXqHczsI5pgt6o3gMy4SKfXAL1QnIffIrE7a # KLixqduWsqdCosnPGUFN4Ib5KpqjEWYw07t0MkvfY3v1mYovG8chr1m1rtxEPJdQ # cdeh0sVV42neV8HR3jDA/czmTfsNv11P6Z0eGTgvvM9YBS7vDaBQNdrvCScc1bN+ # NR4Iuto229Nfj950iEkSoYIC0jCCAjsCAQEwgfyhgdSkgdEwgc4xCzAJBgNVBAYT # AlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9uMRAwDgYDVQQHEwdSZWRtb25kMR4wHAYD # VQQKExVNaWNyb3NvZnQgQ29ycG9yYXRpb24xKTAnBgNVBAsTIE1pY3Jvc29mdCBP # cGVyYXRpb25zIFB1ZXJ0byBSaWNvMSYwJAYDVQQLEx1UaGFsZXMgVFNTIEVTTjow # QTU2LUUzMjktNEQ0RDElMCMGA1UEAxMcTWljcm9zb2Z0IFRpbWUtU3RhbXAgU2Vy # dmljZaIjCgEBMAcGBSsOAwIaAxUACrtBbqYy0r+YGLtUaFVRW/Yh7qaggYMwgYCk # fjB8MQswCQYDVQQGEwJVUzETMBEGA1UECBMKV2FzaGluZ3RvbjEQMA4GA1UEBxMH # UmVkbW9uZDEeMBwGA1UEChMVTWljcm9zb2Z0IENvcnBvcmF0aW9uMSYwJAYDVQQD # Ex1NaWNyb3NvZnQgVGltZS1TdGFtcCBQQ0EgMjAxMDANBgkqhkiG9w0BAQUFAAIF # AOSROrkwIhgPMjAyMTA3MDgxMjUwMDFaGA8yMDIxMDcwOTEyNTAwMVowdzA9Bgor # BgEEAYRZCgQBMS8wLTAKAgUA5JE6uQIBADAKAgEAAgIogAIB/zAHAgEAAgIRazAK # AgUA5JKMOQIBADA2BgorBgEEAYRZCgQCMSgwJjAMBgorBgEEAYRZCgMCoAowCAIB # AAIDB6EgoQowCAIBAAIDAYagMA0GCSqGSIb3DQEBBQUAA4GBABDYdBH3hVqinT+a # eZHNa9PDPfWkyvItkoxeFCav2pyydxsQkLx/HPMzub+3mTYDfpiBVPbmV0/ogeIS # CI27iKXRUGiQVXnxWYaW2T3g+qXro3z+rzFInZzSCcJFzA3mhiHRrNYNPL6TH49i # yKb2EYgP9+1F2/t4W4MnNc2+JcAyMYIDDTCCAwkCAQEwgZMwfDELMAkGA1UEBhMC # VVMxEzARBgNVBAgTCldhc2hpbmd0b24xEDAOBgNVBAcTB1JlZG1vbmQxHjAcBgNV # BAoTFU1pY3Jvc29mdCBDb3Jwb3JhdGlvbjEmMCQGA1UEAxMdTWljcm9zb2Z0IFRp # bWUtU3RhbXAgUENBIDIwMTACEzMAAAFbfLC6NGc3wacAAAAAAVswDQYJYIZIAWUD # BAIBBQCgggFKMBoGCSqGSIb3DQEJAzENBgsqhkiG9w0BCRABBDAvBgkqhkiG9w0B # CQQxIgQg2KQX2dIEd7FMbomeVvHAxVUt+WPl+sjuBorOaNSONd8wgfoGCyqGSIb3 # DQEJEAIvMYHqMIHnMIHkMIG9BCDJIuCpKGMRh4lCGucGPHCNJ7jq9MTbe3mQ2FtS # ZLCFGTCBmDCBgKR+MHwxCzAJBgNVBAYTAlVTMRMwEQYDVQQIEwpXYXNoaW5ndG9u # MRAwDgYDVQQHEwdSZWRtb25kMR4wHAYDVQQKExVNaWNyb3NvZnQgQ29ycG9yYXRp # b24xJjAkBgNVBAMTHU1pY3Jvc29mdCBUaW1lLVN0YW1wIFBDQSAyMDEwAhMzAAAB # W3ywujRnN8GnAAAAAAFbMCIEIHqyPZva7fMxjB+cFIVnb2/HgSH55VLx1UdiJc9R # mTZjMA0GCSqGSIb3DQEBCwUABIIBACXUSMEni+W5dajPEn4U1NOcVMCAcoBHOIsE # EZDKjFlOVZfZc0UHRIogykdP2GMFsDfxOEfHngfOYF9ZtF57owpfT3rrEgyDO2GJ # Ow5mekhepn1GK/PDpSZuGN63mY+HF5qwst0mJIHvoSP6OyYHg10aOMpc4CfCvbHC # iIEAqZIgd+B37P/3qxnw6n1Av9ZGEBobQ4so5fNDiSIOUxtxeSD8fXNeR2f7WClK # l3TAds7/RCvv5Y35Qq+n+z76pOnD90QZ0/mQNMDzRHUgeidtlitpTSbxl6YuiReX # t3lPB+qFg1lBLIMskzQ9gM8aBlq+k9FlVDa2UJI/UFkZUZJnFTs= # SIG # End signature block |