Skip to content

Commit a9ba1a3

Browse files
Merge branch 'main' into 2157-pim-batch-retry-throttling
2 parents 8c32d3f + b2406ae commit a9ba1a3

50 files changed

Lines changed: 10157 additions & 7648 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/pull_request_template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,6 @@ Uncomment this section if a screenshot is needed.
4545
- [ ] All relevant repo and/or project documentation updated to reflect these changes.
4646
- [ ] Unit tests added/updated to cover PowerShell and Rego changes.
4747
- [ ] Functional tests added/updated to cover PowerShell and Rego changes.
48-
- [ ] All relevant functional tests passed.
4948
- [ ] All automated checks (e.g., linting, static analysis, unit/smoke tests) passed.
5049

5150
## ✅ Pre-merge checklist ##
@@ -55,6 +54,7 @@ Uncomment this section if a screenshot is needed.
5554
<!-- approved. -->
5655

5756
- [ ] PR passed smoke test check.
57+
- [ ] PR/feature branch passes functional tests for relevant products, if applicable.
5858
- [ ] Feature branch has been rebased against changes from parent branch, as needed.
5959

6060
Use `Update branch` button below or use [this](https://www.digitalocean.com/community/tutorials/how-to-rebase-and-update-a-pull-request) reference to rebase from the command line.

PowerShell/ScubaGear/Modules/Connection/Connection.psm1

Lines changed: 152 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
Import-Module -Name $PSScriptRoot/../Providers/ProviderHelpers/PowerPlatformRestHelper.psm1 -Function Get-PowerPlatformBaseUrl, Get-PowerPlatformScope
3838
Import-Module -Name $PSScriptRoot/../Providers/ProviderHelpers/SPORestHelper.psm1 -Function Get-SPOAdminUrl
3939
Import-Module -Name $PSScriptRoot/../Providers/ProviderHelpers/PowerBIRestHelper.psm1 -Function Get-PowerBIBaseUrl, Get-PowerBIScope
40+
Import-Module -Name $PSScriptRoot/../Providers/ProviderHelpers/EXORestHelper.psm1 -Function Get-ExchangeOnlineScope, Get-ExchangeOnlineApiEndpoint
4041

4142
# Prevent duplicate sign ins
4243
$EXOAuthRequired = $true
@@ -55,12 +56,14 @@
5556

5657
# Token data for REST-based products (populated during connection)
5758
$TokenData = @{
58-
SPOAccessToken = $null
59-
SPOAdminUrl = $null
60-
PPAccessToken = $null
61-
PPBaseUrl = $null
62-
PBIAccessToken = $null
63-
PBIBaseUrl = $null
59+
SPOAccessToken = $null
60+
SPOAdminUrl = $null
61+
PPAccessToken = $null
62+
PPBaseUrl = $null
63+
PBIAccessToken = $null
64+
PBIBaseUrl = $null
65+
EXOAccessToken = $null
66+
EXOApiEndpoint = $null
6467
}
6568

6669
$N = 0
@@ -95,14 +98,66 @@
9598
}
9699
{($_ -eq "exo") -or ($_ -eq "securitysuite")} {
97100
if ($EXOAuthRequired) {
98-
$EXOHelperParams = @{
99-
M365Environment = $M365Environment;
101+
if ($AADAuthRequired) {
102+
$LimitedGraphParams = @{
103+
'M365Environment' = $M365Environment;
104+
'ErrorAction' = 'Stop';
105+
}
106+
if ($ServicePrincipalParams) {
107+
$LimitedGraphParams += @{ServicePrincipalParams = $ServicePrincipalParams}
108+
}
109+
Connect-GraphHelper @LimitedGraphParams
110+
$AADAuthRequired = $false
100111
}
101-
if ($ServicePrincipalParams) {
102-
$EXOHelperParams += @{ServicePrincipalParams = $ServicePrincipalParams}
112+
113+
# Resolve tenant info if not already cached
114+
if ([string]::IsNullOrEmpty($TenantName)) {
115+
$OrgDetails = (Invoke-GraphDirectly -Commandlet Get-MgBetaOrganization -M365Environment $M365Environment).Value
116+
$InitialDomain = $OrgDetails.VerifiedDomains | Where-Object { $_.isInitial }
117+
$TenantName = $InitialDomain.Name
118+
$InitialDomainPrefix = $TenantName.split(".")[0]
119+
}
120+
121+
# Acquire Exchange Online access token
122+
$EXOScope = Get-ExchangeOnlineScope -M365Environment $M365Environment
123+
124+
if ($ServicePrincipalParams.CertThumbprintParams) {
125+
$TokenData.EXOAccessToken = Get-MsalAccessToken `
126+
-Scope $EXOScope `
127+
-CertificateThumbprint $ServicePrincipalParams.CertThumbprintParams.CertificateThumbprint `
128+
-AppID $ServicePrincipalParams.CertThumbprintParams.AppID `
129+
-Tenant $ServicePrincipalParams.CertThumbprintParams.Organization `
130+
-M365Environment $M365Environment
131+
}
132+
else {
133+
# Microsoft Exchange Online Remote PowerShell well-known client ID
134+
$EXOClientId = "fb78d390-0c51-40cd-8e17-fdbfab77341b"
135+
$TokenData.EXOAccessToken = Get-MsalAccessToken `
136+
-Scope $EXOScope `
137+
-ClientId $EXOClientId `
138+
-Tenant $TenantName `
139+
-M365Environment $M365Environment
140+
}
141+
142+
# Resolve the EXO API endpoint (handles redirects)
143+
$TenantId = (Invoke-GraphDirectly -Commandlet Get-MgBetaOrganization -M365Environment $M365Environment).Value.Id
144+
$TokenData.EXOApiEndpoint = Get-ExchangeOnlineApiEndpoint `
145+
-TenantId $TenantId `
146+
-TenantDomain $TenantName `
147+
-M365Environment $M365Environment `
148+
-AccessToken $TokenData.EXOAccessToken
149+
150+
# EXO product checks use REST; only establish an EXO module session
151+
# when Security Suite is part of the requested product list.
152+
if ($ProductNames -contains "securitysuite") {
153+
$EXOHelperParams = @{ M365Environment = $M365Environment }
154+
if ($ServicePrincipalParams.CertThumbprintParams) {
155+
$EXOHelperParams += @{ ServicePrincipalParams = $ServicePrincipalParams }
156+
}
157+
Connect-EXOHelper @EXOHelperParams
103158
}
104-
Write-Verbose "For the Security Suite baseline, Defender will require a sign in every single run regardless of what the LogIn parameter is set"
105-
Connect-EXOHelper @EXOHelperParams
159+
160+
Write-Verbose "Exchange Online token and endpoint acquired successfully"
106161
$EXOAuthRequired = $false
107162
}
108163
}
@@ -348,6 +403,8 @@
348403
PPBaseUrl = $TokenData.PPBaseUrl
349404
PBIAccessToken = $TokenData.PBIAccessToken
350405
PBIBaseUrl = $TokenData.PBIBaseUrl
406+
EXOAccessToken = $TokenData.EXOAccessToken
407+
EXOApiEndpoint = $TokenData.EXOApiEndpoint
351408
}
352409
}
353410

@@ -408,7 +465,8 @@ function Disconnect-SCuBATenant {
408465
if($Product -eq "securitysuite") {
409466
Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
410467
}
411-
Disconnect-ExchangeOnline -Confirm:$false -ErrorAction SilentlyContinue -InformationAction SilentlyContinue | Out-Null
468+
# EXO now uses REST API with on-demand token - no persistent connection to disconnect
469+
Disconnect-MgGraph -ErrorAction SilentlyContinue | Out-Null
412470
}
413471
else {
414472
Write-Warning "Product $Product not recognized, skipping..."
@@ -427,7 +485,86 @@ function Disconnect-SCuBATenant {
427485

428486
}
429487

488+
function Get-ServicePrincipalParams {
489+
<#
490+
.Description
491+
Returns a valid a hastable of parameters for authentication via
492+
Service Principal. Throws an error if there are none.
493+
.Functionality
494+
Internal
495+
#>
496+
[CmdletBinding()]
497+
param(
498+
[Parameter(Mandatory=$true)]
499+
[ValidateNotNullOrEmpty()]
500+
[object]
501+
$ScubaConfig
502+
)
503+
504+
$ServicePrincipalParams = @{}
505+
506+
$CheckThumbprintParams = ($ScubaConfig.CertificateThumbprint) `
507+
-and ($ScubaConfig.AppID) -and ($ScubaConfig.Organization)
508+
509+
if ($CheckThumbprintParams) {
510+
$CertThumbprintParams = @{
511+
CertificateThumbprint = $ScubaConfig.CertificateThumbprint;
512+
AppID = $ScubaConfig.AppID;
513+
Organization = $ScubaConfig.Organization;
514+
}
515+
$ServicePrincipalParams += @{CertThumbprintParams = $CertThumbprintParams}
516+
}
517+
else {
518+
throw "When authenticating with Service Principal authentication, the following command line parameters must be provided: -AppID, -CertificateThumbprint and -Organization."
519+
}
520+
$ServicePrincipalParams
521+
}
522+
523+
function Get-M365EnvironmentByDomain {
524+
<#
525+
.SYNOPSIS
526+
Determines the M365 environment based on the tenant domain.
527+
528+
.DESCRIPTION
529+
Determines the M365 environment based on the tenant domain.
530+
531+
.PARAMETER TenantDomain
532+
The domain of the tenant for which to determine the environment.
533+
534+
.EXAMPLE
535+
$M365Environment = Get-M365EnvironmentByDomain -TenantDomain "contoso.onmicrosoft.com"
536+
537+
.FUNCTIONALITY
538+
Internal
539+
#>
540+
[CmdletBinding(DefaultParameterSetName = 'Interactive')]
541+
param(
542+
[Parameter(Mandatory = $true)]
543+
[string]$TenantDomain
544+
)
545+
546+
$MetadataUri = "https://login.microsoftonline.com/$TenantDomain/.well-known/openid-configuration"
547+
548+
$Metadata = Invoke-RestMethod -Uri $MetadataUri -Method Get -ErrorAction Stop
549+
550+
$TenantRegionSubScope = $Metadata.tenant_region_sub_scope
551+
552+
$M365Environment = switch ($TenantRegionSubScope) {
553+
"DODCON" { "gcchigh" }
554+
"GCC" { "gcc" }
555+
"DOD" { "dod" }
556+
$null { "commercial" }
557+
default {
558+
throw "Unknown tenant_region_sub_scope value: '$TenantRegionSubScope'"
559+
}
560+
}
561+
562+
return $M365Environment
563+
}
564+
430565
Export-ModuleMember -Function @(
431-
'Connect-Tenant',
432-
'Disconnect-SCuBATenant'
566+
'Connect-Tenant',
567+
'Disconnect-SCuBATenant',
568+
'Get-ServicePrincipalParams',
569+
'Get-M365EnvironmentByDomain'
433570
)

0 commit comments

Comments
 (0)