Update permissions documentation for new action system (#2551)

This commit is contained in:
Simon Willison 2025-10-30 17:59:54 -07:00 committed by GitHub
commit e4be95b16c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 177 additions and 168 deletions

View file

@ -6,18 +6,18 @@
Datasette doesn't require authentication by default. Any visitor to a Datasette instance can explore the full data and execute read-only SQL queries.
Datasette's plugin system can be used to add many different styles of authentication, such as user accounts, single sign-on or API keys.
Datasette can be configured to only allow authenticated users, or to control which databases, tables, and queries can be accessed by the public or by specific users. Datasette's plugin system can be used to add many different styles of authentication, such as user accounts, single sign-on or API keys.
.. _authentication_actor:
Actors
======
Through plugins, Datasette can support both authenticated users (with cookies) and authenticated API agents (via authentication tokens). The word "actor" is used to cover both of these cases.
Through plugins, Datasette can support both authenticated users (with cookies) and authenticated API clients (via authentication tokens). The word "actor" is used to cover both of these cases.
Every request to Datasette has an associated actor value, available in the code as ``request.actor``. This can be ``None`` for unauthenticated requests, or a JSON compatible Python dictionary for authenticated users or API agents.
Every request to Datasette has an associated actor value, available in the code as ``request.actor``. This can be ``None`` for unauthenticated requests, or a JSON compatible Python dictionary for authenticated users or API clients.
The actor dictionary can be any shape - the design of that data structure is left up to the plugins. A useful convention is to include an ``"id"`` string, as demonstrated by the "root" actor below.
The actor dictionary can be any shape - the design of that data structure is left up to the plugins. Actors should always include a unique ``"id"`` string, as demonstrated by the "root" actor below.
Plugins can use the :ref:`plugin_hook_actor_from_request` hook to implement custom logic for authenticating an actor based on the incoming HTTP request.
@ -32,19 +32,21 @@ The one exception is the "root" account, which you can sign into while using Dat
The ``--root`` flag is designed for local development and testing. When you start Datasette with ``--root``, the root user automatically receives every permission, including:
* All view permissions (view-instance, view-database, view-table, etc.)
* All write permissions (insert-row, update-row, delete-row, create-table, alter-table, drop-table)
* Debug permissions (permissions-debug, debug-menu)
* All view permissions (``view-instance``, ``view-database``, ``view-table``, etc.)
* All write permissions (``insert-row``, ``update-row``, ``delete-row``, ``create-table``, ``alter-table``, ``drop-table``)
* Debug permissions (``permissions-debug``, ``debug-menu``)
* Any custom permissions defined by plugins
.. warning::
The ``--root`` flag should only be used for local development. Never use it in production or on publicly accessible servers.
If you add explicit deny rules in ``datasette.yaml`` those can still block the
root actor from specific databases or tables.
The ``--root`` flag sets an internal ``root_enabled`` switch—without it, a signed-in user with ``{"id": "root"}`` is treated like any other actor.
To sign in as root, start Datasette using the ``--root`` command-line option, like this::
datasette --root
::
Datasette will output a single-use-only login URL on startup::
http://127.0.0.1:8001/-/auth-token?token=786fc524e0199d70dc9a581d851f466244e114ca92f33aa3b42a139e9388daa7
INFO: Started server process [25801]
@ -52,7 +54,7 @@ To sign in as root, start Datasette using the ``--root`` command-line option, li
INFO: Application startup complete.
INFO: Uvicorn running on http://127.0.0.1:8001 (Press CTRL+C to quit)
The URL on the first line includes a one-use token which can be used to sign in as the "root" actor in your browser. Click on that link and then visit ``http://127.0.0.1:8001/-/actor`` to confirm that you are authenticated as an actor that looks like this:
Click on that link and then visit ``http://127.0.0.1:8001/-/actor`` to confirm that you are authenticated as an actor that looks like this:
.. code-block:: json
@ -65,7 +67,7 @@ The URL on the first line includes a one-use token which can be used to sign in
Permissions
===========
Datasette has an extensive permissions system built-in, which can be further extended and customized by plugins.
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:
@ -73,37 +75,47 @@ The key question the permissions system answers is this:
**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 <permissions>` - examples include ``view-table`` and ``execute-sql``.
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``.
A **resource** is the item the actor wishes to interact with - for example a specific database or table. Some actions, such as ``permissions-debug``, are not associated with a particular resource.
Datasette's built-in view permissions (``view-database``, ``view-table`` etc) default to *allow* - unless you :ref:`configure additional permission rules <authentication_permissions_config>` unauthenticated users will be allowed to access content.
Datasette's built-in view actions (``view-database``, ``view-table`` etc) are allowed by Datasette's default configuration: unless you :ref:`configure additional permission rules <authentication_permissions_config>` unauthenticated users will be allowed to access content.
Permissions with potentially harmful effects should default to *deny*. Plugin authors should account for this when designing new plugins - for example, the `datasette-upload-csvs <https://github.com/simonw/datasette-upload-csvs>`__ plugin defaults to deny so that installations don't accidentally allow unauthenticated users to create new tables by uploading a CSV file.
Other actions, including those introduced by plugins, will default to *deny*.
.. _authentication_permissions_explained:
How permissions are resolved
----------------------------
The :ref:`datasette.permission_allowed(actor, action, resource=None, default=...)<datasette_permission_allowed>` method is called to check if an actor is allowed to perform a specific action.
Datasette performs permission checks using the internal :ref:`datasette_allowed`, method which accepts keyword arguments for ``action``, ``resource`` and an optional ``actor``.
This method asks every plugin that implements the :ref:`plugin_hook_permission_allowed` hook if the actor is allowed to perform the action.
``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.
Each plugin can return ``True`` to indicate that the actor is allowed to perform the action, ``False`` if they are not allowed and ``None`` if the plugin has no opinion on the matter.
When a check runs Datasette gathers allow/deny rules from multiple sources and
compiles them into a SQL query. The resulting query describes all of the
resources an actor may access for that action, together with the reasons those
resources were allowed or denied. The combined sources are:
``False`` acts as a veto - if any plugin returns ``False`` then the permission check is denied. Otherwise, if any plugin returns ``True`` then the permission check is allowed.
* ``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.
* Any additional SQL provided by plugins implementing :ref:`plugin_hook_permission_resources_sql`.
The ``resource`` argument can be used to specify a specific resource that the action is being performed against. Some permissions, such as ``view-instance``, do not involve a resource. Others such as ``view-database`` have a resource that is a string naming the database. Permissions that take both a database name and the name of a table, view or canned query within that database use a resource that is a tuple of two strings, ``(database_name, resource_name)``.
Plugins that implement the ``permission_allowed()`` hook can decide if they are going to consider the provided resource or not.
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.
.. _authentication_permissions_allow:
Defining permissions with "allow" blocks
----------------------------------------
The standard way to define permissions in Datasette is to use an ``"allow"`` block :ref:`in the datasette.yaml file <authentication_permissions_config>`. This is a JSON document describing which actors are allowed to perform a permission.
One way to define permissions in Datasette is to use an ``"allow"`` block :ref:`in the datasette.yaml file <authentication_permissions_config>`. This is a JSON document describing which actors are allowed to perform an action against a specific resource.
Each ``allow`` block is compiled into SQL and combined with any
:ref:`plugin-provided rules <plugin_hook_permission_resources_sql>` to produce
the cascading allow/deny decisions that power :ref:`datasette_allowed`.
The most basic form of allow block is this (`allow demo <https://latest.datasette.io/-/allow-debug?actor=%7B%22id%22%3A+%22root%22%7D&allow=%7B%0D%0A++++++++%22id%22%3A+%22root%22%0D%0A++++%7D>`__, `deny demo <https://latest.datasette.io/-/allow-debug?actor=%7B%22id%22%3A+%22trevor%22%7D&allow=%7B%0D%0A++++++++%22id%22%3A+%22root%22%0D%0A++++%7D>`__):
@ -425,7 +437,7 @@ You can control the following:
* Access to specific tables and views
* Access to specific :ref:`canned_queries`
If a user cannot access a specific database, they will not be able to access tables, views or queries within that database. If a user cannot access the instance they will not be able to access any of the databases, tables, views or queries.
If a user has permission to view a table they will be able to view that table, independent of if they have permission to view the database or instance that the table exists within.
.. _authentication_permissions_instance:
@ -663,7 +675,7 @@ Controlling the ability to execute arbitrary SQL
Datasette defaults to allowing any site visitor to execute their own custom SQL queries, for example using the form on `the database page <https://latest.datasette.io/fixtures>`__ or by appending a ``?_where=`` parameter to the table page `like this <https://latest.datasette.io/fixtures/facetable?_where=_city_id=1>`__.
Access to this ability is controlled by the :ref:`permissions_execute_sql` permission.
Access to this ability is controlled by the :ref:`actions_execute_sql` permission.
The easiest way to disable arbitrary SQL queries is using the :ref:`default_allow_sql setting <setting_default_allow_sql>` when you first start Datasette running.
@ -1027,9 +1039,25 @@ This example outputs the following::
Checking permissions in plugins
===============================
Datasette plugins can check if an actor has permission to perform an action using the :ref:`datasette.permission_allowed(...)<datasette_permission_allowed>` method.
Datasette plugins can check if an actor has permission to perform an action using :ref:`datasette_allowed`—for example::
Datasette core performs a number of permission checks, :ref:`documented below <permissions>`. Plugins can implement the :ref:`plugin_hook_permission_allowed` plugin hook to participate in decisions about whether an actor should be able to perform a specified action.
from datasette.resources import TableResource
can_edit = await datasette.allowed(
action="update-row",
resource=TableResource(database="fixtures", table="facetable"),
actor=request.actor,
)
Use :ref:`datasette_ensure_permission` when you need to enforce a permission and
raise a ``Forbidden`` error automatically.
Plugins that define new operations should return :class:`~datasette.permissions.Action`
objects from :ref:`plugin_register_actions` and can supply additional allow/deny
rules by returning :class:`~datasette.permissions.PermissionSQL` objects from the
:ref:`plugin_hook_permission_resources_sql` hook. Those rules are merged with
configuration ``allow`` blocks and actor restrictions to determine the final
result for each check.
.. _authentication_actor_matches_allow:
@ -1049,12 +1077,14 @@ The currently authenticated actor is made available to plugins as ``request.acto
.. _PermissionsDebugView:
The permissions debug tool
==========================
Permissions debug tools
=======================
The debug tool at ``/-/permissions`` is only available to the :ref:`authenticated root user <authentication_root>` (or any actor granted the ``permissions-debug`` action).
The debug tool at ``/-/permissions`` is available to any actor with the ``permissions-debug`` permission. By default this is just the :ref:`authenticated root user <authentication_root>` but you can open it up to all users by starting Datasette like this::
It shows the thirty most recent permission checks that have been carried out by the Datasette instance.
datasette -s permissions.permissions-debug true data.db
The page shows the permission checks that have been carried out by the Datasette instance.
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.
@ -1063,37 +1093,20 @@ This is designed to help administrators and plugin authors understand exactly ho
.. _AllowedResourcesView:
Allowed resources view
======================
----------------------
The ``/-/allowed`` endpoint displays resources that the current actor can access for a supplied ``action`` query string argument.
The ``/-/allowed`` endpoint displays resources that the current actor can access for a specified ``action``.
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.
This endpoint is publicly accessible to help users understand their own permissions. The potentially sensitive ``reason`` field is only shown to users with the ``permissions-debug`` permission - it shows the plugins and explanatory reasons that were responsible for each decision.
.. _PermissionRulesView:
Permission rules view
=====================
---------------------
The ``/-/rules`` endpoint displays all permission rules (both allow and deny) for each candidate resource for the requested action.
@ -1101,12 +1114,12 @@ This endpoint provides an interactive HTML form interface. Add ``.json`` to the
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.
This endpoint requires the ``permissions-debug`` permission.
.. _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.
@ -1114,8 +1127,6 @@ This endpoint provides an interactive HTML form interface. Add ``.json`` to the
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
@ -1181,168 +1192,156 @@ The /-/logout page
The page at ``/-/logout`` provides the ability to log out of a ``ds_actor`` cookie authentication session.
.. _permissions:
.. _actions:
Built-in permissions
====================
Built-in actions
================
This section lists all of the permission checks that are carried out by Datasette core, along with the ``resource`` if it was passed.
.. _permissions_view_instance:
.. _actions_view_instance:
view-instance
-------------
Top level permission - Actor is allowed to view any pages within this instance, starting at https://latest.datasette.io/
Default *allow*.
.. _permissions_view_database:
.. _actions_view_database:
view-database
-------------
Actor is allowed to view a database page, e.g. https://latest.datasette.io/fixtures
``resource`` - string
The name of the database
``resource`` - ``datasette.permissions.DatabaseResource(database)``
``database`` is the name of the database (string)
Default *allow*.
.. _permissions_view_database_download:
.. _actions_view_database_download:
view-database-download
----------------------
Actor is allowed to download a database, e.g. https://latest.datasette.io/fixtures.db
``resource`` - string
The name of the database
``resource`` - ``datasette.resources.DatabaseResource(database)``
``database`` is the name of the database (string)
Default *allow*.
.. _permissions_view_table:
.. _actions_view_table:
view-table
----------
Actor is allowed to view a table (or view) page, e.g. https://latest.datasette.io/fixtures/complex_foreign_keys
``resource`` - tuple: (string, string)
The name of the database, then the name of the table
``resource`` - ``datasette.resources.TableResource(database, table)``
``database`` is the name of the database (string)
Default *allow*.
``table`` is the name of the table (string)
.. _permissions_view_query:
.. _actions_view_query:
view-query
----------
Actor is allowed to view (and execute) a :ref:`canned query <canned_queries>` page, e.g. https://latest.datasette.io/fixtures/pragma_cache_size - this includes executing :ref:`canned_queries_writable`.
``resource`` - tuple: (string, string)
The name of the database, then the name of the canned query
``resource`` - ``datasette.resources.QueryResource(database, query)``
``database`` is the name of the database (string)
``query`` is the name of the canned query (string)
Default *allow*.
.. _permissions_insert_row:
.. _actions_insert_row:
insert-row
----------
Actor is allowed to insert rows into a table.
``resource`` - tuple: (string, string)
The name of the database, then the name of the table
``resource`` - ``datasette.resources.TableResource(database, table)``
``database`` is the name of the database (string)
Default *deny*.
``table`` is the name of the table (string)
.. _permissions_delete_row:
.. _actions_delete_row:
delete-row
----------
Actor is allowed to delete rows from a table.
``resource`` - tuple: (string, string)
The name of the database, then the name of the table
``resource`` - ``datasette.resources.TableResource(database, table)``
``database`` is the name of the database (string)
Default *deny*.
``table`` is the name of the table (string)
.. _permissions_update_row:
.. _actions_update_row:
update-row
----------
Actor is allowed to update rows in a table.
``resource`` - tuple: (string, string)
The name of the database, then the name of the table
``resource`` - ``datasette.resources.TableResource(database, table)``
``database`` is the name of the database (string)
Default *deny*.
``table`` is the name of the table (string)
.. _permissions_create_table:
.. _actions_create_table:
create-table
------------
Actor is allowed to create a database table.
``resource`` - string
The name of the database
``resource`` - ``datasette.resources.DatabaseResource(database)``
``database`` is the name of the database (string)
Default *deny*.
.. _permissions_alter_table:
.. _actions_alter_table:
alter-table
-----------
Actor is allowed to alter a database table.
``resource`` - tuple: (string, string)
The name of the database, then the name of the table
``resource`` - ``datasette.resources.TableResource(database, table)``
``database`` is the name of the database (string)
Default *deny*.
``table`` is the name of the table (string)
.. _permissions_drop_table:
.. _actions_drop_table:
drop-table
----------
Actor is allowed to drop a database table.
``resource`` - tuple: (string, string)
The name of the database, then the name of the table
``resource`` - ``datasette.resources.TableResource(database, table)``
``database`` is the name of the database (string)
Default *deny*.
``table`` is the name of the table (string)
.. _permissions_execute_sql:
.. _actions_execute_sql:
execute-sql
-----------
Actor is allowed to run arbitrary SQL queries against a specific database, e.g. https://latest.datasette.io/fixtures?sql=select+100
Actor is allowed to run arbitrary SQL queries against a specific database, e.g. https://latest.datasette.io/fixtures/-/query?sql=select+100
``resource`` - string
The name of the database
``resource`` - ``datasette.resources.DatabaseResource(database)``
``database`` is the name of the database (string)
Default *allow*. See also :ref:`the default_allow_sql setting <setting_default_allow_sql>`.
See also :ref:`the default_allow_sql setting <setting_default_allow_sql>`.
.. _permissions_permissions_debug:
.. _actions_permissions_debug:
permissions-debug
-----------------
Actor is allowed to view the ``/-/permissions`` debug page.
Actor is allowed to view the ``/-/permissions`` debug tools.
Default *deny*.
.. _permissions_debug_menu:
.. _actions_debug_menu:
debug-menu
----------
Controls if the various debug pages are displayed in the navigation menu.
Default *deny*.