New-CDBAuthHeader.psm1

$FunctionScriptName = "New-CDBAuthHeader"
Write-Verbose "Import-Start| [$($FunctionScriptName)]"

#* Dependencies
# N/A

#todo: Support AAD based tokens - https://docs.microsoft.com/en-us/rest/api/cosmos-db/access-control-on-cosmosdb-resources
#todo: support scoped/resource based tokens

function New-CDBAuthHeader {
    <#
        .SYNOPSIS
            Creates an auth Header for CosmosDB SQL API via REST
         
        .DESCRIPTION
            Creates an auth Header for CosmosDB SQL API via REST
            Used by SQL/Core REST API
         
        .PARAMETER Config
            Config array
            Accepts the following values: DatabaseID / CollectionID / AuthKey / keyType / CosmosDBEndpoint / KeyType
            All required values can be set directly. Directly set values have precedent.
            Not all values may be required.
         
        .PARAMETER DatabaseID
            Can be set via -Config value
            Name of the CosmosDB Database
         
        .PARAMETER CollectionId
            Can be set via -Config value
            Name of the CosmosDB Collection
         
        .PARAMETER AuthKey
            Can be set via -Config value
            Key value to authenticate against cosmosDB
         
        .PARAMETER KeyType
            Can be set via -Config value
            Type of AuthKey provided.
            Default value: "master"
         
        .PARAMETER PartitionKey
            Value of the Partition. Not the Name of the Key itself!
         
        .PARAMETER DocumentID
            ID of the document
            Value of the document value "id"
         
        .PARAMETER HTTPverb
            HTTPverb used by REST API
            Use as required:
                GET = Query document
                POST = Write document
                PATCH = Patch part of document
                DELETE = Delete document
         
        .PARAMETER DocumentUpsert
            Only for HTTPverb: POST - Default: TRUE
            Sets the "x-ms-documentdb-is-upsert" header
            True = Overwrites existing document if exists. Create if new.
            False = Throws error if document exists. Create if new.
         
        .PARAMETER enablecrosspartition
            Only for HTTPverb: POST - Default: FALSE
            Sets the "x-ms-documentdb-query-enablecrosspartition" header
            True = Scopes multiple partitions. The primary one still needs to be defined.
            False = Only uses the defined partition. Increases the performance.
         
        .PARAMETER APIVersion
            API Version of the CosmosDB REST API
            Default value: "2018-12-31"
         
        .PARAMETER AuthSignatureOnly
            Default value: FALSE
            TRUE = Returns only the signature without a complete header.
            False = Returns the usable auth header.
         
        .EXAMPLE
            New-CDBAuthHeader -Config $Config -PartitionKey $PKEY -HTTPverb "POST"
            New-CDBAuthHeader -Config $Config -PartitionKey $PKEY -HTTPverb "GET" -DocumentID $DocumentID
         
        .NOTES
            AUTHOR: Ken Dobrunz // Ken.Dobrunz@Skaylink.com
            LASTEDIT: 05.08.2022 - Version: 1.0
        #>

    [cmdletbinding()]
    Param(
        #* Active data
        # Config or direct values needed
        [Parameter()]$Config,
        [Parameter()][string]$DatabaseID,
        [Parameter()][string]$CollectionId,
        [Parameter()][string]$AuthKey,
        [Parameter()][string]$KeyType = "master",
        
        # Entity
        [parameter()][Alias('pkey')][string]$PartitionKey,
        [parameter()][Alias('verb')][string]$HTTPverb, #? GET / POST / DELETE / PATCH
        
        # GET Variables
        [Parameter()][Alias('doc')][string]$DocumentID,
        
        # POST Variables
        [parameter()][Alias('upsert')][bool]$DocumentUpsert = $true,
        [parameter()][bool]$enablecrosspartition = $false,
        
        # Tech Config
        [parameter()][string]$APIVersion = "2018-12-31",
        [parameter()][bool]$AuthSignatureOnly = $false
    )
    Begin {
        $SelfIdentifier = "New-CDBAuthHeader"
        
        # Variables for future use (Scoping)
        $resourceType = "docs"
        $tokenVersion = "1.0"
        
        # Check Config / Set Variables
        $DatabaseID = if ($DatabaseID) { $DatabaseID }elseif ($Config.DatabaseID) { $Config.DatabaseID }else { Write-Error "[$($SelfIdentifier)] No DatabaseID provided" }
        $CollectionId = if ($CollectionId) { $CollectionId }elseif ($Config.CollectionId) { $Config.CollectionId }else { Write-Error "[$($SelfIdentifier)] No CollectionId provided" }
        $AuthKey = if ($AuthKey) { $AuthKey }elseif ($Config.AuthKey) { $Config.AuthKey }else { Write-Error "[$($SelfIdentifier)] No AuthKey provided" }
        $KeyType = if ($KeyType) { $KeyType }elseif ($Config.KeyType) { $Config.KeyType }else { Write-Error "[$($SelfIdentifier)] No KeyType provided" }

        switch ($HTTPverb) {
            "GET" {
                $DocumentID = if ($DocumentID) { $DocumentID }elseif ($Config.DocumentID) { $Config.DocumentID }else { Write-Error "[$($SelfIdentifier)] No DocumentID provided" }
                $ResourceLink = "dbs/$DatabaseId/colls/$CollectionId/$resourceType/$DocumentID"
            }
            "DELETE" {
                $DocumentID = if ($DocumentID) { $DocumentID }elseif ($Config.DocumentID) { $Config.DocumentID }else { Write-Error "[$($SelfIdentifier)] No DocumentID provided" }
                $ResourceLink = "dbs/$DatabaseId/colls/$CollectionId/$resourceType/$DocumentID"
            }
            "PATCH" {
                $DocumentID = if ($DocumentID) { $DocumentID }elseif ($Config.DocumentID) { $Config.DocumentID }else { Write-Error "[$($SelfIdentifier)] No DocumentID provided" }
                $ResourceLink = "dbs/$DatabaseId/colls/$CollectionId/$resourceType/$DocumentID"
            }
            "POST" {
                $ResourceLink = "dbs/$DatabaseId/colls/$CollectionId"
            } 
            Default {
                Write-Error "[$($SelfIdentifier)] Invalid or no HTTPVERB provided: [$($HTTPverb)]"
            }
        }

    }
    Process { 
        $dateTime = [DateTime]::UtcNow.ToString("r") #? Format as required by CDB
        Write-Debug "[$($SelfIdentifier)] Creating [$($keyType)] signature for [$($HTTPverb)][$($ResourceLink)] on [$($dateTime)]"
        
        # Generate Signature
        $hmacSha256 = New-Object System.Security.Cryptography.HMACSHA256
        $hmacSha256.Key = [System.Convert]::FromBase64String($AuthKey)
        
        $payLoad = "$($HTTPverb.ToLowerInvariant())`n$($resourceType.ToLowerInvariant())`n$resourceLink`n$($dateTime.ToLowerInvariant())`n`n"
        
        $hashPayLoad = $hmacSha256.ComputeHash([System.Text.Encoding]::UTF8.GetBytes($payLoad))
        $signature = [System.Convert]::ToBase64String($hashPayLoad);
        
        $authsignature = [System.Web.HttpUtility]::UrlEncode("type=$keyType&ver=$tokenVersion&sig=$signature")
        if (!$AuthSignatureOnly) {
            # Creating header
            $header = @{
                "Authorization"                = $authsignature
                "x-ms-documentdb-partitionkey" = '["' + $PartitionKey + '"]'
                "x-ms-version"                 = $APIVersion
                "x-ms-date"                    = $dateTime
                "Accept"                       = "application/json"
            }
            # Adding optional values
            if ($HTTPverb = "POST") {
                if ($enablecrosspartition) { $header."x-ms-documentdb-query-enablecrosspartition" = $true }
                if ($DocumentUpsert) { $header."x-ms-documentdb-is-upsert" = $true }
            }
        }
    }
    End {
        if ($AuthSignatureOnly) {
            Write-Debug "[$($SelfIdentifier)] Returing Auth Signature"
            return $authsignature
        } else {
            Write-Debug "[$($SelfIdentifier)] Returing Auth Header"
            return $header
        }
    }
} #v0.1

Export-ModuleMember -Function *
Write-Verbose "Import-END| [$($FunctionScriptName)]"