From 12c0bc09cc4bf9addde20f3d46613de11f27c641 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Fri, 24 Jul 2020 15:54:41 -0700 Subject: [PATCH] /-/allow-debug tool, closes #908 --- datasette/app.py | 4 ++ datasette/templates/allow_debug.html | 58 ++++++++++++++++++++++++++++ datasette/views/special.py | 38 ++++++++++++++++++ docs/authentication.rst | 7 ++++ tests/test_permissions.py | 18 +++++++++ 5 files changed, 125 insertions(+) create mode 100644 datasette/templates/allow_debug.html diff --git a/datasette/app.py b/datasette/app.py index bafee857..fa273df0 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -35,6 +35,7 @@ from .views.special import ( PatternPortfolioView, AuthTokenView, LogoutView, + AllowDebugView, PermissionsDebugView, MessagesDebugView, ) @@ -869,6 +870,9 @@ class Datasette: add_route( MessagesDebugView.as_view(self), r"/-/messages$", ) + add_route( + AllowDebugView.as_view(self), r"/-/allow-debug$", + ) add_route( PatternPortfolioView.as_view(self), r"/-/patterns$", ) diff --git a/datasette/templates/allow_debug.html b/datasette/templates/allow_debug.html new file mode 100644 index 00000000..05e3dd90 --- /dev/null +++ b/datasette/templates/allow_debug.html @@ -0,0 +1,58 @@ +{% extends "base.html" %} + +{% block title %}Debug allow rules{% endblock %} + +{% block extra_head %} + +{% endblock %} + +{% block content %} + +

Debug allow rules

+ +

Use this tool to try out different actor and allow combinations. See Defining permissions with "allow" blocks for documentation.

+ +
+
+

+ +
+
+

+ +
+
+ +
+
+ +{% if error %}

{{ error }}

{% endif %} + +{% if result == "True" %}

Result: allow

{% endif %} + +{% if result == "False" %}

Result: deny

{% endif %} + +{% endblock %} diff --git a/datasette/views/special.py b/datasette/views/special.py index ed5a36f7..3067b0d1 100644 --- a/datasette/views/special.py +++ b/datasette/views/special.py @@ -1,5 +1,6 @@ import json from datasette.utils.asgi import Response, Forbidden +from datasette.utils import actor_matches_allow from .base import BaseView import secrets @@ -107,6 +108,43 @@ class PermissionsDebugView(BaseView): ) +class AllowDebugView(BaseView): + name = "allow_debug" + + def __init__(self, datasette): + self.ds = datasette + + async def get(self, request): + errors = [] + actor_input = request.args.get("actor") or '{"id": "root"}' + try: + actor = json.loads(actor_input) + actor_input = json.dumps(actor, indent=4) + except json.decoder.JSONDecodeError as ex: + errors.append("Actor JSON error: {}".format(ex)) + allow_input = request.args.get("allow") or '{"id": "*"}' + try: + allow = json.loads(allow_input) + allow_input = json.dumps(allow, indent=4) + except json.decoder.JSONDecodeError as ex: + errors.append("Allow JSON error: {}".format(ex)) + + result = None + if not errors: + result = str(actor_matches_allow(actor, allow)) + + return await self.render( + ["allow_debug.html"], + request, + { + "result": result, + "error": "\n\n".join(errors) if errors else "", + "actor_input": actor_input, + "allow_input": allow_input, + }, + ) + + class MessagesDebugView(BaseView): name = "messages_debug" diff --git a/docs/authentication.rst b/docs/authentication.rst index a2b1276b..648d40f8 100644 --- a/docs/authentication.rst +++ b/docs/authentication.rst @@ -157,6 +157,13 @@ You can specify that unauthenticated actors (from anynomous HTTP requests) shoul Allow keys act as an "or" mechanism. An actor will be able to execute the query if any of their JSON properties match any of the values in the corresponding lists in the ``allow`` block. +.. _AllowDebugView: + +The /-/allow-debug tool +----------------------- + +The ``/-/allow-debug`` tool lets you try out different ``"action"`` blocks against different ``"actor"`` JSON objects. You can try that out here: https://latest.datasette.io/-/allow-debug + .. _authentication_permissions_metadata: Configuring permissions in metadata.json diff --git a/tests/test_permissions.py b/tests/test_permissions.py index 90e58a27..e66ba53b 100644 --- a/tests/test_permissions.py +++ b/tests/test_permissions.py @@ -2,6 +2,7 @@ from .fixtures import app_client, assert_permissions_checked, make_app_client from bs4 import BeautifulSoup as Soup import copy import pytest +import urllib @pytest.mark.parametrize( @@ -312,6 +313,23 @@ def test_permissions_debug(app_client): ] == checks +@pytest.mark.parametrize( + "actor,allow,expected_fragment", + [ + ('{"id":"root"}', "{}", "Result: deny"), + ('{"id":"root"}', '{"id": "*"}', "Result: allow"), + ('{"', '{"id": "*"}', "Actor JSON error"), + ('{"id":"root"}', '"*"}', "Allow JSON error"), + ], +) +def test_allow_debug(app_client, actor, allow, expected_fragment): + response = app_client.get( + "/-/allow-debug?" + urllib.parse.urlencode({"actor": actor, "allow": allow}) + ) + assert 200 == response.status + assert expected_fragment in response.text + + @pytest.mark.parametrize( "allow,expected", [({"id": "root"}, 403), ({"id": "root", "unauthenticated": True}, 200),],