From cd23908d740ba006a3b73c90246f4bd29d0d360a Mon Sep 17 00:00:00 2001 From: Sean Ahern Date: Mon, 28 Sep 2026 13:21:43 -0400 Subject: [PATCH] Block downloads for view-only accounts --- driving_log/templates/dmv.html | 4 ++-- driving_log/templates/imports.html | 4 ++-- driving_log/web.py | 2 ++ tests/test_web.py | 15 ++++++--------- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/driving_log/templates/dmv.html b/driving_log/templates/dmv.html index d1fdf37..6cd6c96 100644 --- a/driving_log/templates/dmv.html +++ b/driving_log/templates/dmv.html @@ -21,10 +21,10 @@

DMV driving record

{% endif %} -
+ {% if not is_read_only %}
Download filled DL-4A PDF -
+
{% endif %} {% if not is_read_only %}

Supervising drivers

{% for profile in profiles %} diff --git a/driving_log/templates/imports.html b/driving_log/templates/imports.html index 850b983..eaebc86 100644 --- a/driving_log/templates/imports.html +++ b/driving_log/templates/imports.html @@ -2,8 +2,8 @@ {% block content %}

Imports and exports

- Download CSV backup - + {% if not is_read_only %}Download CSV backup + {% endif %} DMV driving record {% if not is_read_only %}Archives{% endif %} {% if not is_read_only %}
diff --git a/driving_log/web.py b/driving_log/web.py index d3cb9ba..ee44669 100644 --- a/driving_log/web.py +++ b/driving_log/web.py @@ -127,6 +127,7 @@ def _duration_parts(minutes: int) -> dict[str, int]: ) AUTH_SUPERVISORS = (ACCOUNTS["sean"], ACCOUNTS["jen"], ACCOUNTS["bethany"]) MUTATION_PAGE_PATHS = frozenset({"/drives/new", "/live", "/archives", "/locations"}) +VIEW_ONLY_DOWNLOAD_PATHS = frozenset({"/csv/export", "/dmv/export"}) def _part_of_day(value: str | datetime, timezone_name: str) -> str: @@ -322,6 +323,7 @@ async def authenticate_and_prevent_stale_html( and ( request.method not in {"GET", "HEAD", "OPTIONS"} or _is_mutation_page(request.url.path) + or request.url.path in VIEW_ONLY_DOWNLOAD_PATHS ) ): return JSONResponse({"detail": "view-only account"}, status_code=403) diff --git a/tests/test_web.py b/tests/test_web.py index c6cd94d..c4e1689 100644 --- a/tests/test_web.py +++ b/tests/test_web.py @@ -271,7 +271,7 @@ async def scenario() -> None: self.run_async(scenario) - def test_daniel_has_read_only_access_and_can_export_records(self) -> None: + def test_daniel_has_read_only_access_without_downloads(self) -> None: settings = Settings( state_dir=self.settings.state_dir, database_path=self.settings.database_path, @@ -352,15 +352,12 @@ async def scenario() -> None: self.assertEqual( (await daniel.get(f"/drives/{drive_id}/edit")).status_code, 403 ) - self.assertEqual((await daniel.get("/csv/export")).status_code, 200) - self.assertEqual((await daniel.get("/dmv/export")).status_code, 200) + self.assertEqual((await daniel.get("/csv/export")).status_code, 403) dmv_page = await daniel.get("/dmv") - self.assertIn("Download filled DL-4A PDF", dmv_page.text) - self.assertIn( - 'href="/dmv/export" data-file-export ' - 'data-export-filename="Daniel-driving-log-DL-4A.pdf"', - dmv_page.text, - ) + self.assertEqual((await daniel.get("/dmv/export")).status_code, 403) + self.assertNotIn("Download filled DL-4A PDF", dmv_page.text) + imports = await daniel.get("/imports") + self.assertNotIn("Download CSV backup", imports.text) self.assertNotIn("Add license information", dmv_page.text) self.assertEqual( (