mirror of
https://github.com/Gerenios/AADInternals
synced 2026-06-08 11:09:38 +00:00
v0.9.8
This commit is contained in:
+2
-5
@@ -4,7 +4,7 @@
|
||||
RootModule = 'AADInternals.psm1'
|
||||
|
||||
# Version number of this module.
|
||||
ModuleVersion = '0.9.7'
|
||||
ModuleVersion = '0.9.8'
|
||||
|
||||
# Supported PSEditions
|
||||
# CompatiblePSEditions = @()
|
||||
@@ -19,7 +19,7 @@
|
||||
CompanyName = 'Gerenios Ltd'
|
||||
|
||||
# Copyright statement for this module
|
||||
Copyright = '(c) 2018 - 2024 Nestori Syynimaa (@DrAzureAD). Distributed under MIT license.'
|
||||
Copyright = '(c) 2018 - 2025 Nestori Syynimaa (@DrAzureAD). Distributed under MIT license.'
|
||||
|
||||
# Description of the functionality provided by this module
|
||||
Description = 'The AADInternals PowerShell Module utilises several internal features of Azure Active Directory, Office 365, and related admin tools.
|
||||
@@ -279,9 +279,6 @@ DISCLAIMER: Functionality provided through this module are not supported by Micr
|
||||
"Test-SARAPort"
|
||||
"Resolve-SARAHost"
|
||||
|
||||
# SPO_utils.ps1
|
||||
"Get-SPOAuthenticationHeader"
|
||||
|
||||
# SPO.ps1
|
||||
"Get-SPOSiteUsers"
|
||||
"Get-SPOSiteGroups"
|
||||
|
||||
+625
-147
File diff suppressed because it is too large
Load Diff
+140
-322
@@ -643,13 +643,7 @@ function Is-AccessTokenValid
|
||||
$dataToVerify="{0}.{1}" -f $header,$payload
|
||||
$dataBin = [text.encoding]::UTF8.GetBytes($dataToVerify)
|
||||
|
||||
# Remove the Base64 URL encoding from the signature and add padding
|
||||
$signature=$signature.Replace("-","+").Replace("_","/")
|
||||
while ($signature.Length % 4)
|
||||
{
|
||||
$signature += "="
|
||||
}
|
||||
$signBytes = [convert]::FromBase64String($signature)
|
||||
$signBytes = Convert-B64ToByteArray -B64 $signature
|
||||
|
||||
# Extract the modulus and exponent from the certificate
|
||||
for($a=0;$a -lt $certBin.Length ; $a++)
|
||||
@@ -739,7 +733,9 @@ function Get-OAuthInfoUsingSAML
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Resource,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$ClientId="1b730954-1685-4b74-9bfd-dac224a7b894"
|
||||
[String]$ClientId="1b730954-1685-4b74-9bfd-dac224a7b894",
|
||||
[Parameter(Mandatory=$False)]
|
||||
[bool]$CAE
|
||||
)
|
||||
Begin
|
||||
{
|
||||
@@ -764,6 +760,12 @@ function Get-OAuthInfoUsingSAML
|
||||
"scope"="openid"
|
||||
}
|
||||
|
||||
# Set Continuous Access Evaluation (CAE) token claims
|
||||
if($CAE)
|
||||
{
|
||||
$body["claims"] = Get-CAEClaims
|
||||
}
|
||||
|
||||
# Debug
|
||||
Write-Debug "FED AUTHENTICATION BODY: $($body | Out-String)"
|
||||
|
||||
@@ -782,317 +784,6 @@ function Get-OAuthInfoUsingSAML
|
||||
}
|
||||
}
|
||||
|
||||
# Return OAuth information for the given user
|
||||
function Get-OAuthInfo
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[System.Management.Automation.PSCredential]$Credentials,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Resource,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$ClientId="1b730954-1685-4b74-9bfd-dac224a7b894",
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$Tenant="common"
|
||||
)
|
||||
Begin
|
||||
{
|
||||
# Create the headers. We like to be seen as Outlook.
|
||||
$headers = @{
|
||||
"User-Agent" = "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 10.0; WOW64; Trident/7.0; .NET4.0C; .NET4.0E; Tablet PC 2.0; Microsoft Outlook 16.0.4266)"
|
||||
}
|
||||
|
||||
if([string]::IsNullOrEmpty($Tenant))
|
||||
{
|
||||
$Tenant="common"
|
||||
}
|
||||
}
|
||||
Process
|
||||
{
|
||||
# Get the user realm
|
||||
$userRealm = Get-UserRealm($Credentials.UserName)
|
||||
|
||||
# Check the authentication type
|
||||
if($userRealm.account_type -eq "Unknown")
|
||||
{
|
||||
Write-Error "User type of $($Credentials.Username) is Unknown!"
|
||||
return $null
|
||||
}
|
||||
elseif($userRealm.account_type -eq "Managed")
|
||||
{
|
||||
# If authentication type is managed, we authenticate directly against Microsoft Online
|
||||
# with user name and password to get access token
|
||||
|
||||
# Create a body for REST API request
|
||||
$body = @{
|
||||
"resource"=$Resource
|
||||
"client_id"=$ClientId
|
||||
"grant_type"="password"
|
||||
"username"=$Credentials.UserName
|
||||
"password"=$Credentials.GetNetworkCredential().Password
|
||||
"scope"="openid"
|
||||
}
|
||||
|
||||
# Debug
|
||||
Write-Debug "AUTHENTICATION BODY: $($body | Out-String)"
|
||||
|
||||
# Set the content type and call the Microsoft Online authentication API
|
||||
$contentType="application/x-www-form-urlencoded"
|
||||
try
|
||||
{
|
||||
$jsonResponse=Invoke-RestMethod -UseBasicParsing -Uri "https://login.microsoftonline.com/$Tenant/oauth2/token" -ContentType $contentType -Method POST -Body $body -Headers $headers
|
||||
}
|
||||
catch
|
||||
{
|
||||
Throw ($_.ErrorDetails.Message | convertfrom-json).error_description
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
# If authentication type is Federated, we must first authenticate against the identity provider
|
||||
# to fetch SAML token and then get access token from Microsoft Online
|
||||
|
||||
# Get the federation metadata url from user realm
|
||||
$federation_metadata_url=$userRealm.federation_metadata_url
|
||||
|
||||
# Call the API to get metadata
|
||||
[xml]$response=Invoke-RestMethod -UseBasicParsing -Uri $federation_metadata_url
|
||||
|
||||
# Get the url of identity provider endpoint.
|
||||
# Note! Tested only with AD FS - others may or may not work
|
||||
$federation_url=($response.definitions.service.port | where name -eq "UserNameWSTrustBinding_IWSTrustFeb2005Async").address.location
|
||||
|
||||
# login.live.com
|
||||
# TODO: Fix
|
||||
#$federation_url=$response.EntityDescriptor.RoleDescriptor[1].PassiveRequestorEndpoint.EndpointReference.Address
|
||||
|
||||
# Set credentials and other needed variables
|
||||
$username=$Credentials.UserName
|
||||
$password=$Credentials.GetNetworkCredential().Password
|
||||
$created=(Get-Date).ToUniversalTime().toString("yyyy-MM-ddTHH:mm:ssZ").Replace(".",":")
|
||||
$expires=(Get-Date).AddMinutes(10).ToUniversalTime().toString("yyyy-MM-ddTHH:mm:ssZ").Replace(".",":")
|
||||
$message_id=(New-Guid).ToString()
|
||||
$user_id=(New-Guid).ToString()
|
||||
|
||||
# Set headers
|
||||
$headers = @{
|
||||
"SOAPAction"="http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue"
|
||||
"Host"=$federation_url.Split("/")[2]
|
||||
"client-request-id"=(New-Guid).toString()
|
||||
}
|
||||
|
||||
# Debug
|
||||
Write-Debug "FED AUTHENTICATION HEADERS: $($headers | Out-String)"
|
||||
|
||||
# Create the SOAP envelope
|
||||
$envelope=@"
|
||||
<s:Envelope xmlns:s='http://www.w3.org/2003/05/soap-envelope' xmlns:a='http://www.w3.org/2005/08/addressing' xmlns:u='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd'>
|
||||
<s:Header>
|
||||
<a:Action s:mustUnderstand='1'>http://schemas.xmlsoap.org/ws/2005/02/trust/RST/Issue</a:Action>
|
||||
<a:MessageID>urn:uuid:$message_id</a:MessageID>
|
||||
<a:ReplyTo>
|
||||
<a:Address>http://www.w3.org/2005/08/addressing/anonymous</a:Address>
|
||||
</a:ReplyTo>
|
||||
<a:To s:mustUnderstand='1'>$federation_url</a:To>
|
||||
<o:Security s:mustUnderstand='1' xmlns:o='http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'>
|
||||
<u:Timestamp u:Id='_0'>
|
||||
<u:Created>$created</u:Created>
|
||||
<u:Expires>$expires</u:Expires>
|
||||
</u:Timestamp>
|
||||
<o:UsernameToken u:Id='uuid-$user_id'>
|
||||
<o:Username>$username</o:Username>
|
||||
<o:Password>$password</o:Password>
|
||||
</o:UsernameToken>
|
||||
</o:Security>
|
||||
</s:Header>
|
||||
<s:Body>
|
||||
<trust:RequestSecurityToken xmlns:trust='http://schemas.xmlsoap.org/ws/2005/02/trust'>
|
||||
<wsp:AppliesTo xmlns:wsp='http://schemas.xmlsoap.org/ws/2004/09/policy'>
|
||||
<a:EndpointReference>
|
||||
<a:Address>urn:federation:MicrosoftOnline</a:Address>
|
||||
</a:EndpointReference>
|
||||
</wsp:AppliesTo>
|
||||
<trust:KeyType>http://schemas.xmlsoap.org/ws/2005/05/identity/NoProofKey</trust:KeyType>
|
||||
<trust:RequestType>http://schemas.xmlsoap.org/ws/2005/02/trust/Issue</trust:RequestType>
|
||||
</trust:RequestSecurityToken>
|
||||
</s:Body>
|
||||
</s:Envelope>
|
||||
"@
|
||||
# Debug
|
||||
Write-Debug "FED AUTHENTICATION: $envelope"
|
||||
|
||||
# Set the content type and call the authentication service
|
||||
$contentType="application/soap+xml"
|
||||
[xml]$xmlResponse=Invoke-RestMethod -UseBasicParsing -Uri $federation_url -ContentType $contentType -Method POST -Body $envelope -Headers $headers
|
||||
|
||||
# Get the SAML token from response and encode it with Base64
|
||||
$samlToken=$xmlResponse.Envelope.Body.RequestSecurityTokenResponse.RequestedSecurityToken.Assertion.OuterXml
|
||||
$encodedSamlToken= [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes($samlToken))
|
||||
|
||||
$jsonResponse = Get-OAuthInfoUsingSAML -SAMLToken $samlToken -Resource $Resource -ClientId $ClientId
|
||||
}
|
||||
|
||||
# Debug
|
||||
Write-Debug "AUTHENTICATION JSON: $($jsonResponse | Out-String)"
|
||||
|
||||
# Return
|
||||
$jsonResponse
|
||||
}
|
||||
}
|
||||
|
||||
# Parse access token and return it as PS object
|
||||
function Read-Accesstoken
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Extract details from the given Access Token
|
||||
|
||||
.DESCRIPTION
|
||||
Extract details from the given Access Token and returns them as PS Object
|
||||
|
||||
.Parameter AccessToken
|
||||
The Access Token.
|
||||
|
||||
.Example
|
||||
PS C:\>$token=Get-AADIntReadAccessTokenForAADGraph
|
||||
PS C:\>Parse-AADIntAccessToken -AccessToken $token
|
||||
|
||||
aud : https://graph.windows.net
|
||||
iss : https://sts.windows.net/f2b2ba53-ed2a-4f4c-a4c3-85c61e548975/
|
||||
iat : 1589477501
|
||||
nbf : 1589477501
|
||||
exp : 1589481401
|
||||
acr : 1
|
||||
aio : ASQA2/8PAAAALe232Yyx9l=
|
||||
amr : {pwd}
|
||||
appid : 1b730954-1685-4b74-9bfd-dac224a7b894
|
||||
appidacr : 0
|
||||
family_name : company
|
||||
given_name : admin
|
||||
ipaddr : 107.210.220.129
|
||||
name : admin company
|
||||
oid : 1713a7bf-47ba-4826-a2a7-bbda9fabe948
|
||||
puid : 100354
|
||||
rh : 0QfALA.
|
||||
scp : user_impersonation
|
||||
sub : BGwHjKPU
|
||||
tenant_region_scope : NA
|
||||
tid : f2b2ba53-ed2a-4f4c-a4c3-85c61e548975
|
||||
unique_name : admin@company.onmicrosoft.com
|
||||
upn : admin@company.onmicrosoft.com
|
||||
uti : -EWK6jMDrEiAesWsiAA
|
||||
ver : 1.0
|
||||
|
||||
.Example
|
||||
PS C:\>Parse-AADIntAccessToken -AccessToken $token -Validate
|
||||
|
||||
Read-Accesstoken : Access Token is expired
|
||||
At line:1 char:1
|
||||
+ Read-Accesstoken -AccessToken $at -Validate -verbose
|
||||
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
|
||||
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Read-Accesstoken
|
||||
|
||||
aud : https://graph.windows.net
|
||||
iss : https://sts.windows.net/f2b2ba53-ed2a-4f4c-a4c3-85c61e548975/
|
||||
iat : 1589477501
|
||||
nbf : 1589477501
|
||||
exp : 1589481401
|
||||
acr : 1
|
||||
aio : ASQA2/8PAAAALe232Yyx9l=
|
||||
amr : {pwd}
|
||||
appid : 1b730954-1685-4b74-9bfd-dac224a7b894
|
||||
appidacr : 0
|
||||
family_name : company
|
||||
given_name : admin
|
||||
ipaddr : 107.210.220.129
|
||||
name : admin company
|
||||
oid : 1713a7bf-47ba-4826-a2a7-bbda9fabe948
|
||||
puid : 100354
|
||||
rh : 0QfALA.
|
||||
scp : user_impersonation
|
||||
sub : BGwHjKPU
|
||||
tenant_region_scope : NA
|
||||
tid : f2b2ba53-ed2a-4f4c-a4c3-85c61e548975
|
||||
unique_name : admin@company.onmicrosoft.com
|
||||
upn : admin@company.onmicrosoft.com
|
||||
uti : -EWK6jMDrEiAesWsiAA
|
||||
ver : 1.0
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True,ValueFromPipeline)]
|
||||
[String]$AccessToken,
|
||||
[Parameter()]
|
||||
[Switch]$ShowDate,
|
||||
[Parameter()]
|
||||
[Switch]$Validate
|
||||
|
||||
)
|
||||
Process
|
||||
{
|
||||
if([string]::IsNullOrEmpty($AccessToken))
|
||||
{
|
||||
Write-Warning "Unable to read access token (null or empty)"
|
||||
return $null
|
||||
}
|
||||
# Token sections
|
||||
$sections = $AccessToken.Split(".")
|
||||
if($sections.Count -eq 5)
|
||||
{
|
||||
Write-Warning "JWE token, expected JWS. Unable to parse."
|
||||
return
|
||||
}
|
||||
$header = $sections[0]
|
||||
$payload = $sections[1]
|
||||
$signature = $sections[2]
|
||||
|
||||
# Convert the token to string and json
|
||||
$payloadString = Convert-B64ToText -B64 $payload
|
||||
$payloadObj=$payloadString | ConvertFrom-Json
|
||||
|
||||
if($ShowDate)
|
||||
{
|
||||
# Show dates
|
||||
$payloadObj.exp=($epoch.Date.AddSeconds($payloadObj.exp)).toString("yyyy-MM-ddTHH:mm:ssZ").Replace(".",":")
|
||||
$payloadObj.iat=($epoch.Date.AddSeconds($payloadObj.iat)).toString("yyyy-MM-ddTHH:mm:ssZ").Replace(".",":")
|
||||
$payloadObj.nbf=($epoch.Date.AddSeconds($payloadObj.nbf)).toString("yyyy-MM-ddTHH:mm:ssZ").Replace(".",":")
|
||||
}
|
||||
|
||||
if($Validate)
|
||||
{
|
||||
# Check the signature
|
||||
if((Is-AccessTokenValid -AccessToken $AccessToken))
|
||||
{
|
||||
Write-Verbose "Access Token signature successfully verified"
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Error "Access Token signature could not be verified"
|
||||
}
|
||||
|
||||
# Check the timestamp
|
||||
if((Is-AccessTokenExpired -AccessToken $AccessToken))
|
||||
{
|
||||
Write-Error "Access Token is expired"
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Verbose "Access Token is not expired"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Debug
|
||||
Write-Debug "PARSED ACCESS TOKEN: $($payloadObj | Out-String)"
|
||||
|
||||
# Return
|
||||
$payloadObj
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Prompts for credentials and gets the access token
|
||||
# Supports MFA.
|
||||
function Prompt-Credentials
|
||||
@@ -1103,7 +794,7 @@ function Prompt-Credentials
|
||||
[String]$Resource,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$ClientId,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Tenant,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[bool]$ForceMFA=$false,
|
||||
@@ -1122,7 +813,9 @@ function Prompt-Credentials
|
||||
[Parameter(Mandatory=$False)]
|
||||
[string]$SubScope,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[string]$ESTSAUTH
|
||||
[string]$ESTSAUTH,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[bool]$CAE
|
||||
)
|
||||
Process
|
||||
{
|
||||
@@ -1154,6 +847,12 @@ function Prompt-Credentials
|
||||
"scope"="openid"
|
||||
}
|
||||
|
||||
# Set Continuous Access Evaluation (CAE) token claims
|
||||
if($CAE)
|
||||
{
|
||||
$body["claims"] = Get-CAEClaims
|
||||
}
|
||||
|
||||
# Debug
|
||||
Write-Debug "AUTHENTICATION BODY: $($body | Out-String)"
|
||||
|
||||
@@ -1165,7 +864,7 @@ function Prompt-Credentials
|
||||
}
|
||||
try
|
||||
{
|
||||
$response=Invoke-RestMethod -UseBasicParsing -Uri "https://login.microsoftonline.com/common/oauth2/token" -Method POST -Body $body -Headers $headers
|
||||
$response=Invoke-RestMethod -UseBasicParsing -Uri "https://login.microsoftonline.com/$tenant/oauth2/token" -Method POST -Body $body -Headers $headers
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -1200,6 +899,13 @@ function Prompt-Credentials
|
||||
redirect_uri = $RedirectURI
|
||||
}
|
||||
|
||||
# Set Continuous Access Evaluation (CAE) token claims
|
||||
if($CAE)
|
||||
{
|
||||
$body["claims"] = Get-CAEClaims
|
||||
}
|
||||
|
||||
|
||||
# Headers
|
||||
$headers = @{
|
||||
"Content-Type" = "application/x-www-form-urlencoded"
|
||||
@@ -3158,4 +2864,116 @@ function Get-TenantLoginUrl
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Jan 13th 2025
|
||||
# Get access token using MSAL
|
||||
function Get-AccessTokenUsingMSAL
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[String]$TenantId="common",
|
||||
[Parameter(Mandatory=$true)]
|
||||
[String]$Resource,
|
||||
[Parameter(Mandatory=$true)]
|
||||
[String]$ClientId,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[String]$RedirectUri,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[String]$SubScope,
|
||||
[Parameter(Mandatory=$false)]
|
||||
[bool]$CAE
|
||||
)
|
||||
Begin
|
||||
{
|
||||
# Load MSAL dll
|
||||
Add-Type -Path "$PSScriptRoot\Microsoft.Identity.Client.dll"
|
||||
}
|
||||
Process
|
||||
{
|
||||
if([string]::IsNullOrEmpty($RedirectUri))
|
||||
{
|
||||
$RedirectUri = Get-AuthRedirectUrl -ClientId $ClientId -Resource $Resource
|
||||
}
|
||||
|
||||
$options = [Microsoft.Identity.Client.PublicClientApplicationOptions]::new()
|
||||
$options.ClientId = $ClientId
|
||||
$options.TenantId = $TenantId
|
||||
$options.RedirectUri = $RedirectUri
|
||||
$options.AzureCloudInstance = Get-MSALAzureScope -SubScope $SubScope
|
||||
|
||||
if($CAE)
|
||||
{
|
||||
$options.ClientCapabilities = [string[]]"cp1"
|
||||
}
|
||||
|
||||
$builder = [Microsoft.Identity.Client.PublicClientApplicationBuilder]::CreateWithApplicationOptions($options)
|
||||
$app = $builder.Build()
|
||||
$tokenParameters = $app.AcquireTokenInteractive([string[]]"$Resource/.default")
|
||||
$result = $tokenParameters.ExecuteAsync()
|
||||
|
||||
return $result.Result
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Jan 13th 2025
|
||||
# Returns MSAL Azure Scope based on subscope
|
||||
function Get-MSALAzureScope
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[String]$SubScope
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Return AzureCloudInstance
|
||||
# Ref: https://learn.microsoft.com/en-us/dotnet/api/microsoft.identity.client.azurecloudinstance?view=msal-dotnet-latest
|
||||
switch($SubScope)
|
||||
{
|
||||
"DOD" # DoD
|
||||
{
|
||||
return 4
|
||||
}
|
||||
"DODCON" # GCC-High
|
||||
{
|
||||
return 4
|
||||
}
|
||||
default # Commercial/GCC
|
||||
{
|
||||
return 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Returns CAE claims
|
||||
# Feb 5th 2024
|
||||
function Get-CAEClaims
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$false)]
|
||||
[String]$SubScope
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Claims
|
||||
$claims = @{
|
||||
"access_token" = @{
|
||||
"xms_cc" = @{
|
||||
"values" = @("cp1")
|
||||
}
|
||||
}
|
||||
"id_token" = @{
|
||||
"xms_cc" = @{
|
||||
"values" = @("cp1")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return ConvertTo-Json -InputObject $claims -Depth 10 -Compress
|
||||
}
|
||||
}
|
||||
+332
-11
@@ -569,9 +569,18 @@ Function Convert-PEMToRSA
|
||||
process
|
||||
{
|
||||
$pemReader = [Org.BouncyCastle.OpenSsl.PemReader]::new([System.IO.StringReader]::new($PEM))
|
||||
$keys = $pemReader.ReadObject()
|
||||
$RSA = $pemReader.ReadObject()
|
||||
|
||||
$RSAParameters = [Org.BouncyCastle.Security.DotNetUtilities]::ToRSAParameters($keys.Private)
|
||||
# Certificate
|
||||
if($RSA.GetType().Name -eq "X509Certificate")
|
||||
{
|
||||
$RSAParameters = [Org.BouncyCastle.Security.DotNetUtilities]::ToRSAParameters($RSA.GetPublicKey())
|
||||
}
|
||||
# Privatekey
|
||||
else
|
||||
{
|
||||
$RSAParameters = [Org.BouncyCastle.Security.DotNetUtilities]::ToRSAParameters($RSA.Private)
|
||||
}
|
||||
|
||||
$pemReader.Reader.Dispose()
|
||||
|
||||
@@ -1162,14 +1171,26 @@ function Get-Digest
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Data
|
||||
[PSObject]$Data
|
||||
)
|
||||
Process
|
||||
{
|
||||
if($Data -is [String])
|
||||
{
|
||||
$binData = [text.encoding]::UTF8.GetBytes([String]$Data)
|
||||
}
|
||||
elseif($Data -is [Byte[]])
|
||||
{
|
||||
$binData = $Data
|
||||
}
|
||||
else
|
||||
{
|
||||
Throw "Data must be either String or ByteArray"
|
||||
}
|
||||
|
||||
# Compute SHA1 digest
|
||||
$SHA1 = [System.Security.Cryptography.SHA1Managed]::Create()
|
||||
$digest = $SHA1.ComputeHash([text.encoding]::UTF8.GetBytes($Data))
|
||||
$digest = $SHA1.ComputeHash($binData)
|
||||
|
||||
$SHA1.Dispose()
|
||||
|
||||
@@ -1693,11 +1714,11 @@ function Get-BinaryContent
|
||||
#return [System.IO.File]::ReadAllBytes([System.IO.Path]::GetFullPath($Path))
|
||||
if($PSVersionTable.PSVersion.Major -ge 6)
|
||||
{
|
||||
Get-Content -Path $Path -AsByteStream -Raw
|
||||
Get-Content -Path $Path -AsByteStream -Raw -ErrorAction $ErrorActionPreference
|
||||
}
|
||||
else
|
||||
{
|
||||
Get-Content -Path $Path -Encoding Byte
|
||||
Get-Content -Path $Path -Encoding Byte -ErrorAction $ErrorActionPreference
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1716,11 +1737,11 @@ function Set-BinaryContent
|
||||
{
|
||||
if($PSVersionTable.PSVersion.Major -ge 6)
|
||||
{
|
||||
Set-Content -Path $Path -Value $Value -AsByteStream
|
||||
Set-Content -Path $Path -Value $Value -AsByteStream -ErrorAction $ErrorActionPreference
|
||||
}
|
||||
else
|
||||
{
|
||||
Set-Content -Path $Path -Value $Value -Encoding Byte
|
||||
Set-Content -Path $Path -Value $Value -Encoding Byte -ErrorAction $ErrorActionPreference
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1869,11 +1890,11 @@ function Set-UserAgent
|
||||
Sets the User-Agent AADInternals will use in requests.
|
||||
|
||||
.DESCRIPTION
|
||||
Sets a pre configured User-Agent for a specific device that AADInternals will use in requests. Supported devices: 'Windows','MacOS','Linux','iOS','Android'.
|
||||
Sets a pre configured User-Agent for a specific device that AADInternals will use in requests. Supported devices: 'Windows','MacOS','Linux','iOS','Android','Default'.
|
||||
To persist, use Save-AADIntConfiguration after setting the User-Agent
|
||||
|
||||
.Parameter UserAgent
|
||||
One of 'Windows','MacOS','Linux','iOS','Android'
|
||||
One of 'Windows','MacOS','Linux','iOS','Android','Default'
|
||||
|
||||
.Example
|
||||
PS C:\>Set-AADIntUserAgent -Device Windows
|
||||
@@ -1887,7 +1908,7 @@ function Set-UserAgent
|
||||
[cmdletbinding()]
|
||||
param(
|
||||
[parameter(Mandatory=$true)]
|
||||
[ValidateSet('Windows','MacOS','Linux','iOS','Android')]
|
||||
[ValidateSet('Windows','MacOS','Linux','iOS','Android','Default')]
|
||||
[string]$Device
|
||||
)
|
||||
Begin
|
||||
@@ -1898,6 +1919,7 @@ function Set-UserAgent
|
||||
"Linux" = "Mozilla/5.0 (X11; Linux x86_64)"
|
||||
"iOS" = "Mozilla/5.0 (iPhone; CPU iPhone OS 16_5 like Mac OS X)"
|
||||
"Android" = "Mozilla/5.0 (Linux; Android 10)"
|
||||
"Default" = "AADInternals"
|
||||
}
|
||||
}
|
||||
Process
|
||||
@@ -2594,4 +2616,303 @@ function Convert-SIDtoObjectID
|
||||
Throw "Invalid SID. SID must start with S-1-12-1"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Create a new self-signed certificate
|
||||
# Jan 31st 2021
|
||||
function New-Certificate
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Creates a new self signed certificate.
|
||||
|
||||
.DESCRIPTION
|
||||
Creates a new self signed certificate for the given subject name and returns it as System.Security.Cryptography.X509Certificates.X509Certificate2 or exports directly to .pfx and .cer files.
|
||||
The certificate is valid for 100 years.
|
||||
|
||||
.Parameter SubjectName
|
||||
The subject name of the certificate, MUST start with CN=
|
||||
|
||||
.Parameter Export
|
||||
Export the certificate (PFX and CER) instead of returning the certificate object. The .pfx file does not have a password.
|
||||
|
||||
.Example
|
||||
PS C:\>$certificate = New-AADIntCertificate -SubjectName "CN=MyCert"
|
||||
|
||||
.Example
|
||||
PS C:\>$certificate = New-AADIntCertificate -SubjectName "CN=MyCert"
|
||||
|
||||
PS C:\>$certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx) | Set-Content MyCert.pfx -Encoding Byte
|
||||
|
||||
.Example
|
||||
PS C:\>$certificate = New-AADIntCertificate -SubjectName "CN=MyCert"
|
||||
|
||||
PS C:\>$certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert) | Set-Content MyCert.cer -Encoding Byte
|
||||
|
||||
.Example
|
||||
PS C:\>New-AADIntCertificate -SubjectName "CN=MyCert" -Export
|
||||
|
||||
Certificate successfully exported:
|
||||
CN=MyCert.pfx
|
||||
CN=MyCert.cer
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
|
||||
param(
|
||||
[parameter(Mandatory=$true,ValueFromPipeline)]
|
||||
[ValidatePattern("[c|C][n|N]=.+")] # Must start with CN=
|
||||
[String]$SubjectName,
|
||||
[Switch]$Export
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Create a private key
|
||||
$rsa = [System.Security.Cryptography.RSA]::Create(2048)
|
||||
|
||||
# Initialize the Certificate Signing Request object
|
||||
$req = [System.Security.Cryptography.X509Certificates.CertificateRequest]::new($SubjectName, $rsa, [System.Security.Cryptography.HashAlgorithmName]::SHA256,[System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
|
||||
$req.CertificateExtensions.Add([System.Security.Cryptography.X509Certificates.X509BasicConstraintsExtension]::new($true,$false,0,$true))
|
||||
$req.CertificateExtensions.Add([System.Security.Cryptography.X509Certificates.X509SubjectKeyIdentifierExtension]::new($req.PublicKey,$false))
|
||||
|
||||
# Create a self-signed certificate
|
||||
$selfSigned = $req.CreateSelfSigned((Get-Date).ToUniversalTime().AddMinutes(-5),(Get-Date).ToUniversalTime().AddYears(100))
|
||||
|
||||
|
||||
# Store the private key to so that it can be exported
|
||||
$cspParameters = [System.Security.Cryptography.CspParameters]::new()
|
||||
$cspParameters.ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"
|
||||
$cspParameters.ProviderType = 24
|
||||
$cspParameters.KeyContainerName ="AADInternals"
|
||||
|
||||
# Set the private key
|
||||
$privateKey = [System.Security.Cryptography.RSACryptoServiceProvider]::new(2048,$cspParameters)
|
||||
$privateKey.ImportParameters($rsa.ExportParameters($true))
|
||||
$selfSigned.PrivateKey = $privateKey
|
||||
|
||||
if($Export)
|
||||
{
|
||||
Set-BinaryContent -Path "$SubjectName.pfx" -Value $selfSigned.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx)
|
||||
Set-BinaryContent -Path "$SubjectName.cer" -Value $selfSigned.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)
|
||||
|
||||
# Print out information
|
||||
Write-Host "Certificate successfully exported:"
|
||||
Write-Host " $SubjectName.pfx"
|
||||
Write-Host " $SubjectName.cer"
|
||||
}
|
||||
else
|
||||
{
|
||||
return $selfSigned
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
# Parse access token and return it as PS object
|
||||
function Read-Accesstoken
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Extract details from the given Access Token
|
||||
|
||||
.DESCRIPTION
|
||||
Extract details from the given Access Token and returns them as PS Object
|
||||
|
||||
.Parameter AccessToken
|
||||
The Access Token.
|
||||
|
||||
.Example
|
||||
PS C:\>$token=Get-AADIntReadAccessTokenForAADGraph
|
||||
PS C:\>Parse-AADIntAccessToken -AccessToken $token
|
||||
|
||||
aud : https://graph.windows.net
|
||||
iss : https://sts.windows.net/f2b2ba53-ed2a-4f4c-a4c3-85c61e548975/
|
||||
iat : 1589477501
|
||||
nbf : 1589477501
|
||||
exp : 1589481401
|
||||
acr : 1
|
||||
aio : ASQA2/8PAAAALe232Yyx9l=
|
||||
amr : {pwd}
|
||||
appid : 1b730954-1685-4b74-9bfd-dac224a7b894
|
||||
appidacr : 0
|
||||
family_name : company
|
||||
given_name : admin
|
||||
ipaddr : 107.210.220.129
|
||||
name : admin company
|
||||
oid : 1713a7bf-47ba-4826-a2a7-bbda9fabe948
|
||||
puid : 100354
|
||||
rh : 0QfALA.
|
||||
scp : user_impersonation
|
||||
sub : BGwHjKPU
|
||||
tenant_region_scope : NA
|
||||
tid : f2b2ba53-ed2a-4f4c-a4c3-85c61e548975
|
||||
unique_name : admin@company.onmicrosoft.com
|
||||
upn : admin@company.onmicrosoft.com
|
||||
uti : -EWK6jMDrEiAesWsiAA
|
||||
ver : 1.0
|
||||
|
||||
.Example
|
||||
PS C:\>Parse-AADIntAccessToken -AccessToken $token -Validate
|
||||
|
||||
Read-Accesstoken : Access Token is expired
|
||||
At line:1 char:1
|
||||
+ Read-Accesstoken -AccessToken $at -Validate -verbose
|
||||
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
+ CategoryInfo : NotSpecified: (:) [Write-Error], WriteErrorException
|
||||
+ FullyQualifiedErrorId : Microsoft.PowerShell.Commands.WriteErrorException,Read-Accesstoken
|
||||
|
||||
aud : https://graph.windows.net
|
||||
iss : https://sts.windows.net/f2b2ba53-ed2a-4f4c-a4c3-85c61e548975/
|
||||
iat : 1589477501
|
||||
nbf : 1589477501
|
||||
exp : 1589481401
|
||||
acr : 1
|
||||
aio : ASQA2/8PAAAALe232Yyx9l=
|
||||
amr : {pwd}
|
||||
appid : 1b730954-1685-4b74-9bfd-dac224a7b894
|
||||
appidacr : 0
|
||||
family_name : company
|
||||
given_name : admin
|
||||
ipaddr : 107.210.220.129
|
||||
name : admin company
|
||||
oid : 1713a7bf-47ba-4826-a2a7-bbda9fabe948
|
||||
puid : 100354
|
||||
rh : 0QfALA.
|
||||
scp : user_impersonation
|
||||
sub : BGwHjKPU
|
||||
tenant_region_scope : NA
|
||||
tid : f2b2ba53-ed2a-4f4c-a4c3-85c61e548975
|
||||
unique_name : admin@company.onmicrosoft.com
|
||||
upn : admin@company.onmicrosoft.com
|
||||
uti : -EWK6jMDrEiAesWsiAA
|
||||
ver : 1.0
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True,ValueFromPipeline)]
|
||||
[String]$AccessToken,
|
||||
[Parameter()]
|
||||
[Switch]$ShowDate,
|
||||
[Parameter()]
|
||||
[Switch]$Validate
|
||||
|
||||
)
|
||||
Process
|
||||
{
|
||||
if([string]::IsNullOrEmpty($AccessToken))
|
||||
{
|
||||
Write-Warning "Unable to read access token (null or empty)"
|
||||
return $null
|
||||
}
|
||||
# Token sections
|
||||
$sections = $AccessToken.Split(".")
|
||||
|
||||
# Check if this is JWE
|
||||
if($sections.Count -eq 5)
|
||||
{
|
||||
Write-Warning "JWE token, expected JWS. Unable to parse."
|
||||
return
|
||||
}
|
||||
$header = $sections[0]
|
||||
$payload = $sections[1]
|
||||
$signature = $sections[2]
|
||||
|
||||
# Convert the token to string and json
|
||||
$payloadString = Convert-B64ToText -B64 $payload
|
||||
$payloadObj=$payloadString | ConvertFrom-Json
|
||||
|
||||
if($ShowDate)
|
||||
{
|
||||
# Show dates
|
||||
$payloadObj.exp=($epoch.Date.AddSeconds($payloadObj.exp)).toString("yyyy-MM-ddTHH:mm:ssZ").Replace(".",":")
|
||||
$payloadObj.iat=($epoch.Date.AddSeconds($payloadObj.iat)).toString("yyyy-MM-ddTHH:mm:ssZ").Replace(".",":")
|
||||
$payloadObj.nbf=($epoch.Date.AddSeconds($payloadObj.nbf)).toString("yyyy-MM-ddTHH:mm:ssZ").Replace(".",":")
|
||||
}
|
||||
|
||||
if($Validate)
|
||||
{
|
||||
# Check the signature
|
||||
if((Is-AccessTokenValid -AccessToken $AccessToken))
|
||||
{
|
||||
Write-Verbose "Access Token signature successfully verified"
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Error "Access Token signature could not be verified"
|
||||
}
|
||||
|
||||
# Check the timestamp
|
||||
if((Is-AccessTokenExpired -AccessToken $AccessToken))
|
||||
{
|
||||
Write-Error "Access Token is expired"
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Verbose "Access Token is not expired"
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
# Debug
|
||||
Write-Debug "PARSED ACCESS TOKEN: $($payloadObj | Out-String)"
|
||||
|
||||
# Return
|
||||
$payloadObj
|
||||
}
|
||||
}
|
||||
|
||||
# Converts the given B64 encoded RSA cert or private key to PEM
|
||||
# Jan 29th 2025
|
||||
Function Convert-B64RSAToPem
|
||||
{
|
||||
[cmdletbinding()]
|
||||
param(
|
||||
[parameter(Mandatory=$true,ValueFromPipeline)]
|
||||
[String]$RSA,
|
||||
[parameter(Mandatory=$false,ValueFromPipeLine)]
|
||||
[ValidateSet("RSA PRIVATE KEY","CERTIFICATE")]
|
||||
[String]$Type = "CERTIFICATE"
|
||||
)
|
||||
Process
|
||||
{
|
||||
$PEM = "-----BEGIN $Type-----`n"
|
||||
|
||||
$p = 0
|
||||
while($p -lt $RSA.Length)
|
||||
{
|
||||
$s = $p
|
||||
$l = 64
|
||||
$c = $RSA.Length - $p
|
||||
if($c -lt 64)
|
||||
{
|
||||
$l = $c
|
||||
}
|
||||
$line = $RSA.Substring($s,$l)
|
||||
$PEM += "$line`n"
|
||||
|
||||
$p += 64
|
||||
}
|
||||
|
||||
$PEM += "-----END $Type-----`n"
|
||||
|
||||
return $PEM
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# Gets the thumbprint of the given B64 encoded RSA certificate
|
||||
# Jan 29th 2025
|
||||
Function Get-B64RSAThumbprint
|
||||
{
|
||||
[cmdletbinding()]
|
||||
param(
|
||||
[parameter(Mandatory=$true,ValueFromPipeline)]
|
||||
[String]$RSA
|
||||
)
|
||||
Process
|
||||
{
|
||||
$binRSA = [byte[]](Convert-B64ToByteArray -B64 $RSA)
|
||||
$thumbprint = Get-Digest -Data $binRSA
|
||||
|
||||
return $thumbprint
|
||||
}
|
||||
}
|
||||
Binary file not shown.
File diff suppressed because it is too large
Load Diff
Binary file not shown.
@@ -0,0 +1,297 @@
|
||||
<?xml version="1.0"?>
|
||||
<doc>
|
||||
<assembly>
|
||||
<name>Microsoft.IdentityModel.Abstractions</name>
|
||||
</assembly>
|
||||
<members>
|
||||
<member name="T:Microsoft.IdentityModel.Abstractions.EventLogLevel">
|
||||
<summary>
|
||||
Defines Event Log Levels.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Microsoft.IdentityModel.Abstractions.EventLogLevel.LogAlways">
|
||||
<summary>
|
||||
No level filtering is done on this log level. Log messages of all levels will be logged.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Microsoft.IdentityModel.Abstractions.EventLogLevel.Critical">
|
||||
<summary>
|
||||
Logs that describe an unrecoverable application or system crash, or a catastrophic failure that requires
|
||||
immediate attention.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Microsoft.IdentityModel.Abstractions.EventLogLevel.Error">
|
||||
<summary>
|
||||
Logs that highlight when the current flow of execution is stopped due to a failure. These should indicate a
|
||||
failure in the current activity, not an application-wide failure.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Microsoft.IdentityModel.Abstractions.EventLogLevel.Warning">
|
||||
<summary>
|
||||
Logs that highlight an abnormal or unexpected event in the application flow, but do not otherwise cause the
|
||||
application execution to stop.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Microsoft.IdentityModel.Abstractions.EventLogLevel.Informational">
|
||||
<summary>
|
||||
Logs that track the general flow of the application. These logs should have long-term value.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Microsoft.IdentityModel.Abstractions.EventLogLevel.Verbose">
|
||||
<summary>
|
||||
Logs that are used for interactive investigation during development. These logs should primarily contain
|
||||
information useful for debugging and have no long-term value.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:Microsoft.IdentityModel.Abstractions.IIdentityLogger">
|
||||
<summary>
|
||||
Interface that needs to be implemented by classes providing logging in Microsoft identity libraries.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.IIdentityLogger.IsEnabled(Microsoft.IdentityModel.Abstractions.EventLogLevel)">
|
||||
<summary>
|
||||
Checks to see if logging is enabled at given <paramref name="eventLogLevel"/>.
|
||||
</summary>
|
||||
<param name="eventLogLevel">Log level of a message.</param>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.IIdentityLogger.Log(Microsoft.IdentityModel.Abstractions.LogEntry)">
|
||||
<summary>
|
||||
Writes a log entry.
|
||||
</summary>
|
||||
<param name="entry">Defines a structured message to be logged at the provided <see cref="P:Microsoft.IdentityModel.Abstractions.LogEntry.EventLogLevel"/>.</param>
|
||||
</member>
|
||||
<member name="T:Microsoft.IdentityModel.Abstractions.ITelemetryClient">
|
||||
<summary>
|
||||
Interface for Telemetry tracking.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Microsoft.IdentityModel.Abstractions.ITelemetryClient.ClientId">
|
||||
<summary>
|
||||
Gets or sets the application or client ID that telemetry is being sent for.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.ITelemetryClient.Initialize">
|
||||
<summary>
|
||||
Perform any necessary bootstrapping for the telemetry client.
|
||||
</summary>
|
||||
<remarks>
|
||||
The expectation is that this should only be called once for the lifetime of the object however the
|
||||
implementation should be idempotent.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.ITelemetryClient.IsEnabled">
|
||||
<summary>
|
||||
Checks to see if telemetry is enabled all up.
|
||||
</summary>
|
||||
<returns>
|
||||
Returns <see langword="true"/> if telemetry should be sent; <see langword="false"/> otherwise.
|
||||
</returns>
|
||||
<remarks>
|
||||
This check should be used to gate any resource intensive operations to generate telemetry as well.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.ITelemetryClient.IsEnabled(System.String)">
|
||||
<summary>
|
||||
Checks to see if telemetry is enabled for the named event.
|
||||
</summary>
|
||||
<param name="eventName">Name of the event to check.</param>
|
||||
<returns>
|
||||
Returns <see langword="true"/> if telemetry should be sent for <paramref name="eventName"/>;
|
||||
<see langword="false"/> otherwise.
|
||||
</returns>
|
||||
<remarks>
|
||||
This check should be used to gate any resource intensive operations to generate telemetry as well.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.ITelemetryClient.TrackEvent(Microsoft.IdentityModel.Abstractions.TelemetryEventDetails)">
|
||||
<summary>
|
||||
Tracks an instance of a named event.
|
||||
</summary>
|
||||
<param name="eventDetails">Details of the event to track.</param>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.ITelemetryClient.TrackEvent(System.String,System.Collections.Generic.IDictionary{System.String,System.String},System.Collections.Generic.IDictionary{System.String,System.Int64},System.Collections.Generic.IDictionary{System.String,System.Boolean},System.Collections.Generic.IDictionary{System.String,System.DateTime},System.Collections.Generic.IDictionary{System.String,System.Double},System.Collections.Generic.IDictionary{System.String,System.Guid})">
|
||||
<summary>
|
||||
Tracks an instance of a named event.
|
||||
</summary>
|
||||
<param name="eventName">Name of the event to track. Should be unique per scenario.</param>
|
||||
<param name="stringProperties">Key value pair of strings to long with the event.</param>
|
||||
<param name="longProperties">Key value pair of longs to long with the event.</param>
|
||||
<param name="boolProperties">Key value pair of bools to long with the event.</param>
|
||||
<param name="dateTimeProperties">Key value pair of DateTimes to long with the event.</param>
|
||||
<param name="doubleProperties">Key value pair of doubles to long with the event.</param>
|
||||
<param name="guidProperties">Key value pair of Guids to long with the event.</param>
|
||||
</member>
|
||||
<member name="T:Microsoft.IdentityModel.Abstractions.LogEntry">
|
||||
<summary>
|
||||
Defines the structure of a log entry.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Microsoft.IdentityModel.Abstractions.LogEntry.EventLogLevel">
|
||||
<summary>
|
||||
Defines the <see cref="P:Microsoft.IdentityModel.Abstractions.LogEntry.EventLogLevel"/>.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Microsoft.IdentityModel.Abstractions.LogEntry.Message">
|
||||
<summary>
|
||||
Message to be logged.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Microsoft.IdentityModel.Abstractions.LogEntry.CorrelationId">
|
||||
<summary>
|
||||
A unique identifier for a request that can help with diagnostics across components.
|
||||
</summary>
|
||||
<remarks>
|
||||
Also referred to as ActivityId in Microsoft.IdentityModel.Tokens.CallContext.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="T:Microsoft.IdentityModel.Abstractions.NullIdentityModelLogger">
|
||||
<summary>
|
||||
A minimalistic <see cref="T:Microsoft.IdentityModel.Abstractions.IIdentityLogger"/> implementation that is disabled by default and doesn't log.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Microsoft.IdentityModel.Abstractions.NullIdentityModelLogger.Instance">
|
||||
<summary>
|
||||
Default instance of <see cref="T:Microsoft.IdentityModel.Abstractions.NullIdentityModelLogger"/>.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.NullIdentityModelLogger.IsEnabled(Microsoft.IdentityModel.Abstractions.EventLogLevel)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.NullIdentityModelLogger.Log(Microsoft.IdentityModel.Abstractions.LogEntry)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="T:Microsoft.IdentityModel.Abstractions.NullTelemetryClient">
|
||||
<summary>
|
||||
The default implementation of the <see cref="T:Microsoft.IdentityModel.Abstractions.ITelemetryClient"/> interface which swallows all telemetry signals.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Microsoft.IdentityModel.Abstractions.NullTelemetryClient.ClientId">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="P:Microsoft.IdentityModel.Abstractions.NullTelemetryClient.Instance">
|
||||
<summary>
|
||||
Singleton instance of <see cref="T:Microsoft.IdentityModel.Abstractions.NullTelemetryClient"/>.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.NullTelemetryClient.#ctor">
|
||||
<summary>
|
||||
Initializes an instance of <see cref="T:Microsoft.IdentityModel.Abstractions.NullTelemetryClient"/>.
|
||||
</summary>
|
||||
<remarks>
|
||||
Private constructor to prevent the default constructor being exposed.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.NullTelemetryClient.IsEnabled">
|
||||
<inheritdoc />
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.NullTelemetryClient.IsEnabled(System.String)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.NullTelemetryClient.Initialize">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.NullTelemetryClient.TrackEvent(Microsoft.IdentityModel.Abstractions.TelemetryEventDetails)">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.NullTelemetryClient.TrackEvent(System.String,System.Collections.Generic.IDictionary{System.String,System.String},System.Collections.Generic.IDictionary{System.String,System.Int64},System.Collections.Generic.IDictionary{System.String,System.Boolean},System.Collections.Generic.IDictionary{System.String,System.DateTime},System.Collections.Generic.IDictionary{System.String,System.Double},System.Collections.Generic.IDictionary{System.String,System.Guid})">
|
||||
<inheritdoc/>
|
||||
</member>
|
||||
<member name="T:Microsoft.IdentityModel.Abstractions.ObservabilityConstants">
|
||||
<summary>
|
||||
Common class containing observability constants to be used as well known metric keys.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Microsoft.IdentityModel.Abstractions.ObservabilityConstants.Succeeded">
|
||||
<summary>
|
||||
String used for the name of the property indicating if the call was successful.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Microsoft.IdentityModel.Abstractions.ObservabilityConstants.Duration">
|
||||
<summary>
|
||||
String used for the name of the property indicating the call in Duration (ms).
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Microsoft.IdentityModel.Abstractions.ObservabilityConstants.ActivityId">
|
||||
<summary>
|
||||
String used for the name of the property indicating the call's Activity Id/Correlation Id.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="F:Microsoft.IdentityModel.Abstractions.ObservabilityConstants.ClientId">
|
||||
<summary>
|
||||
String used for the name of the property indicating the caller's ClientId.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="T:Microsoft.IdentityModel.Abstractions.TelemetryEventDetails">
|
||||
<summary>
|
||||
Details of the telemetry event.
|
||||
</summary>
|
||||
<remarks>
|
||||
This implementation is not meant to be thread-safe. This implementation would either need to be overridden or
|
||||
usage should not be concurrently operated on.
|
||||
</remarks>
|
||||
</member>
|
||||
<member name="P:Microsoft.IdentityModel.Abstractions.TelemetryEventDetails.PropertyValues">
|
||||
<summary>
|
||||
The underlying properties making up the <see cref="T:Microsoft.IdentityModel.Abstractions.TelemetryEventDetails"/>.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Microsoft.IdentityModel.Abstractions.TelemetryEventDetails.Name">
|
||||
<summary>
|
||||
Name of the telemetry event, should be unique between events.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="P:Microsoft.IdentityModel.Abstractions.TelemetryEventDetails.Properties">
|
||||
<summary>
|
||||
Properties which describe the event.
|
||||
</summary>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.TelemetryEventDetails.SetProperty(System.String,System.String)">
|
||||
<summary>
|
||||
Sets a property on the event details.
|
||||
</summary>
|
||||
<param name="key">Property key.</param>
|
||||
<param name="value">Property value.</param>
|
||||
<exception cref="T:System.ArgumentNullException">'key' is null.</exception>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.TelemetryEventDetails.SetProperty(System.String,System.Int64)">
|
||||
<summary>
|
||||
Sets a property on the event details.
|
||||
</summary>
|
||||
<param name="key">Property key.</param>
|
||||
<param name="value">Property value.</param>
|
||||
<exception cref="T:System.ArgumentNullException">'key' is null.</exception>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.TelemetryEventDetails.SetProperty(System.String,System.Boolean)">
|
||||
<summary>
|
||||
Sets a property on the event details.
|
||||
</summary>
|
||||
<param name="key">Property key.</param>
|
||||
<param name="value">Property value.</param>
|
||||
<exception cref="T:System.ArgumentNullException">'key' is null.</exception>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.TelemetryEventDetails.SetProperty(System.String,System.DateTime)">
|
||||
<summary>
|
||||
Sets a property on the event details.
|
||||
</summary>
|
||||
<param name="key">Property key.</param>
|
||||
<param name="value">Property value.</param>
|
||||
<exception cref="T:System.ArgumentNullException">'key' is null.</exception>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.TelemetryEventDetails.SetProperty(System.String,System.Double)">
|
||||
<summary>
|
||||
Sets a property on the event details.
|
||||
</summary>
|
||||
<param name="key">Property key.</param>
|
||||
<param name="value">Property value.</param>
|
||||
<exception cref="T:System.ArgumentNullException">'key' is null.</exception>
|
||||
</member>
|
||||
<member name="M:Microsoft.IdentityModel.Abstractions.TelemetryEventDetails.SetProperty(System.String,System.Guid)">
|
||||
<summary>
|
||||
Sets a property on the event details.
|
||||
</summary>
|
||||
<param name="key">Property key.</param>
|
||||
<param name="value">Property value.</param>
|
||||
<exception cref="T:System.ArgumentNullException">'key' is null.</exception>
|
||||
</member>
|
||||
</members>
|
||||
</doc>
|
||||
@@ -12,22 +12,19 @@ function New-UserPRTToken
|
||||
Creates a new Primary Refresh Token (PRT) as JWT to be used to sign-in as the user.
|
||||
|
||||
.Parameter RefreshToken
|
||||
Primary Refresh Token (PRT) or the user.
|
||||
Primary Refresh Token (PRT) of the user.
|
||||
|
||||
.Parameter SessionKey
|
||||
The session key of the user
|
||||
The session key binded to given PRT
|
||||
|
||||
.Parameter Context
|
||||
The context used = B64 encoded byte array (size 24)
|
||||
Optional. The context used in the request header.
|
||||
|
||||
.Parameter Settings
|
||||
PSObject containing refresh_token and session_key attributes.
|
||||
|
||||
.Parameter Nonce
|
||||
Nonce to be added to the token.
|
||||
|
||||
.Parameter GetNonce
|
||||
Get nonce automatically by connecting to Azure AD.
|
||||
Optional. Nonce used in the request.
|
||||
|
||||
.EXAMPLE
|
||||
Get-AADIntAccessTokenForAADJoin -SaveToCache
|
||||
@@ -49,7 +46,7 @@ function New-UserPRTToken
|
||||
|
||||
PS C:\>$prtKeys = Get-UserAADIntPRTKeys -PfxFileName .\d03994c9-24f8-41ba-a156-1805998d6dc7.pfx -Credentials $cred
|
||||
|
||||
PS C:\>$prtToken = New-AADIntUserPRTToken -RefreshToken $prtKeys.refresh_token -SessionKey $prtKeys.session_key -GetNonce
|
||||
PS C:\>$prtToken = New-AADIntUserPRTToken -RefreshToken $prtKeys.refresh_token -SessionKey $prtKeys.session_key
|
||||
|
||||
PS C:\>$at = Get-AADIntAccessTokenForAADGraph -PRTToken $prtToken
|
||||
|
||||
@@ -59,7 +56,7 @@ function New-UserPRTToken
|
||||
eyJhbGciOiJIUzI1NiIsICJjdHgiOiJBQUFBQUFBQUFBQUF...
|
||||
|
||||
.EXAMPLE
|
||||
PS C:\>New-AADIntUserPRTToken -Settings $prtKeys -GetNonce
|
||||
PS C:\>New-AADIntUserPRTToken -Settings $prtKeys
|
||||
|
||||
eyJhbGciOiJIUzI1NiIsICJjdHgiOiJBQUFBQUFBQUFBQUF...
|
||||
|
||||
@@ -71,13 +68,13 @@ function New-UserPRTToken
|
||||
[Parameter(ParameterSetName='TokenAndKey',Mandatory=$True)]
|
||||
[String]$SessionKey,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$Context,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$Nonce,
|
||||
[String]$Context="8J+YiEFBREludGVybmFscyBGVFfwn5Kp",
|
||||
[Parameter(ParameterSetName='Settings',Mandatory=$True)]
|
||||
$Settings,
|
||||
[switch]$GetNonce,
|
||||
[bool]$KdfV2 = $true
|
||||
[Parameter(Mandatory=$False)]
|
||||
[bool]$KdfV2 = $true,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$SubScope
|
||||
)
|
||||
Process
|
||||
{
|
||||
@@ -88,69 +85,75 @@ function New-UserPRTToken
|
||||
throw "refresh_token and/or session_key missing!"
|
||||
}
|
||||
$RefreshToken = $Settings.refresh_token
|
||||
$SessionKey = $Settings.session_key
|
||||
$SessionKey = $Settings.session_key
|
||||
}
|
||||
|
||||
if(!$Context)
|
||||
return New-PRTSignedJWE -RefreshToken $RefreshToken -SessionKey $SessionKey -Context $Context -KdfV2 $KdfV2 -PRTToken
|
||||
}
|
||||
}
|
||||
|
||||
# Creates a new PRT signed request
|
||||
# Feb 4th 2025
|
||||
function New-SignedPRTRequest
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Creates a new signed PRT request.
|
||||
|
||||
.DESCRIPTION
|
||||
Creates a new signed Primary Refresh Token (PRT) request used to get access tokens.
|
||||
|
||||
.Parameter RefreshToken
|
||||
Primary Refresh Token (PRT) of the user.
|
||||
|
||||
.Parameter SessionKey
|
||||
The session key binded to given PRT
|
||||
|
||||
.Parameter Context
|
||||
Optional. The context used in the request header.
|
||||
|
||||
.Parameter Settings
|
||||
PSObject containing refresh_token and session_key attributes.
|
||||
|
||||
.Parameter Nonce
|
||||
Optional. Nonce used in the request.
|
||||
|
||||
.EXAMPLE
|
||||
TBA
|
||||
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(ParameterSetName='TokenAndKey',Mandatory=$True)]
|
||||
[String]$RefreshToken,
|
||||
[Parameter(ParameterSetName='TokenAndKey',Mandatory=$True)]
|
||||
[String]$SessionKey,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$Context="8J+YiEFBREludGVybmFscyBGVFfwn5Kp",
|
||||
[Parameter(ParameterSetName='Settings',Mandatory=$True)]
|
||||
$Settings,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[bool]$KdfV2 = $true,
|
||||
[Parameter(Mandatory=$True)]
|
||||
$ClientId,
|
||||
[Parameter(Mandatory=$True)]
|
||||
$Resource,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$SubScope
|
||||
)
|
||||
Process
|
||||
{
|
||||
if($Settings)
|
||||
{
|
||||
# Create a random context
|
||||
$ctx = New-Object byte[] 24
|
||||
([System.Security.Cryptography.RandomNumberGenerator]::Create()).GetBytes($ctx)
|
||||
}
|
||||
else
|
||||
{
|
||||
$ctx = Convert-B64ToByteArray -B64 $Context
|
||||
}
|
||||
|
||||
$sKey = Convert-B64ToByteArray -B64 $SessionKey
|
||||
$iat = [int]((Get-Date).ToUniversalTime() - $epoch).TotalSeconds
|
||||
|
||||
# Create the header and body
|
||||
$hdr = [ordered]@{
|
||||
"alg" = "HS256"
|
||||
"typ" = "JWT"
|
||||
"ctx" = (Convert-ByteArrayToB64 -Bytes $ctx)
|
||||
if([string]::IsNullOrEmpty($Settings.refresh_token) -or [string]::IsNullOrEmpty($Settings.session_key))
|
||||
{
|
||||
throw "refresh_token and/or session_key missing!"
|
||||
}
|
||||
$RefreshToken = $Settings.refresh_token
|
||||
$SessionKey = $Settings.session_key
|
||||
}
|
||||
|
||||
$pld = [ordered]@{
|
||||
"refresh_token" = $RefreshToken
|
||||
"is_primary" = "true"
|
||||
"iat" = $iat
|
||||
}
|
||||
|
||||
# Derive the key from session key and context
|
||||
if($KdfV2)
|
||||
{
|
||||
$hdr["kdf_ver"] = 2
|
||||
$derivedContext = Get-KDFv2Context -Context $ctx -Payload $pld
|
||||
}
|
||||
else
|
||||
{
|
||||
$derivedContext = $ctx
|
||||
}
|
||||
|
||||
$key = Get-PRTDerivedKey -Context $derivedContext -SessionKey $sKey
|
||||
|
||||
# Fetch the nonce if not provided
|
||||
if([string]::IsNullOrEmpty($Nonce))
|
||||
{
|
||||
$Nonce = (Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/Common/oauth2/token" -Body "grant_type=srv_challenge").Nonce
|
||||
}
|
||||
$pld["request_nonce"] = $Nonce
|
||||
|
||||
|
||||
# As the payload may have changed due to nonce, derive the key again if needed
|
||||
if($KdfV2)
|
||||
{
|
||||
$derivedContext = Get-KDFv2Context -Context $ctx -Payload $pld
|
||||
$key = Get-PRTDerivedKey -Context $derivedContext -SessionKey $sKey
|
||||
}
|
||||
|
||||
# Create the JWT
|
||||
$jwt = New-JWT -Key $key -Header $hdr -Payload $pld
|
||||
|
||||
# Return
|
||||
return $jwt
|
||||
return New-PRTSignedJWE -RefreshToken $RefreshToken -SessionKey $SessionKey -Context $Context -KdfV2 $KdfV2 -ClientId $ClientId -Resource $Resource -Request
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1575,3 +1578,324 @@ function Set-DeviceWHfBKey
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Generates a new P2P certificate
|
||||
# Aug 21st 2020
|
||||
function New-P2PDeviceCertificate
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Creates a new P2P device or user certificate using the device certificate or PRT information.
|
||||
|
||||
.DESCRIPTION
|
||||
Creates a new peer-to-peer (P2P) device or user certificate and exports it and the corresponding CA certificate.
|
||||
It can be used to enable RDP trust between devices of the same AAD tenant.
|
||||
|
||||
.EXAMPLE
|
||||
Get-AADIntAccessTokenForAADJoin -SaveToCache
|
||||
PS\:>Join-AADIntAzureAD -DeviceName "My computer" -DeviceType "Commodore" -OSVersion "C64"
|
||||
|
||||
.Parameter Certificate
|
||||
x509 certificate used to sign the certificate request.
|
||||
|
||||
.Parameter PfxFileName
|
||||
File name of the .pfx certificate used to sign the certificate request.
|
||||
|
||||
.Parameter PfxPassword
|
||||
The password of the .pfx certificate used to sign the certificate request.
|
||||
|
||||
.Parameter TenantId
|
||||
The tenant id or name of users' tenant.
|
||||
|
||||
.Parameter DeviceName
|
||||
The name of the device. Will be added to DNS Names attribute of the certificate.
|
||||
|
||||
.Parameter OSVersion
|
||||
The operating system version of the device. Defaults to "10.0.18363.0"
|
||||
|
||||
.Parameter RefreshToken
|
||||
Primary Refresh Token (PRT) or the user.
|
||||
|
||||
.Parameter SessionKey
|
||||
The session key of the user
|
||||
|
||||
.Parameter Context
|
||||
The context used = B64 encoded byte array (size 24)
|
||||
|
||||
.Parameter Settings
|
||||
PSObject containing refresh_token and session_key attributes.
|
||||
|
||||
.EXAMPLE
|
||||
PS C\:>New-AADIntP2PDeviceCertificate -PfxFileName .\d03994c9-24f8-41ba-a156-1805998d6dc7.pfx -DeviceName "mypc1.company.com"
|
||||
|
||||
Device P2P certificate successfully created:
|
||||
Subject: "CN=d03994c9-24f8-41ba-a156-1805998d6dc7, DC=4169fee0-df47-4e31-b1d7-5d248222b872"
|
||||
DnsName: "mypc1.company.com"
|
||||
Issuer: "CN=MS-Organization-P2P-Access [2020]"
|
||||
Cert thumbprint: 84D7641F9BFA90767EA3456E443E21948FC425E5
|
||||
Cert file name : "d03994c9-24f8-41ba-a156-1805998d6dc7-P2P.pfx"
|
||||
CA file name : "d03994c9-24f8-41ba-a156-1805998d6dc7-P2P-CA.der"
|
||||
|
||||
.EXAMPLE
|
||||
Get-AADIntAccessTokenForAADJoin -SaveToCache
|
||||
PS C:\>Join-AADIntAzureAD -DeviceName "My computer" -DeviceType "Commodore" -OSVersion "C64"
|
||||
|
||||
Device successfully registered to Azure AD:
|
||||
DisplayName: "My computer"
|
||||
DeviceId: d03994c9-24f8-41ba-a156-1805998d6dc7
|
||||
Cert thumbprint: 78CC77315A100089CF794EE49670552485DE3689
|
||||
Cert file name : "d03994c9-24f8-41ba-a156-1805998d6dc7.pfx"
|
||||
Local SID:
|
||||
S-1-5-32-544
|
||||
Additional SIDs:
|
||||
S-1-12-1-797902961-1250002609-2090226073-616445738
|
||||
S-1-12-1-3408697635-1121971140-3092833713-2344201430
|
||||
S-1-12-1-2007802275-1256657308-2098244751-2635987013
|
||||
|
||||
PS C:\>$creds = Get-Credential
|
||||
|
||||
PS C:\>$prtKeys = Get-UserAADIntPRTKeys -PfxFileName .\d03994c9-24f8-41ba-a156-1805998d6dc7.pfx -Credentials $cred
|
||||
|
||||
PS C:\>New-AADIntP2PDeviceCertificate -RefreshToken $prtKeys.refresh_token -SessionKey $prtKeys.session_key
|
||||
|
||||
User certificate successfully created:
|
||||
Subject: "CN=TestU@contoso.com, CN=S-1-12-1-xx-xx-xx-xx, DC=0f73eaa6-7fd6-48b8-8897-e382ba96daf4"
|
||||
Issuer: "CN=MS-Organization-P2P-Access [2020]"
|
||||
Cert thumbprint: A7F1D1F134569E0234E6AA722354D99C3AA68D0F
|
||||
Cert file name : "TestU@contoso.com-P2P.pfx"
|
||||
CA file name : "TestU@contoso.com-P2P-CA.der"
|
||||
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(ParameterSetName='Certificate',Mandatory=$True)]
|
||||
[System.Security.Cryptography.X509Certificates.X509Certificate2]$Certificate,
|
||||
|
||||
[Parameter(ParameterSetName='FileAndPassword',Mandatory=$True)]
|
||||
[string]$PfxFileName,
|
||||
[Parameter(ParameterSetName='FileAndPassword',Mandatory=$False)]
|
||||
[string]$PfxPassword,
|
||||
|
||||
[Parameter(ParameterSetName='TokenAndKey',Mandatory=$True)]
|
||||
[String]$RefreshToken,
|
||||
[Parameter(ParameterSetName='TokenAndKey',Mandatory=$True)]
|
||||
[String]$SessionKey,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$Context,
|
||||
[Parameter(ParameterSetName='Settings',Mandatory=$True)]
|
||||
$Settings,
|
||||
|
||||
[Parameter(ParameterSetName='FileAndPassword',Mandatory=$True)]
|
||||
[Parameter(ParameterSetName='Certificate',Mandatory=$True)]
|
||||
[String]$TenantId,
|
||||
[Parameter(ParameterSetName='FileAndPassword',Mandatory=$True)]
|
||||
[Parameter(ParameterSetName='Certificate',Mandatory=$True)]
|
||||
[String]$DeviceName,
|
||||
[Parameter(ParameterSetName='FileAndPassword',Mandatory=$False)]
|
||||
[Parameter(ParameterSetName='Certificate',Mandatory=$False)]
|
||||
[String]$OSVersion="10.0.18363.0",
|
||||
[Parameter(ParameterSetName='FileAndPassword',Mandatory=$False)]
|
||||
[Parameter(ParameterSetName='Certificate',Mandatory=$False)]
|
||||
[String[]]$DNSNames
|
||||
)
|
||||
Process
|
||||
{
|
||||
if($Settings)
|
||||
{
|
||||
if([string]::IsNullOrEmpty($Settings.refresh_token) -or [string]::IsNullOrEmpty($Settings.session_key))
|
||||
{
|
||||
throw "refresh_token and/or session_key missing!"
|
||||
}
|
||||
$RefreshToken = $Settings.refresh_token
|
||||
$SessionKey = $Settings.session_key
|
||||
}
|
||||
|
||||
if($SessionKey -ne $null -and [string]::IsNullOrEmpty($Context))
|
||||
{
|
||||
# Create a random context
|
||||
$ctx = New-Object byte[] 24
|
||||
([System.Security.Cryptography.RandomNumberGenerator]::Create()).GetBytes($ctx)
|
||||
}
|
||||
elseif($Context)
|
||||
{
|
||||
$ctx = Convert-B64ToByteArray -B64 $Context
|
||||
}
|
||||
|
||||
if($Certificate -eq $null -and [string]::IsNullOrEmpty($PfxFileName) -eq $false)
|
||||
{
|
||||
$Certificate = Load-Certificate -FileName $PfxFileName -Password $PfxPassword -Exportable
|
||||
}
|
||||
|
||||
if(!$DNSNames)
|
||||
{
|
||||
$DNSNames = @($DeviceName)
|
||||
}
|
||||
|
||||
if($Certificate)
|
||||
{
|
||||
$TenantId = (Parse-CertificateOIDs -Certificate $Certificate).TenantId
|
||||
}
|
||||
|
||||
if(!$TenantId)
|
||||
{
|
||||
$TenantId = (Read-Accesstoken $prtKeys.id_token).tid
|
||||
}
|
||||
|
||||
# Get the nonce
|
||||
$nonce = (Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/$TenantId/oauth2/token" -Body "grant_type=srv_challenge").Nonce
|
||||
|
||||
# We are doing this with the existing device certificate
|
||||
if($Certificate)
|
||||
{
|
||||
# Get the private key
|
||||
$privateKey = Load-PrivateKey -Certificate $Certificate
|
||||
|
||||
# Initialize the Certificate Signing Request object
|
||||
$CN = $Certificate.Subject
|
||||
$req = [System.Security.Cryptography.X509Certificates.CertificateRequest]::new($CN, $privateKey, [System.Security.Cryptography.HashAlgorithmName]::SHA256,[System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
|
||||
|
||||
# Create the signing request
|
||||
$csr = [convert]::ToBase64String($req.CreateSigningRequest())
|
||||
|
||||
# B64 encode the public key
|
||||
$x5c = [convert]::ToBase64String(($certificate.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Cert)))
|
||||
|
||||
# Create the header and body
|
||||
$hdr = [ordered]@{
|
||||
"alg" = "RS256"
|
||||
"typ" = "JWT"
|
||||
"x5c" = "$x5c"
|
||||
}
|
||||
|
||||
$pld = [ordered]@{
|
||||
"client_id" = "38aa3b87-a06d-4817-b275-7a316988d93b"
|
||||
"request_nonce" = $nonce
|
||||
"win_ver" = $OSVersion
|
||||
"grant_type" = "device_auth"
|
||||
"cert_token_use" = "device_cert"
|
||||
"csr_type" = "http://schemas.microsoft.com/windows/pki/2009/01/enrollment#PKCS10"
|
||||
"csr" = $csr
|
||||
"netbios_name" = $DeviceName
|
||||
"dns_names" = $DNSNames
|
||||
}
|
||||
|
||||
# Create the JWT
|
||||
$jwt = New-JWT -PrivateKey $privateKey -Header $hdr -Payload $pld
|
||||
|
||||
# Construct the body
|
||||
$body = @{
|
||||
"windows_api_version" = "2.0"
|
||||
"grant_type" = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||
"request" = "$jwt"
|
||||
}
|
||||
}
|
||||
else # We are doing this with the PRT keys information
|
||||
{
|
||||
# Create a private key and do something with it to get it stored
|
||||
$rsa=[System.Security.Cryptography.RSA]::Create(2048)
|
||||
|
||||
# Store the private key to so that it can be exported
|
||||
$cspParameters = [System.Security.Cryptography.CspParameters]::new()
|
||||
$cspParameters.ProviderName = "Microsoft Enhanced RSA and AES Cryptographic Provider"
|
||||
$cspParameters.ProviderType = 24
|
||||
$cspParameters.KeyContainerName ="AADInternals"
|
||||
|
||||
# Set the private key
|
||||
$privateKey = [System.Security.Cryptography.RSACryptoServiceProvider]::new(2048,$cspParameters)
|
||||
$privateKey.ImportParameters($rsa.ExportParameters($true))
|
||||
|
||||
# Initialize the Certificate Signing Request object
|
||||
$req = [System.Security.Cryptography.X509Certificates.CertificateRequest]::new("CN=", $rsa, [System.Security.Cryptography.HashAlgorithmName]::SHA256,[System.Security.Cryptography.RSASignaturePadding]::Pkcs1)
|
||||
|
||||
# Create the signing request
|
||||
$csr = [convert]::ToBase64String($req.CreateSigningRequest())
|
||||
|
||||
# Create the header and body
|
||||
$hdr = [ordered]@{
|
||||
"alg" = "HS256"
|
||||
"typ" = "JWT"
|
||||
"ctx" = (Convert-ByteArrayToB64 -Bytes $ctx)
|
||||
}
|
||||
|
||||
$pld = [ordered]@{
|
||||
"iss" = "aad:brokerplugin"
|
||||
"grant_type" = "refresh_token"
|
||||
"aud" = "login.microsoftonline.com"
|
||||
"request_nonce" = $nonce
|
||||
"scope" = "openid aza ugs"
|
||||
"refresh_token" = $RefreshToken
|
||||
"client_id" = "38aa3b87-a06d-4817-b275-7a316988d93b"
|
||||
"cert_token_use" = "user_cert"
|
||||
"csr_type" = "http://schemas.microsoft.com/windows/pki/2009/01/enrollment#PKCS10"
|
||||
"csr" = $csr
|
||||
}
|
||||
|
||||
# Create the JWT
|
||||
$jwt = New-JWT -Key (Get-PRTDerivedKey -Context $ctx -SessionKey (Convert-B64ToByteArray $SessionKey)) -Header $hdr -Payload $pld
|
||||
|
||||
# Construct the body
|
||||
$body = @{
|
||||
"grant_type" = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||
"request" = "$jwt"
|
||||
"windows_api_version" = "1.0"
|
||||
}
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
# Make the request to get the P2P certificate
|
||||
$response = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "https://login.microsoftonline.com/$tenantId/oauth2/token" -ContentType "application/x-www-form-urlencoded" -Body $body
|
||||
}
|
||||
catch
|
||||
{
|
||||
$errorMessage = $_.ErrorDetails.Message | ConvertFrom-Json
|
||||
Write-Error $errorMessage.error_description
|
||||
return
|
||||
}
|
||||
|
||||
# Get the certificate
|
||||
$binCert = [byte[]](Convert-B64ToByteArray -B64 $response.x5c)
|
||||
|
||||
# Create a new x509certificate
|
||||
$P2PCert = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($binCert,"",[System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::PersistKeySet -bor [System.Security.Cryptography.X509Certificates.X509KeyStorageFlags]::Exportable)
|
||||
$P2PCert.PrivateKey = $privateKey
|
||||
|
||||
# Write the device P2P certificate to disk
|
||||
$certName = $P2PCert.Subject.Split(",")[0].Split("=")[1]
|
||||
Set-BinaryContent -Path "$certName-P2P.pfx" -Value $P2PCert.Export([System.Security.Cryptography.X509Certificates.X509ContentType]::Pfx)
|
||||
|
||||
# Write the P2P certificate CA to disk
|
||||
$CA = @"
|
||||
-----BEGIN PUBLIC KEY-----
|
||||
$($response.x5c_ca)
|
||||
-----END PUBLIC KEY-----
|
||||
"@
|
||||
$CA | Set-Content "$certName-P2P-CA.der"
|
||||
|
||||
if($Certificate)
|
||||
{
|
||||
# Unload the private key
|
||||
Unload-PrivateKey -PrivateKey $privateKey
|
||||
}
|
||||
|
||||
# Print out information
|
||||
if($Certificate)
|
||||
{
|
||||
Write-Host "Device P2P certificate successfully created:"
|
||||
}
|
||||
else
|
||||
{
|
||||
Write-Host "User certificate successfully created:"
|
||||
}
|
||||
Write-Host " Subject: ""$($P2PCert.Subject)"""
|
||||
if($Certificate)
|
||||
{
|
||||
Write-Host " DnsNames: ""$($P2PCert.DnsNameList.Unicode)"""
|
||||
}
|
||||
Write-Host " Issuer: ""$($P2PCert.Issuer)"""
|
||||
Write-Host " Cert thumbprint: $($P2PCert.Thumbprint)"
|
||||
Write-host " Cert file name : ""$certName-P2P.pfx"""
|
||||
Write-host " CA file name : ""$certName-P2P-CA.der"""
|
||||
|
||||
}
|
||||
}
|
||||
+179
-11
@@ -279,9 +279,9 @@ function Get-PRTDerivedKey
|
||||
}
|
||||
}
|
||||
|
||||
# Get the access token with PRT
|
||||
# Get the access token with PRTToken
|
||||
# Aug 20th 2020
|
||||
function Get-AccessTokenWithPRT
|
||||
function Get-AccessTokenWithPRTToken
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
@@ -295,7 +295,8 @@ function Get-AccessTokenWithPRT
|
||||
[String]$RedirectUri,
|
||||
[switch]$GetNonce,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$Tenant
|
||||
[String]$Tenant,[Parameter(Mandatory=$False)]
|
||||
[String]$SubScope
|
||||
)
|
||||
Process
|
||||
{
|
||||
@@ -318,7 +319,7 @@ function Get-AccessTokenWithPRT
|
||||
$requestId = $mscrid
|
||||
|
||||
# Create url and headers
|
||||
$url = "https://login.microsoftonline.com/$Tenant/oauth2/authorize?resource=$Resource&client_id=$ClientId&response_type=code&redirect_uri=$RedirectUri&client-request-id=$requestId&mscrid=$mscrid"
|
||||
$url = "$(Get-TenantLoginUrl -SubScope $SubScope)/$Tenant/oauth2/authorize?resource=$Resource&client_id=$ClientId&response_type=code&redirect_uri=$RedirectUri&client-request-id=$requestId&mscrid=$mscrid"
|
||||
|
||||
# Add sso_nonce if exist
|
||||
if($parsedCookie.request_nonce)
|
||||
@@ -327,13 +328,19 @@ function Get-AccessTokenWithPRT
|
||||
}
|
||||
|
||||
$headers = @{
|
||||
"User-Agent" = ""
|
||||
"User-Agent" = Get-UserAgent
|
||||
"x-ms-RefreshTokenCredential" = $Cookie
|
||||
}
|
||||
|
||||
# First, make the request to get the authorisation code (tries to redirect so throws an error)
|
||||
$response = Invoke-WebRequest -UseBasicParsing -Uri $url -Headers $headers -MaximumRedirection 0 -ErrorAction SilentlyContinue
|
||||
|
||||
$config = Parse-LoginMicrosoftOnlineComConfig -Body $response
|
||||
if($config.sErrorCode)
|
||||
{
|
||||
Throw (Get-Error -ErrorCode $config.sErrorCode)
|
||||
}
|
||||
|
||||
$code = Parse-CodeFromResponse -Response $response
|
||||
|
||||
if(!$code)
|
||||
@@ -349,8 +356,13 @@ function Get-AccessTokenWithPRT
|
||||
redirect_uri = $RedirectUri
|
||||
}
|
||||
|
||||
if($CAE)
|
||||
{
|
||||
$body["claims"] = Get-CAEClaims
|
||||
}
|
||||
|
||||
# Make the second request to get the access token
|
||||
$response = Invoke-RestMethod -UseBasicParsing -Uri "https://login.microsoftonline.com/common/oauth2/token" -Body $body -ContentType "application/x-www-form-urlencoded" -Method Post
|
||||
$response = Invoke-RestMethod -UseBasicParsing -Uri "$(Get-TenantLoginUrl -SubScope $SubScope)/common/oauth2/token" -Body $body -ContentType "application/x-www-form-urlencoded" -Method Post
|
||||
|
||||
Write-Debug "ACCESS TOKEN: $($response.access_token)"
|
||||
Write-Debug "REFRESH TOKEN: $($response.refresh_token)"
|
||||
@@ -372,11 +384,68 @@ function Get-AccessTokenWithBPRT
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Resource,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$ClientId
|
||||
[String]$ClientId,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$SubScope
|
||||
)
|
||||
Process
|
||||
{
|
||||
Get-AccessTokenWithRefreshToken -Resource "urn:ms-drs:enterpriseregistration.windows.net" -ClientId "b90d5b8f-5503-4153-b545-b31cecfaece2" -TenantId "Common" -RefreshToken $BPRT
|
||||
Get-AccessTokenWithRefreshToken -Resource "urn:ms-drs:enterpriseregistration.windows.net" -ClientId "b90d5b8f-5503-4153-b545-b31cecfaece2" -TenantId "Common" -RefreshToken $BPRT -SubScope $SubScope
|
||||
}
|
||||
}
|
||||
|
||||
# Gets the access token using a signed PRT request
|
||||
# Feb 4th 2025
|
||||
function Get-AccessTokenWithSignedPRTRequest
|
||||
{
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$RefreshToken,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$SessionKey,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$Context="8J+YiEFBREludGVybmFscyBGVFfwn5Kp",
|
||||
[Parameter(Mandatory=$True)]
|
||||
$ClientId,
|
||||
[Parameter(Mandatory=$True)]
|
||||
$Resource,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$SubScope,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[bool]$CAE
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Create a signed PRT request
|
||||
$jwt = New-PRTSignedJWE -RefreshToken $RefreshToken -SessionKey $SessionKey -Context $Context -KdfV2 $true -ClientId $ClientId -Resource $Resource -Request
|
||||
|
||||
# Set the body for API call
|
||||
$body = @{
|
||||
"windows_api_version" = "2.0"
|
||||
"grant_type" = "urn:ietf:params:oauth:grant-type:jwt-bearer"
|
||||
"request" = $jwt
|
||||
"client_info" = "1"
|
||||
"tgt" = "true"
|
||||
}
|
||||
|
||||
# Set Continuous Access Evaluation (CAE) token claims
|
||||
if($CAE)
|
||||
{
|
||||
$body["claims"] = Get-CAEClaims
|
||||
}
|
||||
|
||||
# Get the encrypted response
|
||||
$jwe = Invoke-RestMethod -UseBasicParsing -Method Post -Uri "$(Get-TenantLoginUrl -SubScope $SubScope)/Common/oauth2/token" -Body $body #-Headers $headers -WebSession $session
|
||||
|
||||
# Convert session key to byte array and decrypt JWE
|
||||
$sKey = Convert-B64ToByteArray -B64 $SessionKey
|
||||
$response = [text.encoding]::UTF8.GetString((Decrypt-JWE -JWE $jwe -SessionKey $sKey)) | ConvertFrom-Json
|
||||
|
||||
# Debug
|
||||
Write-Debug "ACCESS TOKEN RESPONSE: $response"
|
||||
|
||||
return $response
|
||||
}
|
||||
}
|
||||
|
||||
@@ -688,7 +757,7 @@ Function Decrypt-JWE
|
||||
{
|
||||
$alg = "RSA-OAEP"
|
||||
}
|
||||
elseif($parsedJWE.enc -ne "A256GCM")
|
||||
elseif($parsedJWE.enc -notin "A256GCM","A128CBC-HS256")
|
||||
{
|
||||
Throw "Unsupported enc: $enc"
|
||||
}
|
||||
@@ -787,7 +856,7 @@ Function Decrypt-JWE
|
||||
else
|
||||
{
|
||||
# De-deflate
|
||||
if($parsedJWE.zip -eq "Deflate")
|
||||
if($parsedJWE.zip -in "Deflate","DEF")
|
||||
{
|
||||
$retVal = Get-DeDeflatedByteArray -byteArray $decData
|
||||
}
|
||||
@@ -938,4 +1007,103 @@ Function New-JWE
|
||||
Throw "Unsupported alg: $alg"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Creates a PRT signed JWE
|
||||
# Feb 4th 2025
|
||||
function New-PRTSignedJWE
|
||||
{
|
||||
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$RefreshToken,
|
||||
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$SessionKey,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$Context="8J+YiEFBREludGVybmFscyBGVFfwn5Kp",
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[bool]$KdfV2 = $true,
|
||||
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$SubScope,
|
||||
|
||||
[Parameter(ParameterSetName='PRTToken',Mandatory=$True)]
|
||||
[switch]$PRTToken,
|
||||
|
||||
[Parameter(ParameterSetName='Request',Mandatory=$True)]
|
||||
[switch]$Request,
|
||||
|
||||
[Parameter(ParameterSetName='PRTToken',Mandatory=$False)]
|
||||
[Parameter(ParameterSetName='Request',Mandatory=$True)]
|
||||
[string]$ClientId,
|
||||
|
||||
[Parameter(ParameterSetName='PRTToken',Mandatory=$False)]
|
||||
[Parameter(ParameterSetName='Request',Mandatory=$True)]
|
||||
[string]$Resource
|
||||
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Define variables
|
||||
$ctx = Convert-B64ToByteArray -B64 $Context
|
||||
$sKey = Convert-B64ToByteArray -B64 $SessionKey
|
||||
$iat = [int]((Get-Date).ToUniversalTime() - $epoch).TotalSeconds
|
||||
$nonce = (Invoke-RestMethod -UseBasicParsing -Method Post -Uri "$(Get-TenantLoginUrl -SubScope $SubScope)/Common/oauth2/token" -Body "grant_type=srv_challenge").Nonce
|
||||
|
||||
# Create the header and body
|
||||
$hdr = [ordered]@{
|
||||
"alg" = "HS256"
|
||||
"typ" = "JWT"
|
||||
"ctx" = $Context
|
||||
}
|
||||
|
||||
if($PRTToken)
|
||||
{
|
||||
$pld = [ordered]@{
|
||||
"refresh_token" = $RefreshToken
|
||||
"is_primary" = "true"
|
||||
"iat" = $iat
|
||||
"request_nonce" = $nonce
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
$pld=[ordered]@{
|
||||
#"win_ver"= "10.0.19041.1620"
|
||||
"scope"= "openid aza"
|
||||
"resource" = $resource
|
||||
"request_nonce" = $nonce
|
||||
"refresh_token" = $RefreshToken
|
||||
#"redirect_uri" = "ms-appx-web://Microsoft.AAD.BrokerPlugin/$clientId"
|
||||
"redirect_uri" = Get-AuthRedirectUrl -ClientId $ClientId -Resource $Resource
|
||||
"iss" = "aad:brokerplugin"
|
||||
"grant_type" = "refresh_token"
|
||||
"client_id" = $clientId
|
||||
"aud" = "login.microsoftonline.com"
|
||||
}
|
||||
}
|
||||
|
||||
# Derive the key from session key and context
|
||||
if($KdfV2)
|
||||
{
|
||||
$hdr["kdf_ver"] = 2
|
||||
$derivedContext = Get-KDFv2Context -Context $ctx -Payload $pld
|
||||
}
|
||||
else
|
||||
{
|
||||
$derivedContext = $ctx
|
||||
}
|
||||
|
||||
$key = Get-PRTDerivedKey -Context $derivedContext -SessionKey $sKey
|
||||
|
||||
# Create the JWT
|
||||
$jwt = New-JWT -Key $key -Header $hdr -Payload $pld
|
||||
|
||||
# Return
|
||||
return $jwt
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,42 +18,29 @@ function Get-SPOSiteGroups
|
||||
SharePoint Online authentication header
|
||||
|
||||
.Example
|
||||
PS C:\>$auth=Get-AADIntSPOAuthenticationHeader -Site https://company.sharepoint.com
|
||||
PS C:\>Get-AADIntSPOSiteGroups -Site https://company.sharepoint.com/sales -AuthHeader $auth
|
||||
PS C:\>Get-AADIntAccessTokenForSPO -SaveToCache
|
||||
PS C:\>Get-AADIntSPOSiteGroups -Site https://company.sharepoint.com/sales
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Site,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AuthHeader,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AccessToken
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Check the site url
|
||||
if($Site.EndsWith("/"))
|
||||
{
|
||||
$Site=$Site.Substring(0,$Site.Length-1)
|
||||
}
|
||||
$Site=$Site.Trim("/")
|
||||
|
||||
$siteDomain=$Site.Split("/")[2]
|
||||
|
||||
if(![string]::IsNullOrEmpty($AuthHeader))
|
||||
{
|
||||
# Create a WebSession object
|
||||
$siteSession = Create-WebSession -SetCookieHeader $AuthHeader -Domain $siteDomain
|
||||
}
|
||||
else
|
||||
{
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource $site -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
||||
$headers=@{
|
||||
"Authorization" = "Bearer $AccessToken"
|
||||
}
|
||||
}
|
||||
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource $site -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
||||
$headers=@{
|
||||
"Authorization" = "Bearer $AccessToken"
|
||||
}
|
||||
|
||||
# Invoke the request
|
||||
$response=Invoke-WebRequest -UseBasicParsing -Uri "$Site/_api/web/sitegroups" -Method Get -WebSession $siteSession -ErrorAction SilentlyContinue -Headers $headers
|
||||
|
||||
@@ -107,16 +94,14 @@ function Get-SPOSiteUsers
|
||||
SharePoint Online authentication header
|
||||
|
||||
.Example
|
||||
PS C:\>$auth=Get-AADIntSPOAuthenticationHeader -Site https://company.sharepoint.com
|
||||
PS C:\>Get-AADIntSPOSiteUsers -Site https://company.sharepoint.com/sales -AuthHeader $auth
|
||||
PS C:\>Get-AADIntAccessTokenForSPO -SaveToCache
|
||||
PS C:\>Get-AADIntSPOSiteUsers -Site https://company.sharepoint.com/sales
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Site,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AuthHeader,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AccessToken
|
||||
)
|
||||
Process
|
||||
@@ -125,19 +110,11 @@ function Get-SPOSiteUsers
|
||||
|
||||
$tenant=$Site.Split("/")[2]
|
||||
|
||||
if(![string]::IsNullOrEmpty($AuthHeader))
|
||||
{
|
||||
# Create a WebSession object
|
||||
$siteSession = Create-WebSession -SetCookieHeader $AuthHeader -Domain $siteDomain
|
||||
}
|
||||
else
|
||||
{
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
||||
$headers=@{
|
||||
"Authorization" = "Bearer $AccessToken"
|
||||
}
|
||||
}
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
||||
$headers=@{
|
||||
"Authorization" = "Bearer $AccessToken"
|
||||
}
|
||||
|
||||
# Invoke the request
|
||||
$response=Invoke-WebRequest -UseBasicParsing -Uri "$Site/_api/web/siteusers" -Method Get -WebSession $siteSession -Headers $headers -ErrorAction SilentlyContinue
|
||||
@@ -204,8 +181,8 @@ function Get-SPOUserProperties
|
||||
LoginName of the user in format "i:0i.t|00000003-0000-0ff1-ce00-000000000000|app@sharepoint"
|
||||
|
||||
.Example
|
||||
PS C:\>$auth=Get-AADIntSPOAuthenticationHeader -Site https://company.sharepoint.com
|
||||
PS C:\>Get-AADIntSPOUserProperties -Site https://company.sharepoint.com/sales -AuthHeader $auth -User "i:0i.t|00000003-0000-0ff1-ce00-000000000000|app@sharepoint"
|
||||
PS C:\>Get-AADIntAccessTokenForSPO -SaveToCache
|
||||
PS C:\>Get-AADIntSPOUserProperties -Site https://company.sharepoint.com/sales -User "i:0i.t|00000003-0000-0ff1-ce00-000000000000|app@sharepoint"
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
@@ -214,8 +191,6 @@ function Get-SPOUserProperties
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$UserName,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AuthHeader,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AccessToken
|
||||
)
|
||||
Process
|
||||
@@ -235,19 +210,11 @@ function Get-SPOUserProperties
|
||||
$UserName="i:0%23.f|membership|$UserName"
|
||||
}
|
||||
|
||||
if(![string]::IsNullOrEmpty($AuthHeader))
|
||||
{
|
||||
# Create a WebSession object
|
||||
$siteSession = Create-WebSession -SetCookieHeader $AuthHeader -Domain $siteDomain
|
||||
}
|
||||
else
|
||||
{
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant.sharepoint.com/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
||||
$headers=@{
|
||||
"Authorization" = "Bearer $AccessToken"
|
||||
}
|
||||
}
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant.sharepoint.com/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
||||
$headers=@{
|
||||
"Authorization" = "Bearer $AccessToken"
|
||||
}
|
||||
|
||||
# Invoke the request
|
||||
$response=Invoke-WebRequest2 -Uri "$Site/_api/sp.userprofiles.peoplemanager/getpropertiesfor(@v)?@v='$UserName'" -Method Get -WebSession $siteSession -Headers $headers -ErrorAction SilentlyContinue
|
||||
@@ -312,12 +279,8 @@ function Get-SPOSiteUserProperties
|
||||
SharePoint Online Access Token
|
||||
|
||||
.Example
|
||||
PS C:\>$auth=Get-AADIntSPOAuthenticationHeader -Site https://company.sharepoint.com
|
||||
PS C:\>Get-AADIntSPOSiteGroups -Site https://company.sharepoint.com/sales -AuthHeader $auth
|
||||
|
||||
.Example
|
||||
PS C:\>$at=Get-AADIntAccessTokenForSPO
|
||||
PS C:\>Get-AADIntSPOSiteGroups -Site https://company.sharepoint.com/sales -AccessToken $at
|
||||
PS C:\>Get-AADIntAccessTokenForSPO -SaveToCache
|
||||
PS C:\>Get-AADIntSPOSiteGroups -Site https://company.sharepoint.com/sales
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
@@ -326,17 +289,12 @@ function Get-SPOSiteUserProperties
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$UserName,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AuthHeader,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AccessToken
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Check the site url
|
||||
if($Site.EndsWith("/"))
|
||||
{
|
||||
$Site=$Site.Substring(0,$Site.Length-1)
|
||||
}
|
||||
$Site=$Site.Trim("/")
|
||||
|
||||
$siteDomain=$Site.Split("/")[2]
|
||||
|
||||
@@ -346,19 +304,11 @@ function Get-SPOSiteUserProperties
|
||||
$UserName="i:0%23.f|membership|$UserName"
|
||||
}
|
||||
|
||||
if(![string]::IsNullOrEmpty($AuthHeader))
|
||||
{
|
||||
# Create a WebSession object
|
||||
$siteSession = Create-WebSession -SetCookieHeader $AuthHeader -Domain $siteDomain
|
||||
}
|
||||
else
|
||||
{
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant.sharepoint.com/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
||||
$headers=@{
|
||||
"Authorization" = "Bearer $AccessToken"
|
||||
}
|
||||
}
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant.sharepoint.com/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
||||
$headers=@{
|
||||
"Authorization" = "Bearer $AccessToken"
|
||||
}
|
||||
|
||||
# Invoke the request
|
||||
$response=Invoke-WebRequest -UseBasicParsing -Uri "$Site/_api/SP.UserProfiles.PeopleManager/GetPropertiesFor(accountName=@v)?@v='$UserName'" -Method Get -WebSession $siteSession -ErrorAction SilentlyContinue -Headers $headers
|
||||
@@ -420,12 +370,8 @@ function Set-SPOSiteUserProperty
|
||||
Property value
|
||||
|
||||
.Example
|
||||
PS C:\>$auth=Get-AADIntSPOAuthenticationHeader -Site https://company.sharepoint.com
|
||||
PS C:\>Set-AADIntSPOUserProperty -Site https://company.sharepoint.com/sales -AuthHeader $auth -UserName user@company.com -Property "AboutMe" -Value "I'm a happy SPO user!"
|
||||
|
||||
.Example
|
||||
PS C:\>$at=Get-AADIntAccessTokenForSPO
|
||||
PS C:\>Set-AADIntSPOUserProperty -Site https://company.sharepoint.com/sales -AccessToken $at -UserName user@company.com -Property "AboutMe" -Value "I'm a happy SPO user!"
|
||||
PS C:\>Get-AADIntAccessTokenForSPO -SaveToCache
|
||||
PS C:\>Set-AADIntSPOUserProperty -Site https://company.sharepoint.com/sales -UserName user@company.com -Property "AboutMe" -Value "I'm a happy SPO user!"
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
@@ -434,8 +380,6 @@ function Set-SPOSiteUserProperty
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$UserName,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AuthHeader,
|
||||
[Parameter(Mandatory=$False)]
|
||||
[String]$AccessToken,
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Property,
|
||||
@@ -452,10 +396,7 @@ function Set-SPOSiteUserProperty
|
||||
}
|
||||
|
||||
# Check the site url
|
||||
if($Site.EndsWith("/"))
|
||||
{
|
||||
$Site=$Site.Substring(0,$Site.Length-1)
|
||||
}
|
||||
$Site=$Site.Trim("/")
|
||||
|
||||
$siteDomain=$Site.Split("/")[2]
|
||||
|
||||
@@ -465,17 +406,9 @@ function Set-SPOSiteUserProperty
|
||||
$UserName="i:0#.f|membership|$UserName"
|
||||
}
|
||||
|
||||
if(![string]::IsNullOrEmpty($AuthHeader))
|
||||
{
|
||||
# Create a WebSession object
|
||||
$siteSession = Create-WebSession -SetCookieHeader $AuthHeader -Domain $siteDomain
|
||||
}
|
||||
else
|
||||
{
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant.sharepoint.com/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
||||
$headers["Authorization"] = "Bearer $AccessToken"
|
||||
}
|
||||
# Get from cache if not provided
|
||||
$AccessToken = Get-AccessTokenFromCache -AccessToken $AccessToken -Resource "https://$Tenant.sharepoint.com/" -ClientId "d3590ed6-52b3-4102-aeff-aad2292ab01c"
|
||||
$headers["Authorization"] = "Bearer $AccessToken"
|
||||
|
||||
# Create the body
|
||||
$body=@{
|
||||
@@ -618,10 +551,8 @@ function Set-SPOSiteMembers
|
||||
Process
|
||||
{
|
||||
# Check the site url
|
||||
if($Site.EndsWith("/"))
|
||||
{
|
||||
$Site=$Site.Substring(0,$Site.Length-1)
|
||||
}
|
||||
$Site=$Site.Trim("/")
|
||||
|
||||
$siteDomain=$Site.Split("/")[2]
|
||||
|
||||
# Create a WebSession object
|
||||
|
||||
-146
@@ -1,151 +1,5 @@
|
||||
# Utility functions for SharePoint Online
|
||||
|
||||
# Gets the authentication cookie for SPO web interface
|
||||
# Supports MFA, federation, etc.
|
||||
# Jul 17th 2019
|
||||
function Get-SPOAuthenticationHeader
|
||||
{
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Gets authentication header for SharePoint Online
|
||||
|
||||
.DESCRIPTION
|
||||
Gets authentication header for SharePoint Online, which is used for example to retrieve site users.
|
||||
|
||||
.Parameter Site
|
||||
Url for the SharePoint Online
|
||||
|
||||
.Example
|
||||
Get-AADIntSPOAuthenticationHeader
|
||||
#>
|
||||
[cmdletbinding()]
|
||||
Param(
|
||||
[Parameter(Mandatory=$True)]
|
||||
[String]$Site
|
||||
)
|
||||
Process
|
||||
{
|
||||
# Check the site url
|
||||
$Site = $Site.Trim("/")
|
||||
|
||||
$siteDomain=$Site.Split("/")[2]
|
||||
|
||||
$headers=@{
|
||||
"User-Agent"="Mozilla/5.0 (Windows NT 10.0; Win64; x64)"
|
||||
"Upgrade-Insecure-Requests" = "1"
|
||||
"Accept-Encoding" = "gzip, deflate, br"
|
||||
"Accept-Language" = "en-US,en;q=0.9"
|
||||
"Accept" = "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3"
|
||||
|
||||
}
|
||||
|
||||
# Step 1: Go to the requested site
|
||||
$response = Invoke-WebRequest2 -uri $Site -MaximumRedirection 0 -ErrorAction SilentlyContinue
|
||||
|
||||
# Step 2: Go to "/_layouts/15/Authenticate.aspx?Source=%2F"
|
||||
$url = $response.Headers.'Location'
|
||||
$response = Invoke-WebRequest2 -uri $url -MaximumRedirection 0 -ErrorAction SilentlyContinue
|
||||
$siteWebSession = Create-WebSession -SetCookieHeader $response.Headers.'Set-Cookie' -Domain $siteDomain
|
||||
|
||||
# Step 3: Go to "/_forms/default.aspx?ReturnUrl=%2f_layouts%2f15%2fAuthenticate.aspx%3fSource%3d%252F&Source=cookie"
|
||||
$html=$response.Content
|
||||
$s=$html.IndexOf('href="')+6
|
||||
$e=$html.IndexOf('"',$s)
|
||||
$url=$html.Substring($s,$e-$s)
|
||||
$url="https://$siteDomain$url"
|
||||
$response = Invoke-WebRequest2 -uri $url -MaximumRedirection 0 -WebSession $siteWebSession -ErrorAction SilentlyContinue
|
||||
|
||||
# Create the cookie header for the login form
|
||||
$cookieHeaderValue=""
|
||||
$cookies = $response.Headers.'Set-Cookie'.Split(";,")
|
||||
foreach($cookie in $cookies)
|
||||
{
|
||||
|
||||
$name = $cookie.Split("=")[0].trim()
|
||||
$value = $cookie.Substring($name.Length+1)
|
||||
|
||||
if($name.StartsWith("nSGt") -or $name -eq "RpsContextCookie")
|
||||
{
|
||||
# If not empty, append the separator
|
||||
if(![String]::IsNullOrEmpty($cookieHeaderValue))
|
||||
{
|
||||
$cookieHeaderValue+="; "
|
||||
}
|
||||
|
||||
$cookieHeaderValue+="$name=$value"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# Set variables
|
||||
$auth_redirect="foobar"#"https://login.microsoftonline.com/common/federation/oauth2"#"https://login.microsoftonline.com/kmsi"
|
||||
$url=$response.Headers.Location
|
||||
|
||||
# Create the form
|
||||
$form = Create-LoginForm -Url $url -auth_redirect $auth_redirect -Headers "Cookie: $cookieHeaderValue"
|
||||
|
||||
# Show the form and wait for the return value
|
||||
if($form.ShowDialog() -ne "OK") {
|
||||
# Dispose the control
|
||||
$form.Controls[0].Dispose()
|
||||
Write-Verbose "Login cancelled"
|
||||
return $null
|
||||
}
|
||||
|
||||
# Extract the needed parameters
|
||||
$forminputs=$form.Controls[0].Document.getElementsByTagName("input")
|
||||
|
||||
$code = $forminputs.GetElementsByName("code")[0].GetAttribute("value")
|
||||
$session_state = $forminputs.GetElementsByName("session_state")[0].GetAttribute("value")
|
||||
$id_token = $forminputs.GetElementsByName("id_token")[0].GetAttribute("value")
|
||||
$correlation_id = $forminputs.GetElementsByName("correlation_id")[0].GetAttribute("value")
|
||||
$url=$form.Controls[0].Document.Forms[0].DomElement.action
|
||||
|
||||
# Dispose the control
|
||||
$form.Controls[0].Dispose()
|
||||
|
||||
# Create the body and get the cookie
|
||||
$body=@{
|
||||
"code" = $code
|
||||
"session_state" = $session_state
|
||||
"id_token" = $id_token
|
||||
"correlation_id" = $correlation_id
|
||||
}
|
||||
$response = Invoke-WebRequest2 -Uri $url -Method Post -Body $body -MaximumRedirection 0 -WebSession $siteWebSession
|
||||
|
||||
|
||||
|
||||
# Extract the cookies
|
||||
$cookieHeader = $response.Headers.'Set-Cookie'
|
||||
$cookieHeaderValue=""
|
||||
|
||||
# Clean up the Set-Cookie header
|
||||
$cookies = $cookieHeader.Split(";,")
|
||||
foreach($cookie in $cookies)
|
||||
{
|
||||
|
||||
$name = $cookie.Split("=")[0].trim()
|
||||
$value = $cookie.Substring($name.Length+1)
|
||||
|
||||
if($name -eq "rtFA" -or $name -eq "FedAuth" -or $name -eq "RpsContextCookie")
|
||||
{
|
||||
# If not empty, append the separator
|
||||
if(![String]::IsNullOrEmpty($cookieHeaderValue))
|
||||
{
|
||||
$cookieHeaderValue+="|"
|
||||
}
|
||||
|
||||
$cookieHeaderValue+="$name=$value"
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# Return
|
||||
return $cookieHeaderValue
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
# Creates a list from xml collection
|
||||
function Create-ListFromCollection
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user