Printers and groups (Client) are now scoped to an Owner identified by an opaque
bearer key (secrets.token_urlsafe(32)) stored in an httponly cookie, defaulting
to temporary. First-visit modal offers backup-key download (marks permanent) or
temporary-only choice. /session/restore re-attaches a fresh browser to a saved
key. Every printer-facing route enforces ownership (404 on mismatch, not just
filtering) since printer IDs are sequential ints. Drivers stay global/shared.
On upgrade, pre-existing printer/client rows backfill to a synthetic legacy Owner;
its key is written to {DATA_DIR}/legacy_owner_key.txt for manual restore.
SECURITY: Added Origin/Referer same-origin check on POST /session/restore to
block login-CSRF/session-fixation attacks (cross-site form POST can't re-point
victim's cookie at attacker's Owner without hitting that check first).
Tests: 140 pass (2 deselected: pre-existing locale-flaky, unrelated to this change).
Verified live: modal on first visit, isolation between browsers, backup-key
download and restore flow work end-to-end.
Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
4.3 KiB
CLAUDE.md
Guide for Claude Code (claude.ai/code) work in repo.
Commands
Run tests:
pytest tests/
pytest tests/unit/test_inf_parser.py # single test file
pytest tests/ -k "test_name" # single test by name
Run dev server:
export DATA_DIR=/tmp/imptune_data
export COOKIE_SECURE=false # plain HTTP — omit if serving behind TLS
uvicorn imptune.main:app --reload --port 8000
Docker:
docker-compose up
docker build -t imptune .
Install deps:
pip install -r requirements.txt -r requirements-dev.txt
Architecture
ImpTune make printer deploy packages (.intunewin for Intune, .zip for NinjaRMM) from Windows driver ZIPs + web UI. No external services — single FastAPI + SQLite + Docker volume.
Request flow:
- Driver upload →
api/drivers.py→services/inf_parser.pyparse INF →storage/driver_store.pystore by SHA256 → PeeweeDriverrecord (shared/global — visible to every Owner) - Printer config →
api/printers.py→db/models.pyPrinterrecord (links Driver FK, scoped torequest.state.owner) - Icon upload →
api/icons.py→ Pillow validate PNG 256×256 → SHA256 storage →Iconrecord - Package export →
api/packages.py→generators/script_generator.pyrender Jinja2 PS1 templates →generators/intunewin_builder.pyencrypt ZIP (AES-256-CBC + HMAC-SHA256)
Key modules:
imptune/config.py—DATA_DIR,DB_PATH,DRIVERS_DIR,ICONS_DIR,COOKIE_SECUREfrom envimptune/db/database.py— SQLite WAL mode +foreign_keys=1; all models inheritBaseModel;init_db()also backfillsowner_idon pre-per-owner-scoping DBs into a synthetic legacyOwner(key written to{DATA_DIR}/legacy_owner_key.txt)imptune/services/session.py—OwnerSessionMiddlewareresolvesrequest.state.ownerfrom theimptune_owner_keycookie, creating one on first visit (skips/health)imptune/services/inf_parser.py— auto-detect encoding (UTF-16/UTF-8/cp1252), resolve%TOKEN%from[Strings], handle multi-model INFsimptune/generators/intunewin_builder.py— Python-native.intunewin(ZIP-in-ZIP); IV 16 bytes (not 32); match reference tool1.8.6.0outputimptune/templates/scripts/— Jinja2 templates forinstall.ps1,uninstall.ps1,detect.ps1
Per-owner storage: Printer/Client (groups) are scoped to an Owner identified by an opaque bearer key in a cookie — no accounts. Driver stays global/shared. Every route taking a printer_id/client_id must filter/check .owner == request.state.owner (404, not 403, on mismatch) — printer IDs are small sequential ints, so a list-only filter isn't enough. Onboarding modal (templates/base.html, gated on request.state.is_new_owner) offers "download backup key" (GET /session/key/download, marks Owner.is_permanent) vs. temporary; /session/restore re-attaches a browser to a previously downloaded key. In tests, use the owner fixture (tests/conftest.py) when creating Printer/Client rows directly via the ORM so the client fixture's cookie-scoped requests can see them.
UI stack: Pico CSS + HTMX 2 + Alpine.js 3 + Jinja2 server-side templates.
HTMX pattern: Forms hx-post, swap #driver-list / #printer-list / #client-list targets. Errors return inline HTML fragments (HTTP 400/409) via _error_response(). Success return partials from templates/partials/.
PowerShell install script notes:
- WOW64 64-bit relaunch guard (Intune run 32-bit,
pnputilneed 64-bit) - UAC self-elevation for user context (SYSTEM context skip)
- Two-step:
pnputil /add-driverthenAdd-PrinterDriver+Add-PrinterPort+Add-Printer - All idempotent (
-ErrorAction SilentlyContinue)
Test Setup
conftest.py monkeypatch config.DATA_DIR + config.DB_PATH to temp dir per test. client fixture yield TestClient(app) with isolated SQLite. E2E in tests/e2e/ use Playwright.
Environment Variables
| Var | Default | Purpose |
|---|---|---|
DATA_DIR |
/data |
Storage root (DB + drivers + icons) |
PORT |
8000 |
Server port |
COOKIE_SECURE |
true |
Owner-session cookie Secure flag. Set false for local plain-HTTP dev (uvicorn --reload) or the browser drops the cookie and a new Owner is created on every request. |