#requires -Version 5
<#
.SYNOPSIS
  ZAP active scan (full DAST) against the LOCAL portal copy. Guard IS8 - NOT against prod.

.DESCRIPTION
  Self-contained orchestrator for tool #68 (security-go-live step 4.2):
    1. Starts "php artisan serve" on 127.0.0.1:8000 if not already up.
    2. Starts the ZAP daemon (zap-2.17.0.jar) on :8090, API key disabled (local only).
    3. Spiders the target URL, polls to 100%.
    4. Active scan (full DAST), polls to 100%.
    5. Exports JSON + HTML report into docs/security/.
    6. Prints an alerts summary by risk level.
    7. Shuts down ZAP and (if it started one) the serve process.

  Target is hardcoded to 127.0.0.1:8000 - no public addresses (IS8).
  ASCII-only on purpose: Windows PowerShell 5.1 mangles non-ASCII in UTF-8 (no BOM) scripts.
  Java is the bundled JRE under bin/_runtimes/ (NOT on PATH), launched by full path so the
  script does not depend on the caller's PATH. ZAP jar is launched directly (bypasses zap.bat).
#>
$ErrorActionPreference = 'Stop'

$repo       = Split-Path -Parent $PSScriptRoot           # bin/ -> repo root
$target     = 'http://127.0.0.1:8000'
$zapPort    = 8090
$zapBase    = "http://127.0.0.1:$zapPort"
$stamp      = '2026-06-18'
$reportDir  = Join-Path $repo 'docs/security'
$jsonReport = Join-Path $reportDir "$stamp-zap-active-scan.json"
$htmlReport = Join-Path $reportDir "$stamp-zap-active-scan.html"
$zapDir     = Join-Path $repo 'bin/ZAP_2.17.0'
# relative jar name on purpose: the absolute repo path contains spaces ("портал crm"),
# and Start-Process -ArgumentList does not quote a space-containing arg, so java would
# get a truncated path. WorkingDirectory=$zapDir resolves the relative name (как zap.bat).
$zapJar     = 'zap-2.17.0.jar'
$zapOut     = Join-Path $env:TEMP 'zap-daemon.out.log'
$zapErr     = Join-Path $env:TEMP 'zap-daemon.err.log'

# Resolve Java: bundled JRE first (not on PATH), then any bundled, then PATH.
$javaExe = Join-Path $repo 'bin/_runtimes/jdk-17.0.19+10-jre/bin/java.exe'
if (-not (Test-Path $javaExe)) {
    $bundled = Get-ChildItem (Join-Path $repo 'bin/_runtimes') -Recurse -Filter java.exe -ErrorAction SilentlyContinue | Select-Object -First 1
    if ($bundled) { $javaExe = $bundled.FullName }
    elseif (Get-Command java -ErrorAction SilentlyContinue) { $javaExe = 'java' }
    else { throw "java.exe not found (bundled JRE missing under bin/_runtimes and not on PATH)" }
}

function Test-Up([string]$url) {
    try { Invoke-WebRequest -UseBasicParsing -TimeoutSec 3 -Uri $url | Out-Null; return $true }
    catch { return $false }
}

if (-not (Test-Path $reportDir)) { New-Item -ItemType Directory -Path $reportDir | Out-Null }

# --- 1. Local app on :8000 ---
$serveProc = $null
if (-not (Test-Up $target)) {
    Write-Host "[zap] starting php artisan serve ..."
    $serveProc = Start-Process -PassThru -WindowStyle Hidden `
        -WorkingDirectory (Join-Path $repo 'app') `
        -FilePath 'php' -ArgumentList 'artisan','serve','--host=127.0.0.1','--port=8000'
    for ($i = 0; $i -lt 30 -and -not (Test-Up $target); $i++) { Start-Sleep -Seconds 1 }
    if (-not (Test-Up $target)) { throw "App did not come up at $target" }
}
Write-Host "[zap] target up: $target"

# --- 2. ZAP daemon (java launched by full path; WorkingDirectory = ZAP dir) ---
Write-Host "[zap] starting ZAP daemon on :$zapPort (java: $javaExe) ..."
$zapProc = Start-Process -PassThru -WindowStyle Hidden -FilePath $javaExe `
    -WorkingDirectory $zapDir `
    -RedirectStandardOutput $zapOut -RedirectStandardError $zapErr `
    -ArgumentList @(
        '-Xmx512m','-jar',$zapJar,
        '-daemon','-port',"$zapPort",'-silent',
        '-config','api.disablekey=true',
        '-config','api.addrs.addr.name=.*',
        '-config','api.addrs.addr.regex=true'
    )
for ($i = 0; $i -lt 150 -and -not (Test-Up "$zapBase/JSON/core/view/version/"); $i++) { Start-Sleep -Seconds 2 }
if (-not (Test-Up "$zapBase/JSON/core/view/version/")) {
    if ($zapProc -and -not $zapProc.HasExited) { Stop-Process -Id $zapProc.Id -Force -ErrorAction SilentlyContinue }
    throw "ZAP daemon did not come up at $zapBase (see $zapErr / $zapOut)"
}
$ver = (Invoke-RestMethod "$zapBase/JSON/core/view/version/").version
Write-Host "[zap] ZAP up: $ver"

try {
    # --- 3. Seed proxy with target + Spider ---
    Invoke-RestMethod "$zapBase/JSON/core/action/accessUrl/?url=$target" | Out-Null
    $spiderId = (Invoke-RestMethod "$zapBase/JSON/spider/action/scan/?url=$target&recurse=true").scan
    Write-Host "[zap] spider id=$spiderId"
    do {
        Start-Sleep -Seconds 2
        $st = (Invoke-RestMethod "$zapBase/JSON/spider/view/status/?scanId=$spiderId").status
    } while ([int]$st -lt 100)
    Write-Host "[zap] spider 100%"

    # let the passive scanner drain its queue
    do { Start-Sleep -Seconds 1; $rec = (Invoke-RestMethod "$zapBase/JSON/pscan/view/recordsToScan/").recordsToScan } while ([int]$rec -gt 0)

    # --- 4. Active scan ---
    $ascanId = (Invoke-RestMethod "$zapBase/JSON/ascan/action/scan/?url=$target&recurse=true&inScopeOnly=false").scan
    Write-Host "[zap] active scan id=$ascanId"
    do {
        Start-Sleep -Seconds 5
        $as = (Invoke-RestMethod "$zapBase/JSON/ascan/view/status/?scanId=$ascanId").status
        Write-Host "[zap] active scan $as%"
    } while ([int]$as -lt 100)
    Write-Host "[zap] active scan 100%"

    # --- 5. Reports ---
    Invoke-RestMethod "$zapBase/OTHER/core/other/jsonreport/" -OutFile $jsonReport
    Invoke-RestMethod "$zapBase/OTHER/core/other/htmlreport/" -OutFile $htmlReport
    Write-Host "[zap] report saved: $jsonReport"

    # --- 6. Summary by risk ---
    $sum = (Invoke-RestMethod "$zapBase/JSON/alert/view/alertsSummary/?baseurl=$target").alertsSummary
    Write-Host "[zap] ALERTS SUMMARY: $($sum | ConvertTo-Json -Compress)"
}
finally {
    # --- 7. Shut down ZAP and serve ---
    try { Invoke-RestMethod "$zapBase/JSON/core/action/shutdown/" | Out-Null } catch {}
    if ($zapProc -and -not $zapProc.HasExited) { Stop-Process -Id $zapProc.Id -Force -ErrorAction SilentlyContinue }
    if ($serveProc) { Stop-Process -Id $serveProc.Id -Force -ErrorAction SilentlyContinue }
    Write-Host "[zap] done"
}
