#!/bin/bash
# =============================================================================
# verify_fix.sh — verifies the CVE-2026-40022 + explicit-prefix variant fix
#
# What it does:
#   1. Builds a minimal Camel Main app (routes /hello, /secure/data, /public)
#      against the VULNERABLE camel-platform-http-main 4.14.5 (shared m2 cache).
#   2. Compiles the 3 patched authentication classes from proposed_fix.diff and
#      builds a patched camel-platform-http-main jar (original jar with the 3
#      auth classes replaced).
#   3. Runs the REAL embedded Vert.x HTTP server with the patched jar on the
#      classpath across the full scenario matrix and asserts HTTP status codes:
#
#      A. default (unset authPath), ctx=/api   -> /api/hello        no-creds 401, w/creds 200  (CVE fixed)
#      B. explicit authPath=/api, ctx=/api     -> /api/hello        no-creds 401, w/creds 200  (variant closed)
#      C. explicit authPath=/api/*, ctx=/api   -> /api/hello        no-creds 401, w/creds 200  (variant closed)
#      D. explicit authPath=/secure/*, ctx=/api-> /api/secure/data  no-creds 401, w/creds 200  (selective protected)
#                                              -> /api/public       no-creds 200              (selective preserved)
#      E. default (unset authPath), ctx=/      -> /hello            no-creds 401, w/creds 200  (root ctx fixed)
#
#   4. Prints a result table and exits 0 only if every assertion passes.
# =============================================================================
set -euo pipefail

RUN_ROOT="/data/pruva/runs/7ae91431-7239-42ff-b03c-4a5d1126150d"
PATCH="$RUN_ROOT/bundle/coding/proposed_fix.diff"
M2="/data/pruva/project-cache/808f31c5-2393-4ae1-b99f-19b2f77f2927/m2-repo"
WORKDIR="/data/pruva/project-cache/808f31c5-2393-4ae1-b99f-19b2f77f2927/verify-fix"
ORIG_JAR="$M2/org/apache/camel/camel-platform-http-main/4.14.5/camel-platform-http-main-4.14.5.jar"
CAMEL_VERSION="4.14.5"
PORT="${PORT:-8090}"

log() { echo "[verify] $*"; }

command -v java >/dev/null 2>&1 || { echo "FATAL: java missing"; exit 2; }
command -v mvn >/dev/null 2>&1 || { echo "FATAL: mvn missing"; exit 2; }

rm -rf "$WORKDIR"; mkdir -p "$WORKDIR/src/main/java/repro" "$WORKDIR/src/main/resources"

# ---- application (3 platform-http routes) -----------------------------------
cat > "$WORKDIR/pom.xml" <<POM
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <groupId>repro</groupId>
  <artifactId>camel-auth-verify</artifactId>
  <version>1.0.0</version>
  <properties>
    <maven.compiler.release>17</maven.compiler.release>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <camel.version>${CAMEL_VERSION}</camel.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.apache.camel</groupId>
      <artifactId>camel-platform-http-main</artifactId>
      <version>\${camel.version}</version>
    </dependency>
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-simple</artifactId>
      <version>2.0.16</version>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.13.0</version>
      </plugin>
    </plugins>
  </build>
</project>
POM

cat > "$WORKDIR/src/main/java/repro/App.java" <<'JAVA'
package repro;

import org.apache.camel.builder.RouteBuilder;
import org.apache.camel.main.Main;

public class App {
    public static void main(String[] args) throws Exception {
        Main main = new Main();
        main.configure().addRoutesBuilder(new RouteBuilder() {
            @Override
            public void configure() {
                from("platform-http:/hello").setBody(constant("ok"));
                from("platform-http:/secure/data").setBody(constant("secure-ok"));
                from("platform-http:/public").setBody(constant("public-ok"));
            }
        });
        main.run();
    }
}
JAVA

cat > "$WORKDIR/src/main/resources/auth.properties" <<'AUTH'
user.admin=secret
AUTH

cat > "$WORKDIR/src/main/resources/simplelogger.properties" <<'SLF4J'
org.slf4j.simpleLogger.defaultLogLevel=info
org.slf4j.simpleLogger.showDateTime=true
org.slf4j.simpleLogger.showShortName=true
SLF4J

# ---- build app + classpath ---------------------------------------------------
log "Building verify app against camel-platform-http-main ${CAMEL_VERSION} ..."
( cd "$WORKDIR" && mvn -q -e clean compile dependency:build-classpath \
      -Dmaven.repo.local="$M2" -Dmdep.outputFile=target/cp.txt ) > "$WORKDIR/build.log" 2>&1 \
  || { log "BUILD FAILED"; tail -50 "$WORKDIR/build.log"; exit 1; }
CP="$(cat "$WORKDIR/target/cp.txt")"
log "app built; classpath resolved."

# ---- compile patched auth classes -------------------------------------------
PATCHED_CLASSES="$WORKDIR/patched-classes"
rm -rf "$PATCHED_CLASSES"; mkdir -p "$PATCHED_CLASSES"
SRCROOT="$WORKDIR/patched-src"
rm -rf "$SRCROOT"; mkdir -p "$SRCROOT"
# materialize the ORIGINAL 4.14.5 auth sources, then apply the patch
RELP="components/camel-platform-http-main/src/main/java/org/apache/camel/component/platform/http/main/authentication"
mkdir -p "$SRCROOT/$RELP"
# materialize the ORIGINAL 4.14.5 auth sources from the published sources jar
SRCSJAR="$WORKDIR/camel-platform-http-main-4.14.5-sources.jar"
SRCEX="$WORKDIR/camel-src-4.14.5"
if [ ! -d "$SRCEX" ]; then
  if [ ! -f "$SRCSJAR" ]; then
    curl -fsSL "https://repo1.maven.org/maven2/org/apache/camel/camel-platform-http-main/4.14.5/camel-platform-http-main-4.14.5-sources.jar" -o "$SRCSJAR" || true
  fi
  mkdir -p "$SRCEX" && ( cd "$SRCEX" && unzip -q -o "$SRCSJAR" )
fi
cp "$SRCEX/org/apache/camel/component/platform/http/main/authentication"/*.java "$SRCROOT/$RELP/"
( cd "$SRCROOT" && patch -p1 < "$PATCH" ) > "$WORKDIR/patch_apply.log" 2>&1 \
  || { log "PATCH APPLY FAILED"; cat "$WORKDIR/patch_apply.log"; exit 1; }
log "patch applied to source tree."

javac -cp "$CP" -d "$PATCHED_CLASSES" \
  "$SRCROOT/$RELP"/MainAuthenticationConfigurer.java \
  "$SRCROOT/$RELP"/BasicAuthenticationConfigurer.java \
  "$SRCROOT/$RELP"/JWTAuthenticationConfigurer.java
log "patched auth classes compiled."

# ---- build patched jar (extract original, replace 3 classes) ----------------
PATCHED_JAR="$WORKDIR/patched-platform-http-main.jar"
JAREXTRACT="$WORKDIR/jar-extract"
rm -rf "$JAREXTRACT"; mkdir -p "$JAREXTRACT"
( cd "$JAREXTRACT" && unzip -q "$ORIG_JAR" )
cp "$PATCHED_CLASSES"/org/apache/camel/component/platform/http/main/authentication/*.class \
   "$JAREXTRACT/org/apache/camel/component/platform/http/main/authentication/"
( cd "$JAREXTRACT" && jar cf "$PATCHED_JAR" . )
log "patched jar built: $PATCHED_JAR"

# classpath with the ORIGINAL platform-http-main jar removed, patched jar first
FILTERED_CP=$(python3 -c "import sys; print(':'.join(p for p in sys.argv[1].split(':') if p and p != sys.argv[2]))" "$CP" "$ORIG_JAR")
RUN_CP="$WORKDIR/target/classes:$PATCHED_JAR:$FILTERED_CP"

# ---- probe helper -----------------------------------------------------------
# args: scenario_name  path  creds(yes|no)  -> sets global PROBE_CODE
probe() {
  local path="$1"; local creds="$2"
  local hdr
  if [ "$creds" = "yes" ]; then
    hdr=$(curl -s -o /dev/null -w "%{http_code}" -u admin:secret "http://localhost:${PORT}${path}" 2>/dev/null || true)
  else
    hdr=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${PORT}${path}" 2>/dev/null || true)
  fi
  PROBE_CODE="$hdr"
}

# ---- run one scenario -------------------------------------------------------
# args: name  props_file  ready_path  -- runs app, calls back probes via env
# Uses globals: SCENARIO_RESULT
run_scenario() {
  local name="$1"; local props="$2"; local ready_path="$3"
  local app_log="$WORKDIR/${name}_app.log"
  rm -f "$app_log"
  cp "$props" "$WORKDIR/src/main/resources/application.properties"
  # rebuild resources into target/classes so the new properties are on classpath
  ( cd "$WORKDIR" && mvn -q -e process-resources -Dmaven.repo.local="$M2" ) >> "$WORKDIR/build.log" 2>&1 || true

  fuser -k "${PORT}/tcp" 2>/dev/null || true
  sleep 1

  ( cd "$WORKDIR" && java -cp "$RUN_CP" repro.App ) > "$app_log" 2>&1 &
  local pid=$!
  local ready=no
  for i in $(seq 1 60); do
    if ! kill -0 "$pid" 2>/dev/null; then log "$name: app died"; break; fi
    local code; code="$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:${PORT}${ready_path}" 2>/dev/null || true)"
    if [ -n "$code" ] && [ "$code" != "000" ]; then ready=yes; break; fi
    sleep 1
  done

  SCENARIO_RESULT="fail"
  if [ "$ready" = "yes" ]; then
    SCENARIO_RESULT="ok"
  fi
  SCENARIO_PID="$pid"
}

cleanup() { kill "$1" 2>/dev/null || true; sleep 1; kill -9 "$1" 2>/dev/null || true; fuser -k "${PORT}/tcp" 2>/dev/null || true; }

# ---- property files for each scenario ---------------------------------------
mkprops() { # writes to $1: ctx, authpath(optional)
  local file="$1"; local ctx="$2"; local authpath="$3"
  {
    echo "camel.server.enabled=true"
    echo "camel.server.path=${ctx}"
    echo "camel.server.port=${PORT}"
    echo "camel.server.authenticationEnabled=true"
    echo "camel.server.basicPropertiesFile=auth.properties"
    if [ -n "$authpath" ]; then echo "camel.server.authenticationPath=${authpath}"; fi
  } > "$file"
}

mkprops "$WORKDIR/props_A.properties" "/api" ""
mkprops "$WORKDIR/props_B.properties" "/api" "/api"
mkprops "$WORKDIR/props_C.properties" "/api" "/api/*"
mkprops "$WORKDIR/props_D.properties" "/api" "/secure/*"
mkprops "$WORKDIR/props_E.properties" "/"   ""

# ---- assertion matrix -------------------------------------------------------
# Each line: scenario | path | creds | expected
ASSERTS=""
add_assert() {
  # $1 scenario, $2 path, $3 creds, $4 expected
  ASSERTS="${ASSERTS}${1}|${2}|${3}|${4}"$'\n'
}
add_assert A "/api/hello"        no  401
add_assert A "/api/hello"        yes 200
add_assert A "/api/public"       no  401
add_assert B "/api/hello"        no  401
add_assert B "/api/hello"        yes 200
add_assert C "/api/hello"        no  401
add_assert C "/api/hello"        yes 200
add_assert D "/api/secure/data"  no  401
add_assert D "/api/secure/data"  yes 200
add_assert D "/api/public"       no  200
add_assert E "/hello"            no  401
add_assert E "/hello"            yes 200

declare -A SCENARIO_PROPS
SCENARIO_PROPS[A]="$WORKDIR/props_A.properties"
SCENARIO_PROPS[B]="$WORKDIR/props_B.properties"
SCENARIO_PROPS[C]="$WORKDIR/props_C.properties"
SCENARIO_PROPS[D]="$WORKDIR/props_D.properties"
SCENARIO_PROPS[E]="$WORKDIR/props_E.properties"
declare -A SCENARIO_READY
SCENARIO_READY[A]="/api/hello"
SCENARIO_READY[B]="/api/hello"
SCENARIO_READY[C]="/api/hello"
SCENARIO_READY[D]="/api/secure/data"
SCENARIO_READY[E]="/hello"

PASS=0; FAIL=0
echo "=============================================="
echo " CVE-2026-40022 fix verification (patched 4.14.5)"
echo "=============================================="
printf "%-3s %-22s %-6s %-8s %-8s %s\n" "SC" "PATH" "CREDS" "EXPECT" "GOT" "RESULT"

for scenario in A B C D E; do
  run_scenario "$scenario" "${SCENARIO_PROPS[$scenario]}" "${SCENARIO_READY[$scenario]}"
  if [ "$SCENARIO_RESULT" != "ok" ]; then
    log "scenario $scenario did not start (see $WORKDIR/${scenario}_app.log)"
  fi
  while IFS='|' read -r sc path creds expected; do
    [ "$sc" = "$scenario" ] || continue
    got="000"
    if [ "$SCENARIO_RESULT" = "ok" ]; then
      probe "$path" "$creds"; got="$PROBE_CODE"
    fi
    res="FAIL"; [ "$got" = "$expected" ] && res="PASS"
    [ "$res" = "PASS" ] && PASS=$((PASS+1)) || FAIL=$((FAIL+1))
    printf "%-3s %-22s %-6s %-8s %-8s %s\n" "$sc" "$path" "$creds" "$expected" "$got" "$res"
  done <<< "$ASSERTS"
  cleanup "$SCENARIO_PID" || true
done

echo "=============================================="
echo "PASS=$PASS FAIL=$FAIL"
echo "=============================================="

if [ "$FAIL" -eq 0 ] && [ "$PASS" -gt 0 ]; then
  log "VERDICT: PASS — fix closes CVE-2026-40022 and the explicit-prefix variant while preserving selective protection."
  exit 0
else
  log "VERDICT: FAIL — one or more assertions did not pass."
  exit 1
fi
