From f57977a08f85da40bbe04c7b0dbb57ba03694a9d Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Tue, 4 Feb 2025 11:09:44 -0800 Subject: [PATCH] /-/permissions?filter=exclude-yours/only-yours - closes #2460 --- datasette/templates/permissions_debug.html | 6 +++ datasette/views/special.py | 17 ++++++- tests/test_permissions.py | 58 ++++++++++++++++++---- 3 files changed, 71 insertions(+), 10 deletions(-) diff --git a/datasette/templates/permissions_debug.html b/datasette/templates/permissions_debug.html index 5b2b67e1..558d16f2 100644 --- a/datasette/templates/permissions_debug.html +++ b/datasette/templates/permissions_debug.html @@ -112,6 +112,12 @@ debugPost.addEventListener('submit', function(ev) {

Recent permissions checks

+

+ {% if filter != "all" %}All{% else %}All{% endif %}, + {% if filter != "exclude-yours" %}Exclude yours{% else %}Exclude yours{% endif %}, + {% if filter != "only-yours" %}Only yours{% else %}Only yours{% endif %} +

+ {% for check in permission_checks %}

diff --git a/datasette/views/special.py b/datasette/views/special.py index 1db24d74..e6fbc9f3 100644 --- a/datasette/views/special.py +++ b/datasette/views/special.py @@ -121,12 +121,27 @@ class PermissionsDebugView(BaseView): await self.ds.ensure_permissions(request.actor, ["view-instance"]) if not await self.ds.permission_allowed(request.actor, "permissions-debug"): raise Forbidden("Permission denied") + filter_ = request.args.get("filter") or "all" + permission_checks = list(reversed(self.ds._permission_checks)) + if filter_ == "exclude-yours": + permission_checks = [ + check + for check in permission_checks + if (check["actor"] or {}).get("id") != request.actor["id"] + ] + elif filter_ == "only-yours": + permission_checks = [ + check + for check in permission_checks + if (check["actor"] or {}).get("id") == request.actor["id"] + ] return await self.render( ["permissions_debug.html"], request, # list() avoids error if check is performed during template render: { - "permission_checks": list(reversed(self.ds._permission_checks)), + "permission_checks": permission_checks, + "filter": filter_, "permissions": [ { "name": p.name, diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 31ba104b..69a8a0b1 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -371,12 +371,15 @@ def test_permissions_checked(app_client, path, permissions): @pytest.mark.asyncio -async def test_permissions_debug(ds_client): +@pytest.mark.parametrize("filter_", ("all", "exclude-yours", "only-yours")) +async def test_permissions_debug(ds_client, filter_): ds_client.ds._permission_checks.clear() assert (await ds_client.get("/-/permissions")).status_code == 403 # With the cookie it should work cookie = ds_client.actor_cookie({"id": "root"}) - response = await ds_client.get("/-/permissions", cookies={"ds_actor": cookie}) + response = await ds_client.get( + f"/-/permissions?filter={filter_}", cookies={"ds_actor": cookie} + ) assert response.status_code == 200 # Should have a select box listing permissions for fragment in ( @@ -398,17 +401,54 @@ async def test_permissions_debug(ds_client): else bool(div.select(".check-result-true")) ), "used_default": bool(div.select(".check-used-default")), + "actor": json.loads( + div.find( + "strong", string=lambda text: text and "Actor" in text + ).parent.text.split(": ", 1)[1] + ), } for div in check_divs ] - assert checks == [ - {"action": "permissions-debug", "result": True, "used_default": False}, - {"action": "view-instance", "result": None, "used_default": True}, - {"action": "debug-menu", "result": False, "used_default": True}, - {"action": "view-instance", "result": True, "used_default": True}, - {"action": "permissions-debug", "result": False, "used_default": True}, - {"action": "view-instance", "result": None, "used_default": True}, + expected_checks = [ + { + "action": "permissions-debug", + "result": True, + "used_default": False, + "actor": {"id": "root"}, + }, + { + "action": "view-instance", + "result": None, + "used_default": True, + "actor": {"id": "root"}, + }, + {"action": "debug-menu", "result": False, "used_default": True, "actor": None}, + { + "action": "view-instance", + "result": True, + "used_default": True, + "actor": None, + }, + { + "action": "permissions-debug", + "result": False, + "used_default": True, + "actor": None, + }, + { + "action": "view-instance", + "result": None, + "used_default": True, + "actor": None, + }, ] + if filter_ == "only-yours": + expected_checks = [ + check for check in expected_checks if check["actor"] is not None + ] + elif filter_ == "exclude-yours": + expected_checks = [check for check in expected_checks if check["actor"] is None] + assert checks == expected_checks @pytest.mark.asyncio