From a508fc4a8e63ec28d9c1516f60dce718cc10f330 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Fri, 7 Nov 2025 16:50:00 -0800 Subject: [PATCH] Remove permission_allowed hook docs, closes #2588 Refs #2528 --- docs/plugin_hooks.rst | 72 ++------------------------------------ tests/plugins/my_plugin.py | 4 +-- tests/test_html.py | 8 ++--- tests/test_plugins.py | 2 +- 4 files changed, 10 insertions(+), 76 deletions(-) diff --git a/docs/plugin_hooks.rst b/docs/plugin_hooks.rst index 93f7f476..118a6bde 100644 --- a/docs/plugin_hooks.rst +++ b/docs/plugin_hooks.rst @@ -1314,72 +1314,6 @@ This example plugin causes 0 results to be returned if ``?_nothing=1`` is added Example: `datasette-leaflet-freedraw `_ -.. _plugin_hook_permission_allowed: - -permission_allowed(datasette, actor, action, resource) ------------------------------------------------------- - -``datasette`` - :ref:`internals_datasette` - You can use this to access plugin configuration options via ``datasette.plugin_config(your_plugin_name)``, or to execute SQL queries. - -``actor`` - dictionary - The current actor, as decided by :ref:`plugin_hook_actor_from_request`. - -``action`` - string - The action to be performed, e.g. ``"edit-table"``. - -``resource`` - string or None - An identifier for the individual resource, e.g. the name of the table. - -Called to check that an actor has permission to perform an action on a resource. Can return ``True`` if the action is allowed, ``False`` if the action is not allowed or ``None`` if the plugin does not have an opinion one way or the other. - -Here's an example plugin which randomly selects if a permission should be allowed or denied, except for ``view-instance`` which always uses the default permission scheme instead. - -.. code-block:: python - - from datasette import hookimpl - import random - - - @hookimpl - def permission_allowed(action): - if action != "view-instance": - # Return True or False at random - return random.random() > 0.5 - # Returning None falls back to default permissions - -This function can alternatively return an awaitable function which itself returns ``True``, ``False`` or ``None``. You can use this option if you need to execute additional database queries using ``await datasette.execute(...)``. - -Here's an example that allows users to view the ``admin_log`` table only if their actor ``id`` is present in the ``admin_users`` table. It aso disallows arbitrary SQL queries for the ``staff.db`` database for all users. - -.. code-block:: python - - @hookimpl - def permission_allowed(datasette, actor, action, resource): - async def inner(): - if action == "execute-sql" and resource == "staff": - return False - if action == "view-table" and resource == ( - "staff", - "admin_log", - ): - if not actor: - return False - user_id = actor["id"] - result = await datasette.get_database( - "staff" - ).execute( - "select count(*) from admin_users where user_id = :user_id", - {"user_id": user_id}, - ) - return result.first()[0] > 0 - - return inner - -See :ref:`built-in permissions ` for a full list of permissions that are included in Datasette core. - -Example: `datasette-permissions-sql `_ - .. _plugin_hook_permission_resources_sql: permission_resources_sql(datasette, actor, action) @@ -1981,16 +1915,16 @@ This example adds a new database action for creating a table, if the user has th .. code-block:: python from datasette import hookimpl + from datasette.resources import DatabaseResource @hookimpl def database_actions(datasette, actor, database): async def inner(): - if not await datasette.permission_allowed( + if not await datasette.allowed( actor, "edit-schema", - resource=database, - default=False, + resource=DatabaseResource("database"), ): return [] return [ diff --git a/tests/plugins/my_plugin.py b/tests/plugins/my_plugin.py index 1435ce28..96a8b4d7 100644 --- a/tests/plugins/my_plugin.py +++ b/tests/plugins/my_plugin.py @@ -469,7 +469,7 @@ def register_actions(datasette): description="View a collection", resource_class=DatabaseResource, ), - # Test actions for test_hook_permission_allowed (global actions - no resource_class) + # Test actions for test_hook_custom_allowed (global actions - no resource_class) Action( name="this_is_allowed", abbr=None, @@ -553,7 +553,7 @@ def register_actions(datasette): def permission_resources_sql(datasette, actor, action): from datasette.permissions import PermissionSQL - # Handle test actions used in test_hook_permission_allowed + # Handle test actions used in test_hook_custom_allowed if action == "this_is_allowed": return PermissionSQL.allow(reason="test plugin allows this_is_allowed") elif action == "this_is_denied": diff --git a/tests/test_html.py b/tests/test_html.py index 9997279b..35b839ec 100644 --- a/tests/test_html.py +++ b/tests/test_html.py @@ -935,7 +935,7 @@ async def test_edit_sql_link_on_canned_queries(ds_client, path, expected): @pytest.mark.parametrize( - "permission_allowed", + "has_permission", [ pytest.param( True, @@ -943,15 +943,15 @@ async def test_edit_sql_link_on_canned_queries(ds_client, path, expected): False, ], ) -def test_edit_sql_link_not_shown_if_user_lacks_permission(permission_allowed): +def test_edit_sql_link_not_shown_if_user_lacks_permission(has_permission): with make_app_client( config={ - "allow_sql": None if permission_allowed else {"id": "not-you"}, + "allow_sql": None if has_permission else {"id": "not-you"}, "databases": {"fixtures": {"queries": {"simple": "select 1 + 1"}}}, } ) as client: response = client.get("/fixtures/simple") - if permission_allowed: + if has_permission: assert "Edit SQL" in response.text else: assert "Edit SQL" not in response.text diff --git a/tests/test_plugins.py b/tests/test_plugins.py index 971b7e82..4a8c60d7 100644 --- a/tests/test_plugins.py +++ b/tests/test_plugins.py @@ -677,7 +677,7 @@ async def test_existing_scope_actor_respected(ds_client): ("this_is_denied_async", False), ], ) -async def test_hook_permission_allowed(action, expected): +async def test_hook_custom_allowed(action, expected): # Test actions and permission logic are defined in tests/plugins/my_plugin.py ds = Datasette(plugins_dir=PLUGINS_DIR) await ds.invoke_startup()