Private/_Auth.ps1

function Create-PACAuth($ServerUrl) {

    if ($ServerUrl -eq $null) {
        $ServerUrl = $global:devops_ServerUrl
    }

    if ($global:devops_DataverseCredType -eq "servicePrincipal") {
        $pdel = & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe auth delete --name ppdo
        # Added escaped quotes required to get secrets working that started with - (https://github.com/dylanhaskins/Capgemini.PowerPlatform.DevOps/issues/150)
        & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe auth create --applicationId $global:devops_ClientID --clientSecret `"$global:clientSecret`" --environment $ServerUrl --tenant $global:devops_TenantID  --name ppdo
        #--name $global:devops_SolutionName
        # Select ppdo connection to use (user may have more than one pac auth saved)
        & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe auth select --name ppdo
    }
    if ($global:devops_DataverseCredType -eq "user") {
        $pdel = & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe auth delete --name ppdo
        & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe auth create --username $global:devops_DataverseEmail --password $global:Password --environment $ServerUrl --name ppdo
        #--name $global:devops_SolutionName
        # Select ppdo connection to use (user may have more than one pac auth saved)
        & $env:APPDATA\Capgemini.PowerPlatform.DevOps\PACTools\tools\pac.exe auth select --name ppdo
    }
}

function Get-AccessToken {
    #Get Access Token
    Write-Host "Getting Updated Access Token"
    $azureDevopsResourceId = "499b84ac-1321-427f-aa17-267ca6975798"
    $token = az account get-access-token --resource $azureDevopsResourceId | ConvertFrom-Json
    $authValue = [Convert]::ToBase64String([System.Text.Encoding]::UTF8.GetBytes(":" + $token.accessToken))    
    $env:AZURE_DEVOPS_EXT_PAT = $token.accessToken
   
    # $REPO_URL = git remote get-url origin
    # Write-Host "git URL is $REPO_URL"
   
    # git config http.$REPO_URL.extraHeader "Authorization:Basic $authValue"
    # Write-Host "Updated Auth Token"
}

function Get-AzureAccounts($reason) {
    try {
        $sel = $null
        $AZAccounts = az account list | ConvertFrom-Json
        [array]$AZoptions = "Login to a New Account"
        $AZoptions += $AZAccounts | ForEach-Object { "$($_.name) $($_.user.name) ($($_.tenantId))" }

        do {
            $sel = Invoke-Menu -MenuTitle "---- Please Select your Subscription ($reason) ------" -MenuOptions $AZoptions          
        } until ($sel -ge 0)
        if ($sel -eq 0) {
            if ($global:devops_isDocker) {
                az login --use-device-code --allow-no-subscriptions    
            }
            else {
                az config set core.allow_broker=true
                az login --allow-no-subscriptions
            }
            
            #Get Azure Accounts / Subscriptions
            $AZAccounts = az account list | ConvertFrom-Json
            Get-AzureAccounts($reason)
        }
        else {
            $global:devops_selectedSubscription = $AZAccounts[$sel - 1].id
            $global:devops_selectedSubscriptionName = $AZAccounts[$sel - 1].name
            az account set --subscription $global:devops_selectedSubscription
        }   
    }
    catch {
        Write-Error $_
        pause
    }
}

function Get-AzureLogins($reason) {
    [array]$options = "Login to a New Account"
    $AZCredentials = az account list --query '[].{Name:user.name, Type:user.type, ID:id, Subscription:name, TenantID:tenantId}' --output json | ConvertFrom-Json
    $UniqueCredentials = $AZCredentials | Sort-Object -Property * -Unique 
    $options += $UniqueCredentials | ForEach-Object { "$($_.Name) ($($_.Type))($($_.Subscription))($($_.TenantID))" }

    do {
        $sel = Invoke-Menu -MenuTitle "---- Please Select your Subscription ($reason) ------" -MenuOptions $options   
    } until ($sel -ge 0)
    if ($sel -eq 0) {
        if ($global:devops_isDocker) {
            az login --use-device-code --allow-no-subscriptions    
        }
        else {
            az login --allow-no-subscriptions
        }
        Get-AzureLogins($reason)
    }
    else {
        $global:devops_DataverseEmail = $UniqueCredentials[$sel - 1].Name
        $global:devops_DataverseADSubscription = $UniqueCredentials[$sel - 1].id
        $global:devops_DataverseCredType = $UniqueCredentials[$sel - 1].Type
        $global:devops_HasDataverseLogin = $true  
    }
}

function Get-DataverseLogin {
    Param(
        [bool] [Parameter(Mandatory = $false)] $overrideSP = $false,
        [bool] [Parameter(Mandatory = $false)] $requiresManagementApi = $true
    )
    if (!$global:devops_HasDataverseLogin -or ($global:devops_projectFile.OverrideSPCheck -eq "True")) {
        $message = "Connecting to Dataverse Environment"
        Write-Host $message
    
        Write-Host ""

        [System.Net.ServicePointManager]::SecurityProtocol = [System.Net.SecurityProtocolType]::Tls12    
        if (($global:devops_ClientID -and $overrideSP -eq $false) -and !($global:devops_projectFile.OverrideSPCheck -eq "True" -and $requiresManagementApi -eq $true)) {
            $global:devops_DataverseCredType = "servicePrincipal"
        }
        else {
            Get-AzureLogins("Power Platform")
        }   

        if ($global:devops_DataverseCredType -eq "user") {
            [String]$userString = $global:devops_DataverseEmail
            Authenticate-PowerPlatform($userString, "https://management.azure.com/")  
            Authenticate-PowerPlatform($userString, "https://service.powerapps.com/")  
        }
        else {
            Write-Host "Using Service Principal"
            if ($global:devops_projectFile.ClientSecretAKVName) {
                Write-Host "Retrieving Secret from KeyVault"
                az account set --subscription $global:devops_projectFile.selectedSubscription

                $KVRetrieve = az keyvault secret show --name $global:devops_projectFile.ClientSecretAKVName --vault-name $global:devops_projectFile.AzureKeyVaultName | ConvertFrom-Json
                $global:clientSecret = $KVRetrieve.value   
                Write-Host "Secret Retrieved"         
            }
            else {       
                if (!$global:clientSecret) {
                    Write-Host "Retrieving Secret from Local Store"
                    $clientKeySS = ($global:devops_configFile.Projects[$global:devops_projectConfigID].ClientSecret) | ConvertTo-SecureString
                    $global:clientSecret = (New-Object PSCredential "user", $clientKeySS).GetNetworkCredential().Password
                    Write-Host "Secret Retrieved" 
                }     
                else { Write-Host "Secret Provided by Command Line/Cache" }
            }
            
            try {
                Add-PowerAppsAccount -ApplicationId $global:devops_ClientID -ClientSecret "$global:clientSecret" -TenantID $global:devops_TenantID 
                $ManagementApps = Get-PowerAppManagementApps 
                $SPAdminPortalAccess = $ManagementApps.value | Where-Object applicationId -eq $global:devops_ClientID
                $SPAdminPortalAccess = $SPAdminPortalAccess.applicationId
                Write-Host "Service Principal Check Override : $($global:devops_projectFile.OverrideSPCheck)"
                if (!$SPAdminPortalAccess -and ($global:devops_projectFile.OverrideSPCheck -eq "False")) {
                    $EnableAccess = Read-Host -Prompt "Service Principal $global:devops_ClientID does not have permissions to administer Power Platform, would you like to [e]nable access, login as another [u]ser or [c]ontinue without Power Platform admin (some actions will not be available)? [e/u/c]"
                    if ($EnableAccess.ToLower() -eq 'e') {
                        Write-Host "Please Login to Power Platform with a User who has access to Administer Environments"
                        $global:currentSession.loggedIn = $false
                        $tempClientID = $global:devops_ClientID
                        $global:devops_ClientID = $null
                        Get-DataverseLogin
                        $global:devops_ClientID = $tempClientID
                        $global:devops_DataverseCredType = "servicePrincipal"
                        $SPAdminPortalAccess = New-PowerAppManagementApp -ApplicationId $global:devops_ClientID
                        if ($SPAdminPortalAccess.StatusCode -eq "403") {
                            Throw $SPAdminPortalAccess
                        }
                        $global:currentSession.loggedIn = $false
                        Add-PowerAppsAccount -ApplicationId $global:devops_ClientID -ClientSecret "$global:clientSecret" -TenantID $global:devops_TenantID    
                        $global:devops_HasDataverseLogin = $true
                        Write-Host "Connected to Dataverse"
                    }
                    elseif ($EnableAccess.ToLower() -eq 'u') {
                        Get-DataverseLogin -overrideSP $true
                    }
                    elseif ($EnableAccess.ToLower() -eq 'c') {
                        $global:devops_HasDataverseLogin = $true
                        Write-Host "Connected to Dataverse"
                    }
                    else {
                        Throw $SPAdminPortalAccess
                    }
                }
                else {
                    $global:devops_HasDataverseLogin = $true
                    Write-Host "Connected to Dataverse"
                }
            }
            catch {
                $global:devops_HasDataverseLogin = $false
                Write-Host $_
                pause
            }
        }
    }
}

function Authenticate-PowerPlatform() {
    [CmdletBinding()]
    Param(
        [Parameter(Mandatory = $True)]$args       
    )
    $theUser = $args[0]
    if ($args[1]) {
        $theAudience = $args[1]        
    }
    else {
        $theAudience = "https://management.azure.com/"
    }
    $authBaseUri =
    switch ($Endpoint) {
        "usgovhigh" { "https://login.microsoftonline.us" }
        "dod" { "https://login.microsoftonline.us" }
        default { "https://login.windows.net" }
    };

    [string]$Endpoint = "prod"

    [string]$Audience = $theAudience
    
    [string]$TenantID = $null
    [string]$CertificateThumbprint = $null
    [string]$ClientSecret = $null

    $getToken = az account get-access-token --resource $Audience -s $global:devops_DataVerseADSubscription
    if (!$getToken) {
        Write-Host "Re-Authenticating $theUser"
        if ($global:devops_isDocker) {
            az login --use-device-code --allow-no-subscriptions    
        }
        else {
            az login --allow-no-subscriptions
        }
        Authenticate-PowerPlatform($theUser, $Audience)
    }

    $authResult = $getToken | ConvertFrom-Json

    $claims = Get-JwtTokenClaimsForPA -JwtToken $authResult.AccessToken

    $global:currentSession = @{
        loggedIn              = $true;
        idToken               = $authResult.IdToken;
        upn                   = $claims.upn;
        tenantId              = $claims.tid;
        userId                = $claims.oid;
        applicationId         = $claims.appid;
        certificateThumbprint = $CertificateThumbprint;
        clientSecret          = $ClientSecret;
        secureClientSecret    = $SecureClientSecret;
        refreshToken          = $authResult.RefreshToken;
        expiresOn             = (Get-Date).AddHours(8);
        resourceTokens        = @{
            $Audience = @{
                accessToken = $authResult.AccessToken;
                expiresOn   = $authResult.ExpiresOn;
            }
        };
        selectedEnvironment   = "~default";
        authBaseUri           = $authBaseUri;
        flowEndpoint          = 
        switch ($Endpoint) {
            "prod" { "api.flow.microsoft.com" }
            "usgov" { "gov.api.flow.microsoft.us" }
            "usgovhigh" { "high.api.flow.microsoft.us" }
            "dod" { "api.flow.appsplatform.us" }
            "china" { "api.powerautomate.cn" }
            "preview" { "preview.api.flow.microsoft.com" }
            "tip1" { "tip1.api.flow.microsoft.com" }
            "tip2" { "tip2.api.flow.microsoft.com" }
            default { throw "Unsupported endpoint '$Endpoint'" }
        };
        powerAppsEndpoint     = 
        switch ($Endpoint) {
            "prod" { "api.powerapps.com" }
            "usgov" { "gov.api.powerapps.us" }
            "usgovhigh" { "high.api.powerapps.us" }
            "dod" { "api.apps.appsplatform.us" }
            "china" { "api.powerapps.cn" }
            "preview" { "preview.api.powerapps.com" }
            "tip1" { "tip1.api.powerapps.com" }
            "tip2" { "tip2.api.powerapps.com" }
            default { throw "Unsupported endpoint '$Endpoint'" }
        };            
        bapEndpoint           = 
        switch ($Endpoint) {
            "prod" { "api.bap.microsoft.com" }
            "usgov" { "gov.api.bap.microsoft.us" }
            "usgovhigh" { "high.api.bap.microsoft.us" }
            "dod" { "api.bap.appsplatform.us" }
            "china" { "api.bap.partner.microsoftonline.cn" }
            "preview" { "preview.api.bap.microsoft.com" }
            "tip1" { "tip1.api.bap.microsoft.com" }
            "tip2" { "tip2.api.bap.microsoft.com" }
            default { throw "Unsupported endpoint '$Endpoint'" }
        };      
        graphEndpoint         = 
        switch ($Endpoint) {
            "prod" { "graph.windows.net" }
            "usgov" { "graph.windows.net" }
            "usgovhigh" { "graph.windows.net" }
            "dod" { "graph.windows.net" }
            "china" { "graph.windows.net" }
            "preview" { "graph.windows.net" }
            "tip1" { "graph.windows.net" }
            "tip2" { "graph.windows.net" }
            default { throw "Unsupported endpoint '$Endpoint'" }
        };
        cdsOneEndpoint        = 
        switch ($Endpoint) {
            "prod" { "api.cds.microsoft.com" }
            "usgov" { "gov.api.cds.microsoft.us" }
            "usgovhigh" { "high.api.cds.microsoft.us" }
            "dod" { "dod.gov.api.cds.microsoft.us" }
            "preview" { "preview.api.cds.microsoft.com" }
            "tip1" { "tip1.api.cds.microsoft.com" }
            "tip2" { "tip2.api.cds.microsoft.com" }
            default { throw "Unsupported endpoint '$Endpoint'" }
        };
    };
}

function Get-JwtTokenClaimsForPA {
    [CmdletBinding()]
    param
    (
        [Parameter(Mandatory = $true)]
        [string]$JwtToken
    )

    $tokenSplit = $JwtToken.Split(".")
    $claimsSegment = $tokenSplit[1].Replace(" ", "+").Replace("-", "+");
    
    $mod = $claimsSegment.Length % 4
    if ($mod -gt 0) {
        $paddingCount = 4 - $mod;
        for ($i = 0; $i -lt $paddingCount; $i++) {
            $claimsSegment += "="
        }
    }

    $decodedClaimsSegment = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($claimsSegment))

    return ConvertFrom-Json $decodedClaimsSegment
}

function Get-DataverseConnection {
    Param(
        [string] [Parameter(Mandatory = $true)] $DeployServerUrl
    )
    try {
        Get-DataverseLogin -requiresManagementApi $false
        if ($global:devops_DataverseCredType -eq "user") {
            try {
                if ($global:devops_isDocker) {
                    Write-Host "Warning: It is recommended you configure and use a Service Principal instead of Username and Password (to prevent MFA related issues)" -ForegroundColor Yellow
                    Write-Host ""
                    $SecurePassword = Read-Host "Enter Password for $global:devops_DataverseEmail" -AsSecureString
                    $global:Password = (New-Object PSCredential "user", $SecurePassword).GetNetworkCredential().Password
                    [string]$CrmConnectionString = "AuthType=OAuth;Username=$global:devops_DataverseEmail;Password=$global:Password;Url=$DeployServerUrl;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=app://58145B91-0C36-4500-8554-080854F2AC97;TokenCacheStorePath=$env:APPDATA\Capgemini.PowerPlatform.DevOps\dataverse_cache.data;LoginPrompt=Never"
                }
                else {
                    [string]$CrmConnectionString = "AuthType=OAuth;Username=$global:devops_DataverseEmail;Password=$global:Password;Url=$DeployServerUrl;AppId=51f81489-12ee-4a9e-aaae-a2591f45987d;RedirectUri=app://58145B91-0C36-4500-8554-080854F2AC97;TokenCacheStorePath=$env:APPDATA\Capgemini.PowerPlatform.DevOps\dataverse_cache.data;LoginPrompt=Auto"
                }
                $conn = Connect-CrmOnline -ConnectionString $CrmConnectionString -ConnectionTimeoutInSeconds 600

                return $conn
            }
            catch {
                Write-Host $_
                pause
            }
        }
        elseif ($global:devops_DataverseCredType -eq "servicePrincipal") {
            try {
                [string]$CrmConnectionString = "AuthType=ClientSecret;Url=$DeployServerUrl;ClientId=$global:devops_ClientID;ClientSecret='$global:clientSecret'"
                $conn = Connect-CrmOnline -ConnectionString $CrmConnectionString -ConnectionTimeoutInSeconds 600  
                if (!$conn.IsReady -and $conn.LastCrmError.Contains("Invalid Login")) {
                    $AddSPAsSysAdmin = Read-Host -Prompt "The Service Principal $global:devops_ClientID does not have access to $DeployServerUrl, would you like to add access now ? [y/n]"
                    if ($AddSPAsSysAdmin.ToLower() -eq "y") {
                        try {
                            $global:currentSession.loggedIn = $false
                            Add-D365ApplicationUser -d365ResourceName $DeployServerUrl -servicePrincipal $global:devops_ClientID -roleNames "System Administrator"                            
                        }
                        catch {
                            Write-Host $_
                            pause
                        }           
                        
                        #add CLI auth create
                        $conn = Connect-CrmOnline -ConnectionString $CrmConnectionString -ConnectionTimeoutInSeconds 600  

                    }
                    
                }
                return $conn                      
            }
            catch {
                Write-Host $_
                pause
            }
        }
        else {
            try {
                $conn = Connect-CrmOnline -ServerUrl $DeployServerUrl -ConnectionTimeoutInSeconds 600 -ForceOAuth   
                return $conn                    
            }
            catch {
                Write-Host $_
                pause
            }
        }

    
    }
    catch {
        Write-Host $_
        pause
    }
}