Modules/Providers/ExportPowerPlatformProvider.psm1

Import-Module -Name $PSScriptRoot/../Utility/Utility.psm1 -Function Invoke-GraphDirectly, ConvertFrom-GraphHashtable

function Export-PowerPlatformProvider {
    <#
    .Description
    Gets the Power Platform settings that are relevant
    to the CyberAssessment Power Platform baselines using the Power Platform Administartion
    PowerShell Module
    .Functionality
    Internal
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateSet("commercial", "gcc", "gcchigh", "dod", IgnoreCase = $false)]
        [ValidateNotNullOrEmpty()]
        [string]
        $M365Environment
    )

    $HelperFolderPath = Join-Path -Path $PSScriptRoot -ChildPath "ProviderHelpers"
    Import-Module (Join-Path -Path $HelperFolderPath -ChildPath "CommandTracker.psm1")
    $Tracker = Get-CommandTracker

    # Manually importing the module name here to bypass cmdlet name conflicts
    # There are conflicting PowerShell Cmdlet names in EXO and Power Platform
    Import-Module Microsoft.PowerApps.Administration.PowerShell -DisableNameChecking

    $TenantDetails = $Tracker.TryCommand("Get-MgBetaOrganization", @{"M365Environment"=$M365Environment; "GraphDirect"=$true})
    $TenantId = if ($TenantDetails.Id) { $TenantDetails.Id } else { "" }

    $DomainInfo = Get-TenantDomainInfo -TenantDetails $TenantDetails -M365Environment $M365Environment
    Test-M365EnvironmentConfiguration -TenantDomain $DomainInfo.TenantDomain -TLD $DomainInfo.TLD -M365Environment $M365Environment

    # MS.POWERPLATFORM.1.1v1, MS.POWERPLATFORM.1.2v1, MS.POWERPLATFORM.5.1v1, MS.POWERPLATFORM.6.1v1
    $EnvironmentCreation = ConvertTo-Json -Depth 4 @($Tracker.TryCommand("Get-TenantSettings"))

    # MS.POWERPLATFORM.2.1v1, MS.POWERPLATFORM.2.2v1, MS.POWERPLATFORM.2.3v1
    $EnvironmentList = ConvertTo-Json @($Tracker.TryCommand("Get-AdminPowerAppEnvironment"))

    # Check for null return
    if (-not $EnvironmentList) {
        $EnvironmentList = ConvertTo-Json @()
        $Tracker.AddUnSuccessfulCommand("Get-AdminPowerAppEnvironment")
    }

    # has to be tested manually because of http 403 errors
    $DLPPolicies = ConvertTo-Json @()
    try {
        $DLPPolicies = Get-DlpPolicy -ErrorAction "Stop"
        if ($DLPPolicies.StatusCode) {
            $Tracker.AddUnSuccessfulCommand("Get-DlpPolicy")
            $StatusCode = $DLPPolicies.StatusCode
            $Message = $DLPPolicies.Message
            $DLPPolicies = ConvertTo-Json @()
            throw "$($Message) HTTP $($StatusCode) ERROR"
        }
        else {
            $DLPPolicies = ConvertTo-Json -Depth 7 @($DLPPolicies)
            $Tracker.AddSuccessfulCommand("Get-DlpPolicy")
        }
    }
    catch {
        Write-Warning "Error running Get-DlpPolicy: $($_). <= If a HTTP 403 ERROR is thrown then this is because you do not have the proper permissions. Necessary roles for running CyberAssessment with Power Platform: Power Platform Administrator with a Power Apps License or Global Admininstrator"
    }

    # MS.POWERPLATFORM.3.1v1
    # has to be tested manually because of http 403 errors
    $TenantIsolation = ConvertTo-Json @()
    try {
        $TenantIso = Get-PowerAppTenantIsolationPolicy -TenantID $TenantID -ErrorAction "Stop"
        if ($TenantIso.StatusCode) {
            $Tracker.AddUnSuccessfulCommand("Get-PowerAppTenantIsolationPolicy")
            $TenantIsolation = ConvertTo-Json @()
            $StatusCode = $DLPPolicies.StatusCode
            $Message = $DLPPolicies.Message
            throw "$($Message) HTTP $($StatusCode) ERROR"
        }
        else {
            $Tracker.AddSuccessfulCommand("Get-PowerAppTenantIsolationPolicy")
            $TenantIsolation = ConvertTo-Json @($TenantIso)
        }
    }
    catch {
        Write-Warning "Error running Get-PowerAppTenantIsolationPolicy: $($_). <= If a HTTP 403 ERROR is thrown then this is because you do not have the proper permissions. Necessary roles for running CyberAssessment with Power Platform: Power Platform Administrator with a Power Apps License or Global Admininstrator"
    }

    # MS.POWERPLATFORM.3.2v1 currently has no corresponding PowerShell Cmdlet

    # MS.POWERPLATFORM.4.1v1 currently has no corresponding PowerShell Cmdlet

    $PowerPlatformSuccessfulCommands = ConvertTo-Json @($Tracker.GetSuccessfulCommands())
    $PowerPlatformUnSuccessfulCommands = ConvertTo-Json @($Tracker.GetUnSuccessfulCommands())

    # tenant_id added for testing purposes
    # Note the spacing and the last comma in the json is important
    $json = @"
    "tenant_id": "$TenantID",
    "environment_creation": $EnvironmentCreation,
    "dlp_policies": $DLPPolicies,
    "tenant_isolation": $TenantIsolation,
    "environment_list": $EnvironmentList,
    "powerplatform_successful_commands": $PowerPlatformSuccessfulCommands,
    "powerplatform_unsuccessful_commands": $PowerPlatformUnSuccessfulCommands,
"@


    $json = $json -replace "[^\x00-\x7f]","" # remove all characters that are not utf-8
    $json
}

function Get-TenantDomainInfo {
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [object]$TenantDetails,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $M365Environment
    )

    $TenantDomain = "Unretrievable"
    $TLD = ".com"
    if (($M365Environment -eq "gcchigh") -or ($M365Environment -eq "dod")) {
        $TLD = ".us"
    }

    foreach ($Domain in $TenantDetails.VerifiedDomains) {
        $Name = $Domain.Name
        $IsInitial = $Domain.IsInitial
        $DomainChecker = $Name.EndsWith(".onmicrosoft$($TLD)") -and !$Name.EndsWith(".mail.onmicrosoft$($TLD)") -and $IsInitial
        if ($DomainChecker){
            $TenantDomain = $Name
        }
    }

    return @{
        TenantDomain = $TenantDomain;
        TLD = $TLD;
    }
}

function Test-M365EnvironmentConfiguration {
    param (
        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$TenantDomain,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]$TLD,

        [Parameter(Mandatory = $true)]
        [ValidateNotNullOrEmpty()]
        [string]
        $M365Environment
    )

    $TenantIdConfig = ""
    try {
        $Uri = "https://login.microsoftonline$($TLD)/$($TenantDomain)/.well-known/openid-configuration"
        $TenantIdConfig = (Invoke-WebRequest -Uri $Uri -UseBasicParsing -ErrorAction "Stop").Content
    }
    catch {
        $EnvCheckWarning = @"
    Power Platform Provider Warning: $($_). Unable to check if M365Environment is set correctly in the Power Platform Provider. This MAY impact the output of the Power Platform Baseline report.
    See https://github.com/cisagov/CyberAssessment/blob/main/docs/troubleshooting/proxy.md for a possible solution to this warning.
"@

        Write-Warning $EnvCheckWarning
    }

    # Commercial: "tenant_region_scope":"NA"
    # GCC: "tenant_region_scope":"NA","tenant_region_sub_scope":"GCC",
    # GCCHigh: "tenant_region_scope":"USGov","tenant_region_sub_scope":"DODCON"
    # DoD: "tenant_region_scope":"USGov","tenant_region_sub_scope":"DOD"
    try {
        if ($TenantIdConfig -ne "") {
            $TenantIdConfigJson = ConvertFrom-Json $TenantIdConfig
            $RegionScope = $TenantIdConfigJson.tenant_region_scope
            $RegionSubScope = $TenantIdConfigJson.tenant_region_sub_scope
            if (-not $RegionSubScope) {
                $RegionSubScope = ""
            }

            $CheckRScope = $true
            $CheckRSubScope = $true
            if ($RegionScope -eq "NA" -or $RegionScope -eq "USGov" -or $RegionScope -eq "USG") {
                switch ($M365Environment) {
                    "commercial" {
                        $CheckRScope = $RegionScope -eq "NA"
                        $CheckRSubScope = $RegionSubScope -eq ""
                    }
                    "gcc" {
                        $CheckRScope = $RegionScope -eq "NA"
                        $CheckRSubScope = $RegionSubScope -eq "GCC"
                    }
                    "gcchigh" {
                        $CheckRScope = $RegionScope -eq "USGov" -or $RegionScope -eq "USG"
                        $CheckRSubScope = $RegionSubScope -eq "DODCON"
                    }
                    "dod" {
                        $CheckRScope = $RegionScope -eq "USGov" -or $RegionScope -eq "USG"
                        $CheckRSubScope = $RegionSubScope -eq "DOD"
                    }
                    default {
                        throw "Unsupported or invalid M365Environment argument"
                    }
                }
            }

            # spacing is intentional
            $EnvErrorMessage = @"
"Power Platform Provider ERROR: The M365Environment parameter value is not set correctly which WILL cause the Power Platform report to display incorrect values.
            ---------------------------------------
            M365Environment Parameter value: $($M365Environment)
            Your tenant's OpenId-Configuration: tenant_region_scope: $($RegionScope), tenant_region_sub_scope: $($RegionSubScope)
"@


        if (-not ($CheckRScope -and $CheckRSubScope)) {
                throw $EnvErrorMessage
            }
        }
    }
    catch {

        $FullEnvErrorMessage = @"
$($_)
        ---------------------------------------
        Rerun CyberAssessment with the correct M365Environment parameter value
        by looking at your tenant's OpenId-Configuration displayed above and
        contrast it with the mapped values in the table below
        M365Enviroment => OpenId-Configuration
        ---------------------------------------
        commercial: tenant_region_scope:NA, tenant_region_sub_scope:
        gcc: tenant_region_scope:NA, tenant_region_sub_scope: GCC
        gcchigh : tenant_region_scope:USGov, tenant_region_sub_scope: DODCON
        dod: tenant_region_scope:USGov, tenant_region_sub_scope: DOD
        ---------------------------------------
        Example Rerun for gcc tenants: Invoke-Cyber -M365Environment gcc
"@

        throw $FullEnvErrorMessage
    }
}

function Get-PowerPlatformTenantDetail {
    <#
    .Description
    Gets the M365 tenant details using the Power Platform PowerShell Module
    .Functionality
    Internal
    #>

    [CmdletBinding()]
    param (
        [Parameter(Mandatory = $true)]
        [ValidateSet("commercial", "gcc", "gcchigh", "dod", IgnoreCase = $false)]
        [ValidateNotNullOrEmpty()]
        [string]
        $M365Environment
    )
    Import-Module Microsoft.PowerApps.Administration.PowerShell -DisableNameChecking

    try {
        $TenantDetails = (Invoke-GraphDirectly -Commandlet "Get-MgBetaOrganization" -M365Environment $M365Environment).Value
        $DomainInfo = Get-TenantDomainInfo -TenantDetails $TenantDetails -M365Environment $M365Environment

        $PowerTenantInfo = @{
            "DisplayName" = $TenantDetails.DisplayName;
            "DomainName" = $DomainInfo.TenantDomain;
            "TenantId" = $TenantDetails.TenantId;
            "PowerPlatformAdditionalData" = $TenantDetails;
        }
        $PowerTenantInfo = ConvertTo-Json @($PowerTenantInfo) -Depth 4
        $PowerTenantInfo
    }
    catch {
        Write-Warning "Error retrieving Tenant details using Get-PowerPlatformTenantDetail: $($_.Exception.Message)`n$($_.ScriptStackTrace)"
        $PowerTenantInfo = @{
            "DisplayName" = "Error retrieving Display name";
            "DomainName" = "Error retrieving Domain name";
            "TenantId" = "Error retrieving Tenant ID";
            "PowerPlatformAdditionalData" = "Error retrieving additional data";
        }
        $PowerTenantInfo = ConvertTo-Json @($PowerTenantInfo) -Depth 4
        $PowerTenantInfo
    }
}