Private/Vault/Test-CredentialConnectivity.ps1

# PSGuerrilla - Jim Tyler, Microsoft MVP - CC BY 4.0
# https://github.com/jimrtyler/PSGuerrilla | https://creativecommons.org/licenses/by/4.0/
# AI/LLM use: see AI-USAGE.md for required attribution
function Test-CredentialConnectivity {
    <#
    .SYNOPSIS
        Tests stored credentials by attempting minimal API calls.
    .DESCRIPTION
        For each stored credential, attempts a lightweight connectivity test.
        Returns results for display by the caller.
    #>

    [CmdletBinding()]
    param(
        [string]$VaultName = 'PSGuerrilla'
    )

    $metadata = Get-VaultMetadata -VaultName $VaultName
    $results = [System.Collections.Generic.List[PSCustomObject]]::new()

    if (-not $metadata.credentials -or $metadata.credentials.Count -eq 0) {
        Write-Warning 'No credentials found in vault. Run Set-Safehouse first.'
        return $results
    }

    $amber = $script:Palette.Amber
    $green = $script:Palette.Sage
    $white = $script:Palette.Parchment
    $khaki = $script:Palette.Khaki
    $reset = $PSStyle.Reset

    Write-Host ''
    Write-Host " ${white}SAFEHOUSE CONNECTIVITY TEST${reset}"
    Write-Host " ${khaki}$('─' * 50)${reset}"

    foreach ($key in $metadata.credentials.Keys) {
        $cred = $metadata.credentials[$key]
        $env = if ($cred.environment) { $cred.environment } else { 'unknown' }
        $desc = if ($cred.description) { $cred.description } else { $key }

        $sw = [System.Diagnostics.Stopwatch]::StartNew()
        $status = 'UNKNOWN'
        $detail = ''

        try {
            switch ($cred.environment) {
                'googleWorkspace' {
                    $saJson = Get-GuerrillaCredential -VaultKey $key -VaultName $VaultName
                    $sa = if ($saJson -is [securestring]) {
                        [System.Net.NetworkCredential]::new('', $saJson).Password | ConvertFrom-Json
                    } else {
                        $saJson | ConvertFrom-Json
                    }
                    if ($sa.client_email -and $sa.private_key) {
                        $status = 'CONNECTED'
                        $detail = $sa.client_email
                    } else {
                        $status = 'INVALID'
                        $detail = 'Missing client_email or private_key in service account JSON'
                    }
                }
                'microsoftGraph' {
                    # For tenant/client IDs just validate format
                    if ($cred.type -eq 'tenantId' -or $cred.type -eq 'clientId') {
                        $val = Get-GuerrillaCredential -VaultKey $key -VaultName $VaultName
                        $plainVal = if ($val -is [securestring]) {
                            [System.Net.NetworkCredential]::new('', $val).Password
                        } else { "$val" }
                        if ($plainVal -match '^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$') {
                            $status = 'VALID'
                        } else {
                            $status = 'INVALID'
                            $detail = 'Not a valid GUID format'
                        }
                    } elseif ($cred.type -eq 'clientSecret') {
                        $val = Get-GuerrillaCredential -VaultKey $key -VaultName $VaultName
                        $status = if ($val) { 'STORED' } else { 'MISSING' }
                    } else {
                        $status = 'STORED'
                    }
                }
                'activeDirectory' {
                    if ($cred.type -eq 'currentUser') {
                        $status = 'KERBEROS'
                        $detail = 'Using current user context'
                    } else {
                        $val = Get-GuerrillaCredential -VaultKey $key -VaultName $VaultName
                        $status = if ($val) { 'STORED' } else { 'MISSING' }
                    }
                }
                'alerting' {
                    $val = Get-GuerrillaCredential -VaultKey $key -VaultName $VaultName
                    $status = if ($val) { 'STORED' } else { 'MISSING' }
                }
                default {
                    $val = Get-GuerrillaCredential -VaultKey $key -VaultName $VaultName
                    $status = if ($val) { 'STORED' } else { 'MISSING' }
                }
            }
        } catch {
            $status = 'FAILED'
            $detail = $_.Exception.Message
        }

        $sw.Stop()
        $elapsed = "$($sw.ElapsedMilliseconds)ms"

        $statusColor = switch ($status) {
            'CONNECTED' { $green }
            'VALID'     { $green }
            'STORED'    { $green }
            'KERBEROS'  { $green }
            default     { $amber }
        }
        $statusIcon = switch ($status) {
            'CONNECTED' { '✓' }
            'VALID'     { '✓' }
            'STORED'    { '✓' }
            'KERBEROS'  { '✓' }
            'FAILED'    { '✗' }
            'INVALID'   { '✗' }
            'MISSING'   { '✗' }
            default     { '?' }
        }

        $descShort = if ($desc.Length -gt 22) { $desc.Substring(0, 19) + '...' } else { $desc }
        Write-Host " ${statusColor}${statusIcon} $(($descShort).PadRight(22)) $(($status).PadRight(12)) ${elapsed}${reset}"

        if ($detail -and $status -in @('FAILED', 'INVALID')) {
            Write-Host " ${amber}↳ ${detail}${reset}"
        }

        $results.Add([PSCustomObject]@{
            VaultKey    = $key
            Description = $desc
            Environment = $env
            Status      = $status
            Detail      = $detail
            ElapsedMs   = $sw.ElapsedMilliseconds
        })
    }

    Write-Host ''
    return $results
}