Files
PacketStation/tests/server/test_audit.py
T

68 lines
2.5 KiB
Python
Raw Normal View History

from __future__ import annotations
import hashlib
import uuid
from datetime import UTC, datetime, timedelta, timezone
from pshelpers import needs_pg
from packetstation import audit, db
from packetstation.settings import Settings
AT = datetime(2026, 10, 15, 12, 0, 0, 123456, tzinfo=UTC)
def test_canonical_is_stable_and_normalised() -> None:
uid = uuid.uuid4()
a = audit.canonical({"at": AT, "action": "x", "user_id": uid, "detail": {"b": 1, "a": [1, 2]}, "ip": "10.0.0.1"})
denver = AT.astimezone(timezone(timedelta(hours=-6)))
b = audit.canonical(
{"detail": {"a": [1, 2], "b": 1}, "ip": "10.0.0.1/32", "user_id": str(uid), "action": "x", "at": denver}
)
assert a == b
assert b'"at":"2026-10-15T12:00:00.123456+00:00"' in a
assert b" " not in a
def test_row_hash_chains() -> None:
f1 = {"at": AT, "action": "one"}
h1 = audit.row_hash(None, f1)
assert h1 == hashlib.sha256(audit.canonical(f1)).digest()
h2 = audit.row_hash(h1, {"at": AT, "action": "two"})
assert h2 == hashlib.sha256(h1 + audit.canonical({"at": AT, "action": "two"})).digest()
assert audit.row_hash(None, {"at": AT, "action": "two"}) != h2
@needs_pg
def test_pg_chain_verifies_and_detects_tamper(pg_settings: Settings) -> None:
with db.connect_as("app", pg_settings, autocommit=True) as conn:
first = audit.append(
conn,
action="a.one",
user_id=uuid.uuid4(),
detail={"n": 1, "s": "x"},
ip="10.1.2.3",
packet_id=5,
subject_type="packet",
subject_id=5,
)
second = audit.append(conn, action="a.two", detail=None)
assert second.id > first.id
prev = conn.execute("SELECT prev_hash FROM audit_events WHERE id = %s", (second.id,)).fetchone()
assert bytes(prev[0]) == first.row_hash
ok, n, detail = audit.verify_chain(conn)
assert ok, detail
assert n == 2
# Tamper as the superuser with the trigger disabled, then verification must fail
import psycopg
from pshelpers import PG_URL
from psycopg.conninfo import make_conninfo
assert PG_URL
with psycopg.connect(make_conninfo(PG_URL, dbname=pg_settings.pg_database), autocommit=True) as su:
su.execute("ALTER TABLE audit_events DISABLE TRIGGER audit_events_append_only")
su.execute("UPDATE audit_events SET action = 'forged' WHERE id = %s", (first.id,))
with db.connect_as("app", pg_settings) as conn:
ok, _, detail = audit.verify_chain(conn)
assert not ok and "row_hash" in detail