Authentication.psm1

using module ./public/classes/AuthenticationResult.psm1
using module ./public/classes/AuthenticationData.psm1

<#
  .SYNOPSIS
    Get-RequiredEnvironmentVariable is a private helper method that retrieves environment variables with the
    expectation that if they are not present that an error will be logged with an immedate exit.

  .DESCRIPTION
    Get-RequiredEnvironmentVariable retrieves the environment variable specified by the input parameter and checks to
    be sure that a value is present for that environment variable. If the value is missing or whitespace a message will
    be written to Write-Error with the name of the variable in the output and exit will be called.

  .PARAMETER VariableName
    This is the name of the environment variable to retrieve and validate that a value is present.

  .EXAMPLE
    Provide -VariableName Parameter

    Get-RequiredEnvironmentVariable -VariableName AZ_APPLICATION_ID

  .EXAMPLE
    Provide variable name without -VariableName Parameter

    Get-RequiredEnvironmentVariable AZ_APPLICATION_ID

  .INPUTS
    System.String

  .OUTPUTS
    System.String
#>

function Get-RequiredEnvironmentVariable {
  [OutputType([System.String])]
  param (
    [Parameter(Mandatory=$true, Position=0)]
    [string]$VariableName
  )
  Begin {
    $Value = [string]$null
  }
  Process {
    $Value = [System.Environment]::GetEnvironmentVariable($VariableName)

    if ([string]::IsNullOrWhiteSpace($Value)) {
      Write-Error("Missing Required Environment Variable $VariableName")
      exit 1
    }
  }
  End {
    return $Value
  }
}

function Connect-Account {
  <#
    .SYNOPSIS
      Connect-Account uses environment variable values to log into an Azure context. This is an internal function and
      should not be used outside of the BenchPress module.

    .DESCRIPTION
      Connect-Account is designed to login to an Azure context using environment variables to login as a
      ServicePrincipal for the PowerShell session.

      The expected environment variables are:

      AZ_APPLICATION_ID - The Service Principal ID
      AZ_ENCRYPTED_PASSWORD - The Service Principal account password properly encrypted using ConvertTo-SecureString and
                              saved as an environment variable using ConvertFrom-SecureString
      AZ_TENANT_ID - The Tenant ID to login to
      AZ_SUBSCRIPTION_ID - The Subscription ID to login to

      If the current context that is logged in to matches the Service Principal, Tenant, and Subscription this function
      is a no-op.

    .EXAMPLE
      There is only one way to call Connect-Account:

      Connect-Account

    .INPUTS
      None

    .OUTPUTS
      AuthenticationResult
  #>

  [OutputType([AuthenticationResult])]
  [CmdletBinding()]
  param ( )
  Begin { }
  Process {
    $ApplicationId = Get-RequiredEnvironmentVariable AZ_APPLICATION_ID
    $TenantId = Get-RequiredEnvironmentVariable AZ_TENANT_ID
    $SubscriptionId = Get-RequiredEnvironmentVariable AZ_SUBSCRIPTION_ID
    $CurrentConnection = Get-AzContext
    $Results = [AuthenticationResult]::new()

    # If the current context matches the subscription, tenant, and service principal, then we're already properly
    # logged in.
    if ($null -ne $CurrentConnection `
      -and ($CurrentConnection).Account.Type -eq 'ServicePrincipal' `
      -and ($CurrentConnection).Account.Id -eq $ApplicationId `
      -and ($CurrentConnection).Tenant.Id -eq $TenantId `
      -and ($CurrentConnection).Subscription.Id -eq $SubscriptionId) {
      $Results.Success = $true
      $Results.AuthenticationData = [AuthenticationData]::new(($CurrentConnection).Subscription.Id)
    } else {
      # The current context is not correct, create the credentials and login to the correct account
      $ClientSecret = Get-RequiredEnvironmentVariable AZ_ENCRYPTED_PASSWORD | ConvertTo-SecureString
      $Credential = New-Object System.Management.Automation.PSCredential -ArgumentList $ApplicationId, $ClientSecret

      try {
        $ConnectionParams = @{
          Credential = $Credential
          TenantId = $TenantId
          Subscription = $SubscriptionId
        }
        $Connection = Connect-AzAccount -ServicePrincipal @ConnectionParams

        $Results.Success = $true
        $Results.AuthenticationData = [AuthenticationData]::new()
        $Results.AuthenticationData.SubscriptionId = $Connection.Context.Subscription.Id
      } catch {
        $Exception = $_
        $Results.Error = $Exception
        $Results.Success = $false
      }

      $Results
    }
  }
  End { }
}

<#
  .SYNOPSIS
    Disconnect-Account uses environment variable values to disconnect from a specific Azure context. This is an
    internal function and should not be used outside of the BenchPress module.

  .DESCRIPTION
    Disconnect-Account is designed to automatically log out of the specific Azure context using environment variables
    to identify the context to disconnect.

    The expected environment variables are:

    AZ_APPLICATION_ID - The Service Principal ID
    AZ_TENANT_ID - The Tenant ID to login to
    AZ_SUBSCRIPTION_ID - The Subscription ID to login to

    If the current context does not match the Service Principal, Tenant, or Subscription then this function is a no-op

  .EXAMPLE
    There is only one way to call Disconnect-Account:

    Disconnect-Account

  .INPUTS
    None

  .OUTPUTS
    None
#>

function Disconnect-Account {
  [OutputType([System.Void])]
  [CmdletBinding()]
  param ( )
  Begin {
    $ApplicationId = Get-RequiredEnvironmentVariable AZ_APPLICATION_ID
    $TenantId = Get-RequiredEnvironmentVariable AZ_TENANT_ID

    # If the current context doesn't match the target subscription, tentant, and client, then the testing account is
    # not logged in. Do nothing.
    $CurrentConnection = Get-AzContext
  }
  Process {
    if ($null -eq $CurrentConnection `
      -or ($CurrentConnection).Account.Type -ne "ServicePrincipal" `
      -or ($CurrentConnection).Account.Id -ne $ApplicationId `
      -or ($CurrentConnection).Tenant.Id -ne $TenantId) {
      return
    }

    $CurrentConnection | Disconnect-AzAccount -Scope CurrentUser
  }
  End { }
}

Export-ModuleMember -Function Connect-Account, Disconnect-Account