Add hostile fixture setup for authz testing

This commit is contained in:
2026-04-11 16:57:15 +02:00
parent ac217dab53
commit 41595605b9
5 changed files with 145 additions and 0 deletions
@@ -0,0 +1,9 @@
{
"alice_email": "alice.m015@example.com",
"bob_email": "bob.m015@example.com",
"company_id": 1,
"job_id": 1,
"correspondence_id": 1,
"attachment_id": 1,
"api_base": "http://localhost:5202/api"
}
@@ -0,0 +1,47 @@
# M015 Hostile Fixture Setup
## Goal
Produce a trustworthy local runtime for cross-user authorization probes.
## Key discovery
The default development SQLite database in `JobTrackerApi/jobtracker.db` is **not** a trustworthy authorization-test target:
- it contains Identity and some later feature tables
- it does **not** contain the core domain tables needed for real cross-user job/correspondence/attachment probing
- current startup `Migrate()` behavior is therefore insufficient as the only hostile-test setup path
## Chosen fixture strategy
Use a dedicated clean SQLite fixture database created from the current EF model with `EnsureCreated()` semantics through a tiny helper program:
- helper project: `tools/hostile-fixture-db/`
- bootstrap script: `scripts/m015-hostile-fixture.sh`
This keeps the hostile runtime inside repo code and the real API host while avoiding ad-hoc manual SQL.
## What the helper does
- creates a clean `jobtracker.db` under a caller-provided data root
- builds the schema from the current `JobTrackerContext` model
- verifies the presence of core tables needed for M015:
- `Companies`
- `JobApplications`
- `Correspondences`
- `Attachments`
- `RuleSettings`
- `AspNetUsers`
## Runtime plan for S02
1. Run `scripts/m015-hostile-fixture.sh`.
2. Start the API with `Data__Root` pointing at that clean fixture root.
3. Mint an admin dev token against the fixture DB.
4. Create/reuse Alice and Bob through real API paths.
5. Seed Alice-owned company/job/correspondence/attachment fixtures through the real API.
6. Capture ids for cross-user hostile probes.
## Honest boundary
This slice establishes the trusted runtime path and fixture strategy. The full two-user seeded dataset and exploit execution belong in the next slice.
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
FIXTURE_ROOT="${FIXTURE_ROOT:-$REPO_ROOT/.tmp/m015-fixture}"
API_PORT="${API_PORT:-5202}"
API_BASE="http://localhost:${API_PORT}/api"
TMP_DIR="$(mktemp -d)"
trap 'rm -rf "$TMP_DIR"' EXIT
mkdir -p "$FIXTURE_ROOT"
echo "[m015] creating clean hostile-test db under $FIXTURE_ROOT"
dotnet run --project "$REPO_ROOT/tools/hostile-fixture-db/HostileFixtureDb.csproj" -- "$FIXTURE_ROOT" > "$TMP_DIR/db.json"
python3 - <<'PY' "$TMP_DIR/db.json"
import json, sys
payload=json.load(open(sys.argv[1]))
required={'Companies','JobApplications','Correspondences','Attachments','RuleSettings','AspNetUsers'}
missing=sorted(required-set(payload['tables']))
if missing:
raise SystemExit(f"missing tables after fixture db init: {', '.join(missing)}")
print('[m015] db tables ok')
PY
echo "[m015] ready to start API with:"
echo " Data__Root=$FIXTURE_ROOT ASPNETCORE_ENVIRONMENT=Development dotnet run --project JobTrackerApi/JobTrackerApi.csproj"
echo "[m015] API base: $API_BASE"
@@ -0,0 +1,11 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net9.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="../../JobTrackerBackend/JobTrackerBackend.csproj" />
</ItemGroup>
</Project>
+50
View File
@@ -0,0 +1,50 @@
using JobTrackerApi.Data;
using JobTrackerApi.Services;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Configuration;
using System.Text.Json;
if (args.Length < 1)
{
Console.Error.WriteLine("Usage: dotnet run --project tools/hostile-fixture-db -- <data-root>");
return 1;
}
var dataRoot = Path.GetFullPath(args[0]);
Directory.CreateDirectory(dataRoot);
var dbPath = Path.Combine(dataRoot, "jobtracker.db");
if (File.Exists(dbPath))
{
File.Delete(dbPath);
}
var config = new ConfigurationBuilder()
.AddInMemoryCollection(new Dictionary<string, string?>
{
["Data:Root"] = dataRoot,
})
.Build();
var currentUser = new StaticCurrentUserService(null);
var options = new DbContextOptionsBuilder<JobTrackerContext>()
.UseSqlite($"Data Source={dbPath}")
.Options;
await using var db = new JobTrackerContext(options, currentUser);
await db.Database.EnsureDeletedAsync();
await db.Database.EnsureCreatedAsync();
var tables = await db.Database.SqlQueryRaw<string>("SELECT name AS Value FROM sqlite_master WHERE type='table' ORDER BY name;").ToListAsync();
var payload = new
{
dataRoot,
dbPath,
tables,
};
Console.WriteLine(JsonSerializer.Serialize(payload));
return 0;
file sealed class StaticCurrentUserService(string? userId) : ICurrentUserService
{
public string? UserId => userId;
}