This commit is contained in:
Nestori
2025-04-18 14:41:13 +03:00
parent 1967bc5dce
commit ff0457aa95
12 changed files with 23305 additions and 822 deletions
+2 -5
View File
@@ -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
View File
File diff suppressed because it is too large Load Diff
+140 -322
View File
@@ -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
View File
@@ -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.
+297
View File
@@ -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>
+396 -72
View File
@@ -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
View File
@@ -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
}
}
+39 -108
View File
@@ -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
View File
@@ -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
{