Explain permission decisions

This commit is contained in:
Simon Willison 2026-07-13 22:05:54 -07:00
commit 8c42f79323
7 changed files with 803 additions and 167 deletions

View file

@ -45,7 +45,10 @@ Using the "root" actor
Datasette currently leaves almost all forms of authentication to plugins - `datasette-auth-github <https://github.com/simonw/datasette-auth-github>`__ for example.
The one exception is the "root" account, which you can sign into while using Datasette on your local machine. The root user has **all permissions** - they can perform any action regardless of other permission rules.
The one exception is the "root" account, which you can sign into while using
Datasette on your local machine. The root user starts with **all permissions**:
Datasette contributes a global allow rule for every action. More specific deny
rules can still override that global rule.
The ``--root`` flag is designed for local development and testing. When you start Datasette with ``--root``, the root user automatically receives every permission, including:
@ -84,12 +87,14 @@ Click on that link and then visit ``http://127.0.0.1:8001/-/actor`` to confirm t
Permissions
===========
Datasette's permissions system is built around SQL queries. Datasette and its plugins construct SQL queries to resolve the list of resources that an actor cas access.
The key question the permissions system answers is this:
Is this **actor** allowed to perform this **action**, optionally against this particular **resource**?
Every permission decision can be understood in terms of those three values.
Datasette implements the decisions using SQL, but you do not need to understand
the generated SQL to configure or debug permissions.
**Actors** are :ref:`described above <authentication_actor>`.
An **action** is a string describing the action the actor would like to perform. A full list is :ref:`provided below <actions>` - examples include ``view-table`` and ``execute-sql``.
@ -138,7 +143,59 @@ This configuration will deny access to everyone except the user with ``id`` of `
How permissions are resolved
----------------------------
Datasette performs permission checks using the internal :ref:`datasette_allowed`, method which accepts keyword arguments for ``action``, ``resource`` and an optional ``actor``.
Permission rules describe an effect (``allow`` or ``deny``) at one of three
levels:
``resource``
A specific child resource, such as the ``analytics/sales`` table.
``parent``
A parent resource, such as the ``analytics`` database. A parent rule also
applies to its child resources.
``global``
Every resource for that action.
Datasette resolves matching rules from most specific to least specific:
#. Resource rules take precedence over parent and global rules.
#. Parent rules take precedence over global rules.
#. If both allow and deny rules match at the same level, deny takes precedence.
#. If no rule matches, access is denied.
This means a resource-level allow can provide an exception to a parent-level
deny. It also means that two plugins which disagree at the same level resolve
to deny.
.. list-table:: Permission rule examples
:header-rows: 1
* - Matching rules
- Result
- Explanation
* - Global allow
- Allow
- The global rule is the most specific matching rule.
* - Global allow, parent deny
- Deny
- The parent rule is more specific.
* - Parent deny, resource allow
- Allow
- The resource rule is more specific.
* - Resource allow and resource deny
- Deny
- Deny takes precedence at the same level.
* - No matching rules
- Deny
- Permissions default to deny when no rule applies.
The built-in public defaults are global allow rules for actions such as
``view-instance``, ``view-database`` and ``view-table``. They follow the same
precedence rules as configuration and plugin rules. The ``--default-deny``
option prevents Datasette from contributing those default allow rules.
Datasette performs checks using :ref:`datasette_allowed`, which accepts keyword
arguments for ``action``, ``resource`` and an optional ``actor``.
``resource`` should be an instance of the appropriate ``Resource`` subclass from :mod:`datasette.resources`—for example ``InstanceResource()``, ``DatabaseResource(database="...``)`` or ``TableResource(database="...", table="...")``. This defaults to ``InstanceResource()`` if not specified.
@ -149,12 +206,18 @@ resources were allowed or denied. The combined sources are:
* ``allow`` blocks configured in :ref:`datasette.yaml <authentication_permissions_config>`.
* :ref:`Actor restrictions <authentication_cli_create_token_restrict>` encoded into the actor dictionary or API token.
* The "root" user shortcut when ``--root`` (or :attr:`Datasette.root_enabled <datasette.app.Datasette.root_enabled>`) is active, replying ``True`` to all permission chucks unless configuration rules deny them at a more specific level.
* The "root" user rule when ``--root`` (or :attr:`Datasette.root_enabled <datasette.app.Datasette.root_enabled>`) is active. This is a global allow rule, so a more specific configuration deny can override it.
* Any additional SQL provided by plugins implementing :ref:`plugin_hook_permission_resources_sql`.
Datasette evaluates the SQL to determine if the requested ``resource`` is
included. Explicit deny rules returned by configuration or plugins will block
access even if other rules allowed it.
Actor restrictions are applied after the allow/deny rules. They act as an
additional allowlist: a restriction can remove access but cannot grant access
that the actor did not already have. See
:ref:`authentication_cli_create_token_restrict`.
Some actions have dependencies on other actions. These are evaluated as an
``AND`` condition. For example, ``execute-sql`` also requires
``view-database``: both decisions must be allowed for the final result to be
allowed.
.. _authentication_permissions_allow:
@ -1145,11 +1208,26 @@ The debug tool at ``/-/permissions`` is available to any actor with the ``permis
datasette -s permissions.permissions-debug true data.db
The page shows the permission checks that have been carried out by the Datasette instance.
The permission debug tools answer four different questions:
It also provides an interface for running hypothetical permission checks against a hypothetical actor. This is a useful way of confirming that your configured permissions work in the way you expect.
Why was this decision allowed or denied?
Use :ref:`PermissionCheckView`. It shows every matching rule, identifies
the winning specificity level, applies actor restrictions and evaluates
any required actions.
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.
Which resources can the current actor access?
Use :ref:`AllowedResourcesView` to view an access map for a selected
action.
Which raw rules did Datasette and its plugins contribute?
Use :ref:`PermissionRulesView` to inspect the rules before they are
resolved into decisions.
Which checks has this Datasette instance performed recently?
Use ``/-/permissions`` to view recent permission activity.
These tools are designed to help administrators and plugin authors understand
and confirm the effective permissions configuration.
These debug endpoints are exempt from the :ref:`JSON API stability promise <json_api_stability>` - their JSON shapes may change in future releases.
@ -1184,11 +1262,28 @@ This endpoint requires the ``permissions-debug`` permission.
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.
The ``/-/check`` endpoint evaluates and explains a single actor, action and
resource decision. The explanation includes:
* Every matching allow and deny rule, with its source and reason.
* The winning resource, parent or global scope.
* Rules ignored because a more specific rule matched, or because a deny won at
the same scope.
* Actor restriction allowlists that included or excluded the resource.
* Additional actions required by the requested action.
* An explicit default-deny explanation when no rule matched.
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.
Pass ``?action=`` to specify the action to check, and optional ``?parent=`` and
``?child=`` parameters to specify the resource. The interactive form also
accepts actor JSON, allowing a hypothetical actor to be tested without signing
in as that actor. The JSON endpoint accepts the same value using the ``actor``
query string parameter. Use ``actor=null`` to represent an anonymous actor.
This endpoint requires the ``permissions-debug`` permission. The hypothetical
actor is used only for the decision being explained; access to the debug tool
is checked against the actor who is actually signed in.
.. _authentication_ds_actor: