Public/Authentication/Connect-UKG.ps1

function Connect-UKG {
    <#
    .SYNOPSIS
        Connects to the UKG HR Service Delivery API.

    .DESCRIPTION
        Authenticates with the UKG HR Service Delivery API using OAuth 2.0 client credentials
        and establishes a session for subsequent API calls.

    .PARAMETER ApplicationId
        The application ID provided by UKG.

    .PARAMETER ApplicationSecret
        The application secret provided by UKG (as SecureString).

    .PARAMETER ApplicationSecretPlainText
        The application secret as plain text (not recommended for production).

    .PARAMETER ClientId
        The client ID for the specific tenant/company.

    .PARAMETER Region
        The region for the API endpoint (EU, US, UKG_ATL, UKG_TOR).

    .PARAMETER Environment
        The environment (Production or Staging).

    .PARAMETER BaseUrl
        Custom base URL (overrides Region/Environment).

    .PARAMETER TokenUrl
        Custom token URL (overrides Region/Environment).

    .EXAMPLE
        $secret = Read-Host -AsSecureString -Prompt "Enter secret"
        Connect-UKG -ApplicationId "app123" -ApplicationSecret $secret -ClientId "client456" -Region EU

    .EXAMPLE
        Connect-UKG -ApplicationId "app123" -ApplicationSecretPlainText "secret789" -ClientId "client456" -Region US -Environment Staging

    .OUTPUTS
        PSCustomObject representing the session information (without sensitive data).
    #>

    [CmdletBinding(DefaultParameterSetName = 'SecureSecret')]
    [OutputType([PSCustomObject])]
    param(
        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$ApplicationId,

        [Parameter(Mandatory, ParameterSetName = 'SecureSecret')]
        [System.Security.SecureString]$ApplicationSecret,

        [Parameter(Mandatory, ParameterSetName = 'PlainTextSecret')]
        [ValidateNotNullOrEmpty()]
        [string]$ApplicationSecretPlainText,

        [Parameter(Mandatory)]
        [ValidateNotNullOrEmpty()]
        [string]$ClientId,

        [Parameter(ParameterSetName = 'SecureSecret')]
        [Parameter(ParameterSetName = 'PlainTextSecret')]
        [ValidateSet('EU', 'US', 'UKG_ATL', 'UKG_TOR')]
        [string]$Region = 'EU',

        [Parameter(ParameterSetName = 'SecureSecret')]
        [Parameter(ParameterSetName = 'PlainTextSecret')]
        [ValidateSet('Production', 'Staging')]
        [string]$Environment = 'Production',

        [Parameter()]
        [string]$BaseUrl,

        [Parameter()]
        [string]$TokenUrl
    )

    # Convert plain text secret to SecureString if provided
    $secureSecret = if ($PSCmdlet.ParameterSetName -eq 'PlainTextSecret') {
        $ss = New-Object System.Security.SecureString
        foreach ($char in $ApplicationSecretPlainText.ToCharArray()) {
            $ss.AppendChar($char)
        }
        $ss.MakeReadOnly()
        $ss
    }
    else {
        $ApplicationSecret
    }

    # Determine URLs
    $urlKey = "${Region}_${Environment}"

    $actualBaseUrl = if ($BaseUrl) {
        $BaseUrl.TrimEnd('/')
    }
    else {
        if (-not $Script:UKGBaseUrls.ContainsKey($urlKey)) {
            throw "Invalid region/environment combination: $urlKey. Valid combinations: $($Script:UKGBaseUrls.Keys -join ', ')"
        }
        $Script:UKGBaseUrls[$urlKey]
    }

    $actualTokenUrl = if ($TokenUrl) {
        $TokenUrl
    }
    else {
        if (-not $Script:UKGTokenUrls.ContainsKey($urlKey)) {
            throw "Invalid region/environment combination: $urlKey"
        }
        $Script:UKGTokenUrls[$urlKey]
    }

    Write-Verbose "Connecting to UKG API at: $actualBaseUrl"
    Write-Verbose "Token URL: $actualTokenUrl"

    # Convert SecureString to plain text for the request
    if ($Script:PSVersionMajor -ge 7) {
        $plainSecret = ConvertFrom-SecureString -SecureString $secureSecret -AsPlainText
    }
    else {
        $bstr = [System.Runtime.InteropServices.Marshal]::SecureStringToBSTR($secureSecret)
        try {
            $plainSecret = [System.Runtime.InteropServices.Marshal]::PtrToStringAuto($bstr)
        }
        finally {
            [System.Runtime.InteropServices.Marshal]::ZeroFreeBSTR($bstr)
        }
    }

    # Request token
    $body = @{
        grant_type    = 'client_credentials'
        client_id     = $ClientId
        client_secret = "${ApplicationId}:${plainSecret}"
    }

    $requestParams = @{
        Uri             = $actualTokenUrl
        Method          = 'POST'
        Body            = $body
        ContentType     = 'application/x-www-form-urlencoded'
        UseBasicParsing = $true
        ErrorAction     = 'Stop'
    }

    try {
        $response = Invoke-RestMethod @requestParams

        # Store session information
        $Script:UKGSession = [PSCustomObject]@{
            PSTypeName        = 'UKG.Session'
            ApplicationId     = $ApplicationId
            ApplicationSecret = $secureSecret
            ClientId          = $ClientId
            Region            = $Region
            Environment       = $Environment
            BaseUrl           = $actualBaseUrl
            TokenUrl          = $actualTokenUrl
            AccessToken       = $response.access_token
            TokenType         = $response.token_type
            TokenExpiry       = (Get-Date).AddSeconds($response.expires_in)
            ConnectedAt       = Get-Date
        }

        Write-Verbose "Successfully connected to UKG API"
        Write-Verbose "Token expires at: $($Script:UKGSession.TokenExpiry)"

        # Return session info (without sensitive data)
        return [PSCustomObject]@{
            PSTypeName  = 'UKG.SessionInfo'
            ClientId    = $ClientId
            Region      = $Region
            Environment = $Environment
            BaseUrl     = $actualBaseUrl
            TokenExpiry = $Script:UKGSession.TokenExpiry
            ConnectedAt = $Script:UKGSession.ConnectedAt
        }
    }
    catch {
        $errorMessage = "Failed to connect to UKG API: "

        if ($_.Exception.Response) {
            try {
                $reader = New-Object System.IO.StreamReader($_.Exception.Response.GetResponseStream())
                $errorBody = $reader.ReadToEnd()
                $reader.Close()

                try {
                    $errorJson = $errorBody | ConvertFrom-Json
                    if ($errorJson.error_description) {
                        $errorMessage += $errorJson.error_description
                    }
                    elseif ($errorJson.error) {
                        $errorMessage += $errorJson.error
                    }
                    else {
                        $errorMessage += $errorBody
                    }
                }
                catch {
                    $errorMessage += $errorBody
                }
            }
            catch {
                $errorMessage += $_.Exception.Message
            }
        }
        else {
            $errorMessage += $_.Exception.Message
        }

        $errorRecord = ConvertTo-UKGError -Response @{ code = 'authentication_failed'; message = $errorMessage } -StatusCode 401
        $PSCmdlet.ThrowTerminatingError($errorRecord)
    }
}

function Disconnect-UKG {
    <#
    .SYNOPSIS
        Disconnects from the UKG HR Service Delivery API.

    .DESCRIPTION
        Clears the current session and removes stored credentials from memory.

    .EXAMPLE
        Disconnect-UKG
    #>

    [CmdletBinding()]
    param()

    if ($null -ne $Script:UKGSession) {
        # Clear sensitive data
        $Script:UKGSession = $null
        Write-Verbose "Disconnected from UKG API"
    }
    else {
        Write-Verbose "No active session to disconnect"
    }
}

function Get-UKGSession {
    <#
    .SYNOPSIS
        Gets information about the current UKG API session.

    .DESCRIPTION
        Returns details about the current connection including region, environment,
        and token expiry time.

    .EXAMPLE
        Get-UKGSession

    .OUTPUTS
        PSCustomObject with session information (without sensitive data).
    #>

    [CmdletBinding()]
    [OutputType([PSCustomObject])]
    param()

    if ($null -eq $Script:UKGSession) {
        Write-Warning "Not connected to UKG API. Use Connect-UKG to establish a connection."
        return $null
    }

    return [PSCustomObject]@{
        PSTypeName     = 'UKG.SessionInfo'
        ClientId       = $Script:UKGSession.ClientId
        Region         = $Script:UKGSession.Region
        Environment    = $Script:UKGSession.Environment
        BaseUrl        = $Script:UKGSession.BaseUrl
        TokenExpiry    = $Script:UKGSession.TokenExpiry
        ConnectedAt    = $Script:UKGSession.ConnectedAt
        IsTokenExpired = ($Script:UKGSession.TokenExpiry -lt (Get-Date))
        TokenExpiresIn = if ($Script:UKGSession.TokenExpiry -gt (Get-Date)) {
            ($Script:UKGSession.TokenExpiry - (Get-Date)).ToString('hh\:mm\:ss')
        }
        else {
            'Expired'
        }
    }
}