Public/Connect-SF.ps1

<#
.SYNOPSIS
Connects to the SuccessFactors API using a ConnectorConfiguration object.

.DESCRIPTION
Validates configuration, selects auth flow (Assertion or ClientCredentials) and stores the access token. Supports two OAuth 2.0 authentication flows: SAML2 assertion with X.509 private key, or direct client credentials. Automatically calculates token expiry and manages renewal.

.PARAMETER ConnectorConfiguration
Hashtable containing configuration and secrets. Must include baseurl, clientid, and companyid. For Assertion flow, also requires userid and secrets.privatekey. For ClientCredentials flow, requires secrets.clientsecret.

.PARAMETER ForceRenewal
Force renewal of the access token during connection.

.OUTPUTS
None - Stores connection state and token internally.

.NOTES
The function stores the access token in module scope for use by other functions. Token expiry includes a 60-second buffer to ensure renewal before actual expiration.
#>

function Connect-SF {
    [CmdletBinding()]
    [OutputType([void])]

    Param
    (
        [Parameter(Mandatory = $true)]
        $ConnectorConfiguration,

        [Parameter(Mandatory = $false)]
        [switch]
        $ForceRenewal
    )

    Process
    {
        # Validate required configuration.
        $baseUrl = $ConnectorConfiguration.configuration.baseurl
        if (-not $baseUrl)      { throw "baseurl is required." }

        $clientID = $ConnectorConfiguration.configuration.clientid
        if (-not $clientID)  { throw "clientid is required." }

        $companyID = $ConnectorConfiguration.configuration.companyid
        if (-not $companyID) { throw "companyid is required." }

        $authFlow = $ConnectorConfiguration.configuration.authflow
        if (-not $authFlow) { $authFlow = 'Assertion' }
        if ($authFlow -notin @('Assertion', 'ClientCredentials')) {
            throw "authflow must be 'Assertion' or 'ClientCredentials'."
        }

        # Retrieve token using the configured auth flow.
        if ($authFlow -eq 'Assertion') {
            # For the Assertion flow, we need the user ID and private key to obtain a SAML assertion first.
            $userID = $ConnectorConfiguration.configuration.userid
            if (-not $userID) { throw "userid is required when authflow is 'Assertion'." }

            # The private key is required for the Assertion flow to obtain the SAML assertion.
            $privateKey = $ConnectorConfiguration.secrets.privatekey
            if (-not $privateKey) { throw "secrets.privatekey is required when authflow is 'Assertion'." }

            # Get SAML assertion and then exchange it for an access token.
            $sfAssertion = Get-SFAssertion -BaseUrl $baseUrl -UserID $userID -ClientID $clientID -TokenEndpointURL "$baseUrl/oauth/token" -PrivateKey $privateKey
            $sfToken = Get-SFToken -BaseUrl $baseUrl -ClientID $clientID -CompanyID $companyID -Assertion $sfAssertion -ForceNew:$ForceRenewal
        } 
        else {
            # For the ClientCredentials flow, we need the client secret to obtain an access token.
            $clientSecret = $ConnectorConfiguration.secrets.clientsecret
            if (-not $clientSecret) { throw "secrets.clientsecret is required when authflow is 'ClientCredentials'." }

            # Get access token directly using client credentials.
            $sfToken = Get-SFToken -BaseUrl $baseUrl -ClientID $clientID -CompanyID $companyID -ClientSecret $clientSecret -ForceNew:$ForceRenewal
        }

        if (-not $sfToken -or -not $sfToken.access_token) {
            throw "SuccessFactors token response did not contain 'access_token'."
        }

        # Only store configuration after a successful token retrieval.
        $Script:ConnectorConfiguration = $ConnectorConfiguration
        $Script:AccessToken = $sfToken.access_token

        # Calculate token expiry time, subtracting a buffer to ensure we renew before it actually expires.
        $expiresIn = $sfToken.expires_in ? [int]$sfToken.expires_in : 3600
        $Script:AccessTokenExpiry = (Get-Date).AddSeconds($expiresIn - 60)
    }
}