Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of support for MSCommerce #174

Open
wants to merge 10 commits into
base: Dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Modules/MSCloudLoginAssistant/ConnectionProfile.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class MSCloudLoginConnectionProfile
# Workloads Object Creation
$this.ExchangeOnline = New-Object ExchangeOnline
$this.MicrosoftGraph = New-Object MicrosoftGraph
$this.MSCommerce = New-Object MSCommerce
$this.PnP = New-Object PnP
$this.PowerPlatform = New-Object PowerPlatform
$this.SecurityComplianceCenter = New-Object SecurityComplianceCenter
Expand Down Expand Up @@ -333,6 +334,27 @@ class MicrosoftGraph:Workload
}
}

class MSCommerce:Workload
{
[string]
$Scope = 'aeb86249-8ea3-49e2-900b-54cc8e308f85/.default'

MSCommerce()
{
}

[void] Connect()
{
([Workload]$this).Setup()

if ($null -ne $this.Credentials -and [System.String]::IsNullOrEmpty($this.TenantId))
{
$this.TenantId = $this.Credentials.Username.Split('@')[1]
}
Connect-MSCloudLoginMSCommerce
}
}

class PnP:Workload
{
[string]
Expand Down
3 changes: 2 additions & 1 deletion Modules/MSCloudLoginAssistant/MSCloudLoginAssistant.psd1
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
RootModule = 'MSCloudLoginAssistant.psm1'

# Version number of this module.
ModuleVersion = '1.1.16'
ModuleVersion = '1.1.17.1'

# Supported PSEditions
# CompatiblePSEditions = @()
Expand Down Expand Up @@ -70,6 +70,7 @@
'ConnectionProfile.psm1',
'Workloads\ExchangeOnline.psm1',
'Workloads\MicrosoftGraph.psm1',
'Workloads\MSCommerce.psm1',
'Workloads\Teams.psm1',
'Workloads\PnP.psm1',
'Workloads\PowerPlatform.psm1',
Expand Down
43 changes: 43 additions & 0 deletions Modules/MSCloudLoginAssistant/MSCloudLoginAssistant.psm1
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,16 @@ function Connect-M365Tenant
$Global:MSCloudLoginConnectionProfile.Teams.Identity = $Identity
$Global:MSCloudLoginConnectionProfile.Teams.Connect()
}
'MSCommerce'
{
$Global:MSCloudLoginConnectionProfile.MSCommerce.Credentials = $Credential
$Global:MSCloudLoginConnectionProfile.MSCommerce.ApplicationId = $ApplicationId
$Global:MSCloudLoginConnectionProfile.MSCommerce.TenantId = $TenantId
$Global:MSCloudLoginConnectionProfile.MSCommerce.CertificateThumbprint = $CertificateThumbprint
$Global:MSCloudLoginConnectionProfile.MSCommerce.ApplicationSecret = $ApplicationSecret
$Global:MSCloudLoginConnectionProfile.MSCommerce.AccessTokens = $AccessTokens
$Global:MSCloudLoginConnectionProfile.MSCommerce.Connect()
}
'PnP'
{
$Global:MSCloudLoginConnectionProfile.PnP.Credentials = $Credential
Expand Down Expand Up @@ -974,3 +984,36 @@ function Assert-IsNonInteractiveShell

return $true
}

function Get-JWTPayload
{
param(
[Parameter()]
[System.String]
$AccessToken
)

# kindly nicked from https://www.michev.info/blog/post/2140/decode-jwt-access-and-id-tokens-via-powershell

#Validate as per https://tools.ietf.org/html/rfc7519
#Access and ID tokens are fine, Refresh tokens will not work
if (-not $AccessToken.Contains(".") -or -not $AccessToken.StartsWith("eyJ"))
{
throw "Invalid token '$AccessToken' - it looks like a Refresh token"
}

#Payload
$tokenPayload = $AccessToken.Split(".")[1].Replace('-', '+').Replace('_', '/')
#Fix padding as needed, keep adding "=" until string length modulus 4 reaches 0
while ($tokenPayload.Length % 4) { Write-Verbose "Invalid length for a Base-64 char array or string, adding ="; $tokenPayload += "=" }
#Convert to Byte array
$tokenByteArray = [System.Convert]::FromBase64String($tokenPayload)
#Convert to string array
$tokenArray = [System.Text.Encoding]::ASCII.GetString($tokenByteArray)
Write-Verbose "Decoded array in JSON format:"
Write-Verbose $tokenArray
#Convert from JSON to PSObject
$tokobj = $tokenArray | ConvertFrom-Json

return $tokobj
}
210 changes: 210 additions & 0 deletions Modules/MSCloudLoginAssistant/Workloads/MSCommerce.psm1
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
function Connect-MSCloudLoginMSCommerce
{
[CmdletBinding()]
param()

$ProgressPreference = 'SilentlyContinue'
$WarningPreference = 'SilentlyContinue'
$VerbosePreference = 'SilentlyContinue'

# If the current profile is not the same we expect, make the switch.
if ($Global:MSCloudLoginConnectionProfile.MSCommerce.Connected)
{
if (($Global:MSCloudLoginConnectionProfile.MSCommerce.AuthenticationType -eq 'ServicePrincipalWithSecret' `
-or $Global:MSCloudLoginConnectionProfile.MSCommerce.AuthenticationType -eq 'Identity') `
-and (Get-Date -Date $Global:MSCloudLoginConnectionProfile.MSCommerce.ConnectedDateTime) -lt [System.DateTime]::Now.AddMinutes(-50))
{
Write-Verbose -Message 'Token is about to expire, renewing'

$Global:MSCloudLoginConnectionProfile.MSCommerce.Connected = $false
}
elseif ($null -eq (Get-MgContext))
{
$Global:MSCloudLoginConnectionProfile.MSCommerce.Connected = $false
}
else
{
return
}
}

$Global:MSCloudLoginConnectionProfile.MSCommerce.AccessTokens = @()
$azureCloudInstanceArg = @{}
if ($this.EnvironmentName)
{
$azureCloudInstanceArg.AzureCloudInstance = $this.EnvironmentName
}

Import-Module MSCommerce -Global
#Connect-MSCommerce not used, it provides token-acquisition as below but with next to no options.
# it is required to call the other MSCommerce-cmdlets/functions with an explicit token:
# $Global:MSCloudLoginConnectionProfile.MSCommerce.AccessTokens[0]

if ($Global:MSCloudLoginConnectionProfile.MSCommerce.AuthenticationType -eq 'CredentialsWithApplicationId' -or
$Global:MSCloudLoginConnectionProfile.MSCommerce.AuthenticationType -eq 'Credentials')
{
Write-Verbose -Message 'Will try connecting with user credentials'
Connect-MSCloudLoginMSCommerceWithUser
}
elseif ($Global:MSCloudLoginConnectionProfile.MSCommerce.AuthenticationType -eq 'CredentialsWithTenantId')
{
Write-Verbose -Message 'Will try connecting with user credentials and Tenant Id'
Connect-MSCloudLoginMSCommerceWithUser -TenantId $Global:MSCloudLoginConnectionProfile.MSCommerce.TenantId
}
elseif ($Global:MSCloudLoginConnectionProfile.MSCommerce.AuthenticationType -eq 'Identity')
{
Write-Verbose 'Connecting with managed identity'

$resourceEndpoint = ($Global:MSCloudLoginConnectionProfile.MSCommerce.ResourceUrl -split '/')[2]
if ('AzureAutomation/' -eq $env:AZUREPS_HOST_ENVIRONMENT)
{
$url = $env:IDENTITY_ENDPOINT
$headers = New-Object 'System.Collections.Generic.Dictionary[[String],[String]]'
$headers.Add('X-IDENTITY-HEADER', $env:IDENTITY_HEADER)
$headers.Add('Metadata', 'True')
$body = @{resource = "https://$resourceEndPoint/" }
$oauth2 = Invoke-RestMethod $url -Method 'POST' -Headers $headers -ContentType 'application/x-www-form-urlencoded' -Body $body
$accessToken = $oauth2.access_token
}
elseif('http://localhost:40342' -eq $env:IMDS_ENDPOINT)
{
#Get endpoint for Azure Arc Connected Device
$apiVersion = "2020-06-01"
$resource = "https://$resourceEndpoint"
$endpoint = "{0}?resource={1}&api-version={2}" -f $env:IDENTITY_ENDPOINT,$resource,$apiVersion
$secretFile = ""
try
{
Invoke-WebRequest -Method GET -Uri $endpoint -Headers @{Metadata='True'} -UseBasicParsing
}
catch
{
$wwwAuthHeader = $_.Exception.Response.Headers["WWW-Authenticate"]
if ($wwwAuthHeader -match "Basic realm=.+")
{
$secretFile = ($wwwAuthHeader -split "Basic realm=")[1]
}
}
$secret = Get-Content -Raw $secretFile
$response = Invoke-WebRequest -Method GET -Uri $endpoint -Headers @{Metadata='True'; Authorization="Basic $secret"} -UseBasicParsing
if ($response)
{
$accessToken = (ConvertFrom-Json -InputObject $response.Content).access_token
}
}
else
{
# Get correct endopint for AzureVM
$oauth2 = Invoke-RestMethod -Uri "http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2F$($resourceEndpoint)%2F" -Headers @{Metadata = 'true' }
$accessToken = $oauth2.access_token
}

#$accessToken = $accessToken | ConvertTo-SecureString -AsPlainText -Force

$Global:MSCloudLoginConnectionProfile.MSCommerce.ConnectedDateTime = [System.DateTime]::Now.ToString()
$Global:MSCloudLoginConnectionProfile.MSCommerce.MultiFactorAuthentication = $false
$Global:MSCloudLoginConnectionProfile.MSCommerce.Connected = $true
$Global:MSCloudLoginConnectionProfile.MSCommerce.AccessTokens += $accessToken
}
else
{
try
{
if ($Global:MSCloudLoginConnectionProfile.MSCommerce.AuthenticationType -eq 'ServicePrincipalWithThumbprint')
{
# Get certificate from CurrentUser or Localmachine
$cert = Get-ChildItem -Path "Cert:\*$($Global:MSCloudLoginConnectionProfile.MSCommerce.CertificateThumbprint)" -Recurse
$token = Get-MsalToken -ClientId $Global:MSCloudLoginConnectionProfile.ApplicationId `
-TenantId $Global:MSCloudLoginConnectionProfile.MSCommerce.TenantId `
-Certificate $cert `
-Scopes $Global:MSCloudLoginConnectionProfile.MSCommerce.Scope `
@azureCloudInstanceArg
$Global:MSCloudLoginConnectionProfile.MSCommerce.ConnectedDateTime = [System.DateTime]::Now.ToString()
$Global:MSCloudLoginConnectionProfile.MSCommerce.MultiFactorAuthentication = $false
$Global:MSCloudLoginConnectionProfile.MSCommerce.Connected = $true
$Global:MSCloudLoginConnectionProfile.MSCommerce.AccessTokens += $token.AccessToken
}
elseif($Global:MSCloudLoginConnectionProfile.MSCommerce.AuthenticationType -eq 'ServicePrincipalWithSecret')
{
Write-Verbose -Message 'Connecting to MSCommerce with ApplicationSecret'
$secStringPassword = ConvertTo-SecureString -String $Global:MSCloudLoginConnectionProfile.MSCommerce.ApplicationSecret -AsPlainText -Force
#$userName = $Global:MSCloudLoginConnectionProfile.MSCommerce.ApplicationId
#[pscredential]$credObject = New-Object System.Management.Automation.PSCredential ($userName, $secStringPassword)
$token = Get-MsalToken -ClientId $Global:MSCloudLoginConnectionProfile.ApplicationId ´
-TenantId $Global:MSCloudLoginConnectionProfile.MSCommerce.TenantId `
-ClientSecret $secStringPassword
-Scopes $Global:MSCloudLoginConnectionProfile.MSCommerce.Scope `
@azureCloudInstanceArg
$Global:MSCloudLoginConnectionProfile.MSCommerce.ConnectedDateTime = [System.DateTime]::Now.ToString()
$Global:MSCloudLoginConnectionProfile.MSCommerce.MultiFactorAuthentication = $false
$Global:MSCloudLoginConnectionProfile.MSCommerce.Connected = $true
$Global:MSCloudLoginConnectionProfile.MSCommerce.AccessTokens += $token.AccessToken
}
elseif($Global:MSCloudLoginConnectionProfile.MSCommerce.AuthenticationType -eq 'AccessToken')
{
Write-Verbose -Message 'Connecting to MSCommerce with AccessToken'
$Global:MSCloudLoginConnectionProfile.MSCommerce.ConnectedDateTime = [System.DateTime]::Now.ToString()
$Global:MSCloudLoginConnectionProfile.MSCommerce.MultiFactorAuthentication = $false
$Global:MSCloudLoginConnectionProfile.MSCommerce.Connected = $true
$Global:MSCloudLoginConnectionProfile.MSCommerce.TenantId = (Get-JWTPayload -AccessToken $Global:MSCloudLoginConnectionProfile.MSCommerce.AccessTokens[0]).tid
}
Write-Verbose -Message 'Connected'
}
catch
{
Write-Verbose -Message $_
throw $_
}
}
}

function Connect-MSCloudLoginMSCommerceWithUser
{
[CmdletBinding()]

<# initial attempt. Doesn't fly
$sessionState = $PSCmdlet.SessionState
$msCommerceToken = $sessionState.PSVariable.GetValue('token')
if ($null -ne $msCommerceToken)
{
# decode JWT to enable identifying authenticated user
$tokenPayload = Get-JWTPayload -AccessToken $msCommerceToken
}
if ($null -ne $msCommerceToken -and $Global:MSCloudLoginConnectionProfile.MSCommerce.Credentials.UserName -ne $tokenPayLoad.upn)
{
Write-Verbose -Message "The current account that is connected doesn't match the one we're trying to authenticate with."
}
#>

try
{
#Connect-MSCommerce # won't work - DSC-resource expects token to be stored in $Global:MSCloudLoginConnectionProfile.MSCommerce.AccessTokens[0]

$token = Get-MsalToken -ClientId '3d5cffa9-04da-4657-8cab-c7f074657cad' `
-UserCredential $Global:MSCloudLoginConnectionProfile.MSCommerce.Credentials `
-RedirectUri 'http://localhost/m365/commerce' `
-Scopes $Global:MSCloudLoginConnectionProfile.MSCommerce.Scope `
@azureCloudInstanceArg

$Global:MSCloudLoginConnectionProfile.MSCommerce.ConnectedDateTime = [System.DateTime]::Now.ToString()
$Global:MSCloudLoginConnectionProfile.MSCommerce.MultiFactorAuthentication = $true
$Global:MSCloudLoginConnectionProfile.MSCommerce.Connected = $true
$Global:MSCloudLoginConnectionProfile.MSCommerce.AccessTokens += $token.AccessToken
}
catch
{
if ($_.Exception -like 'System.Net.WebException: The remote server returned an error: (400) Bad Request.*' -and `
(Assert-IsNonInteractiveShell) -eq $true)
{
$warningPref = $WarningPreference
$WarningPreference = 'Continue'
Write-Warning -Message "Unable to retrieve AccessToken. Have you registered the 'M365 License Manager' application already? Please run 'Connect-MSCommerce' and logon using '$($Global:MSCloudLoginConnectionProfile.MSCommerce.Credentials.Username)'"
$WarningPreference = $warningPref
return
}
else
{
throw "Terminating error connecting to MSCommerce: $($_.Eception.Message)"
}
}
}
45 changes: 45 additions & 0 deletions er
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
error: wrong number of arguments, should be from 1 to 2
usage: git config [<options>]

Config file location
--global use global config file
--system use system config file
--local use repository config file
--worktree use per-worktree config file
-f, --file <file> use given config file
--blob <blob-id> read config from given blob object

Action
--get get value: name [value-pattern]
--get-all get all values: key [value-pattern]
--get-regexp get values for regexp: name-regex [value-pattern]
--get-urlmatch get value specific for the URL: section[.var] URL
--replace-all replace all matching variables: name value [value-pattern]
--add add a new variable: name value
--unset remove a variable: name [value-pattern]
--unset-all remove all matches: name [value-pattern]
--rename-section rename section: old-name new-name
--remove-section remove a section: name
-l, --list list all
--fixed-value use string equality when comparing values to 'value-pattern'
-e, --edit open an editor
--get-color find the color configured: slot [default]
--get-colorbool find the color setting: slot [stdout-is-tty]

Type
-t, --type <> value is given this type
--bool value is "true" or "false"
--int value is decimal number
--bool-or-int value is --bool or --int
--bool-or-str value is --bool or string
--path value is a path (file or directory name)
--expiry-date value is an expiry date

Other
-z, --null terminate values with NUL byte
--name-only show variable names only
--includes respect include directives on lookup
--show-origin show origin of config (file, standard input, blob, command line)
--show-scope show scope of config (worktree, local, global, system, command)
--default <value> with --get, use default value when missing entry