mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
New allowed_resources_sql plugin hook and debug tools (#2505)
* allowed_resources_sql plugin hook and infrastructure * New methods for checking permissions with the new system * New /-/allowed and /-/check and /-/rules special endpoints Still needs to be integrated more deeply into Datasette, especially for listing visible tables. Refs: #2502 --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
85da8474d4
commit
27084caa04
20 changed files with 3381 additions and 27 deletions
|
|
@ -1050,6 +1050,62 @@ It also provides an interface for running hypothetical permission checks against
|
|||
|
||||
This is designed to help administrators and plugin authors understand exactly how permission checks are being carried out, in order to effectively configure Datasette's permission system.
|
||||
|
||||
.. _AllowedResourcesView:
|
||||
|
||||
Allowed resources view
|
||||
======================
|
||||
|
||||
The ``/-/allowed`` endpoint displays resources that the current actor can access for a supplied ``action`` query string argument.
|
||||
|
||||
This endpoint provides an interactive HTML form interface. Add ``.json`` to the URL path (e.g. ``/-/allowed.json``) to get the raw JSON response instead.
|
||||
|
||||
Pass ``?action=view-table`` (or another action) to select the action. Optional ``parent=`` and ``child=`` query parameters can narrow the results to a specific database/table pair.
|
||||
|
||||
This endpoint is publicly accessible to help users understand their own permissions. However, potentially sensitive fields (``reason`` and ``source_plugin``) are only included in responses for users with the ``permissions-debug`` permission.
|
||||
|
||||
Datasette includes helper endpoints for exploring the action-based permission resolver:
|
||||
|
||||
``/-/allowed``
|
||||
Returns a paginated list of resources that the current actor is allowed to access for a given action. Pass ``?action=view-table`` (or another action) to select the action, and optional ``parent=``/``child=`` query parameters to narrow the results to a specific database/table pair.
|
||||
|
||||
``/-/rules``
|
||||
Lists the raw permission rules (both allow and deny) contributing to each resource for the supplied action. This includes configuration-derived and plugin-provided rules. **Requires the permissions-debug permission** (only available to the root user by default).
|
||||
|
||||
``/-/check``
|
||||
Evaluates whether the current actor can perform ``action`` against an optional ``parent``/``child`` resource tuple, returning the winning rule and reason.
|
||||
|
||||
These endpoints work in conjunction with :ref:`plugin_hook_permission_resources_sql` and make it easier to verify that configuration allow blocks and plugins are behaving as intended.
|
||||
|
||||
All three endpoints support both HTML and JSON responses. Visit the endpoint directly for an interactive HTML form interface, or add ``.json`` to the URL for a raw JSON response.
|
||||
|
||||
**Security note:** The ``/-/check`` and ``/-/allowed`` endpoints are publicly accessible to help users understand their own permissions. However, potentially sensitive fields (``reason`` and ``source_plugin``) are only included in responses for users with the ``permissions-debug`` permission. The ``/-/rules`` endpoint requires the ``permissions-debug`` permission for all access.
|
||||
|
||||
.. _PermissionRulesView:
|
||||
|
||||
Permission rules view
|
||||
======================
|
||||
|
||||
The ``/-/rules`` endpoint displays all permission rules (both allow and deny) for each candidate resource for the requested action.
|
||||
|
||||
This endpoint provides an interactive HTML form interface. Add ``.json`` to the URL path (e.g. ``/-/rules.json?action=view-table``) to get the raw JSON response instead.
|
||||
|
||||
Pass ``?action=`` as a query parameter to specify which action to check.
|
||||
|
||||
**Requires the permissions-debug permission** - this endpoint returns a 403 Forbidden error for users without this permission. The :ref:`root user <authentication_root>` has this permission by default.
|
||||
|
||||
.. _PermissionCheckView:
|
||||
|
||||
Permission check view
|
||||
======================
|
||||
|
||||
The ``/-/check`` endpoint evaluates a single action/resource pair and returns information indicating whether the access was allowed along with diagnostic information.
|
||||
|
||||
This endpoint provides an interactive HTML form interface. Add ``.json`` to the URL path (e.g. ``/-/check.json?action=view-instance``) to get the raw JSON response instead.
|
||||
|
||||
Pass ``?action=`` to specify the action to check, and optional ``?parent=`` and ``?child=`` parameters to specify the resource.
|
||||
|
||||
This endpoint is publicly accessible to help users understand their own permissions. However, potentially sensitive fields (``reason`` and ``source_plugin``) are only included in responses for users with the ``permissions-debug`` permission.
|
||||
|
||||
.. _authentication_ds_actor:
|
||||
|
||||
The ds_actor cookie
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ General guidelines
|
|||
* **main should always be releasable**. Incomplete features should live in branches. This ensures that any small bug fixes can be quickly released.
|
||||
* **The ideal commit** should bundle together the implementation, unit tests and associated documentation updates. The commit message should link to an associated issue.
|
||||
* **New plugin hooks** should only be shipped if accompanied by a separate release of a non-demo plugin that uses them.
|
||||
* **New user-facing views and documentation** should be added or updated alongside their implementation. The `/docs` folder includes pages for plugin hooks and built-in views—please ensure any new hooks or views are reflected there so the documentation tests continue to pass.
|
||||
|
||||
.. _devenvironment:
|
||||
|
||||
|
|
|
|||
|
|
@ -1290,12 +1290,13 @@ Here's an example that allows users to view the ``admin_log`` table only if thei
|
|||
if not actor:
|
||||
return False
|
||||
user_id = actor["id"]
|
||||
return await datasette.get_database(
|
||||
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
|
||||
|
||||
|
|
@ -1303,6 +1304,184 @@ See :ref:`built-in permissions <permissions>` for a full list of permissions tha
|
|||
|
||||
Example: `datasette-permissions-sql <https://datasette.io/plugins/datasette-permissions-sql>`_
|
||||
|
||||
.. _plugin_hook_permission_resources_sql:
|
||||
|
||||
permission_resources_sql(datasette, actor, action)
|
||||
-------------------------------------------------
|
||||
|
||||
``datasette`` - :ref:`internals_datasette`
|
||||
Access to the Datasette instance.
|
||||
|
||||
``actor`` - dictionary or None
|
||||
The current actor dictionary. ``None`` for anonymous requests.
|
||||
|
||||
``action`` - string
|
||||
The permission action being evaluated. Examples include ``"view-table"`` or ``"insert-row"``.
|
||||
|
||||
Return value
|
||||
A :class:`datasette.utils.permissions.PluginSQL` object, ``None`` or an iterable of ``PluginSQL`` objects.
|
||||
|
||||
Datasette's action-based permission resolver calls this hook to gather SQL rows describing which
|
||||
resources an actor may access (``allow = 1``) or should be denied (``allow = 0``) for a specific action.
|
||||
Each SQL snippet should return ``parent``, ``child``, ``allow`` and ``reason`` columns. Any bound parameters
|
||||
supplied via ``PluginSQL.params`` are automatically namespaced per plugin.
|
||||
|
||||
|
||||
Permission plugin examples
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
These snippets show how to use the new ``permission_resources_sql`` hook to
|
||||
contribute rows to the action-based permission resolver. Each hook receives the
|
||||
current actor dictionary (or ``None``) and must return ``None`` or an instance or list of
|
||||
``datasette.utils.permissions.PluginSQL`` (or a coroutine that resolves to that).
|
||||
|
||||
Allow Alice to view a specific table
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This plugin grants the actor with ``id == "alice"`` permission to perform the
|
||||
``view-table`` action against the ``sales`` table inside the ``accounting`` database.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from datasette import hookimpl
|
||||
from datasette.utils.permissions import PluginSQL
|
||||
|
||||
|
||||
@hookimpl
|
||||
def permission_resources_sql(datasette, actor, action):
|
||||
if action != "view-table":
|
||||
return None
|
||||
if not actor or actor.get("id") != "alice":
|
||||
return None
|
||||
|
||||
return PluginSQL(
|
||||
source="alice_sales_allow",
|
||||
sql="""
|
||||
SELECT
|
||||
'accounting' AS parent,
|
||||
'sales' AS child,
|
||||
1 AS allow,
|
||||
'alice can view accounting/sales' AS reason
|
||||
""",
|
||||
params={},
|
||||
)
|
||||
|
||||
Restrict execute-sql to a database prefix
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Only allow ``execute-sql`` against databases whose name begins with
|
||||
``analytics_``. This shows how to use parameters that the permission resolver
|
||||
will pass through to the SQL snippet.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from datasette import hookimpl
|
||||
from datasette.utils.permissions import PluginSQL
|
||||
|
||||
|
||||
@hookimpl
|
||||
def permission_resources_sql(datasette, actor, action):
|
||||
if action != "execute-sql":
|
||||
return None
|
||||
|
||||
return PluginSQL(
|
||||
source="analytics_execute_sql",
|
||||
sql="""
|
||||
SELECT
|
||||
parent,
|
||||
NULL AS child,
|
||||
1 AS allow,
|
||||
'execute-sql allowed for analytics_*' AS reason
|
||||
FROM catalog_databases
|
||||
WHERE database_name LIKE :prefix
|
||||
""",
|
||||
params={
|
||||
"prefix": "analytics_%",
|
||||
},
|
||||
)
|
||||
|
||||
Read permissions from a custom table
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
This example stores grants in an internal table called ``permission_grants``
|
||||
with columns ``(actor_id, action, parent, child, allow, reason)``.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from datasette import hookimpl
|
||||
from datasette.utils.permissions import PluginSQL
|
||||
|
||||
|
||||
@hookimpl
|
||||
def permission_resources_sql(datasette, actor, action):
|
||||
if not actor:
|
||||
return None
|
||||
|
||||
return PluginSQL(
|
||||
source="permission_grants_table",
|
||||
sql="""
|
||||
SELECT
|
||||
parent,
|
||||
child,
|
||||
allow,
|
||||
COALESCE(reason, 'permission_grants table') AS reason
|
||||
FROM permission_grants
|
||||
WHERE actor_id = :actor_id
|
||||
AND action = :action
|
||||
""",
|
||||
params={
|
||||
"actor_id": actor.get("id"),
|
||||
"action": action,
|
||||
},
|
||||
)
|
||||
|
||||
Default deny with an exception
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Combine a root-level deny with a specific table allow for trusted users.
|
||||
The resolver will automatically apply the most specific rule.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from datasette import hookimpl
|
||||
from datasette.utils.permissions import PluginSQL
|
||||
|
||||
|
||||
TRUSTED = {"alice", "bob"}
|
||||
|
||||
|
||||
@hookimpl
|
||||
def permission_resources_sql(datasette, actor, action):
|
||||
if action != "view-table":
|
||||
return None
|
||||
|
||||
actor_id = (actor or {}).get("id")
|
||||
|
||||
if actor_id not in TRUSTED:
|
||||
return PluginSQL(
|
||||
source="view_table_root_deny",
|
||||
sql="""
|
||||
SELECT NULL AS parent, NULL AS child, 0 AS allow,
|
||||
'default deny view-table' AS reason
|
||||
""",
|
||||
params={},
|
||||
)
|
||||
|
||||
return PluginSQL(
|
||||
source="trusted_allow",
|
||||
sql="""
|
||||
SELECT NULL AS parent, NULL AS child, 0 AS allow,
|
||||
'default deny view-table' AS reason
|
||||
UNION ALL
|
||||
SELECT 'reports' AS parent, 'daily_metrics' AS child, 1 AS allow,
|
||||
'trusted user access' AS reason
|
||||
""",
|
||||
params={"actor_id": actor_id},
|
||||
)
|
||||
|
||||
The ``UNION ALL`` ensures the deny rule is always present, while the second row
|
||||
adds the exception for trusted users.
|
||||
|
||||
.. _plugin_hook_register_magic_parameters:
|
||||
|
||||
register_magic_parameters(datasette)
|
||||
|
|
|
|||
|
|
@ -224,6 +224,7 @@ If you run ``datasette plugins --all`` it will include default plugins that ship
|
|||
"hooks": [
|
||||
"actor_from_request",
|
||||
"permission_allowed",
|
||||
"permission_resources_sql",
|
||||
"register_permissions",
|
||||
"skip_csrf"
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue