mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Document datasette.allowed(), PermissionSQL class, and SQL parameters
- Added documentation for datasette.allowed() method with keyword-only arguments - Added comprehensive PermissionSQL class documentation with examples - Documented the three SQL parameters available: :actor, :actor_id, :action - Included examples of using json_extract() to access actor fields - Explained permission resolution rules (specificity, deny over allow, implicit deny) - Fixed RST formatting warnings (escaped asterisk, fixed underline length)
This commit is contained in:
parent
5fc58c8775
commit
faef51ad05
2 changed files with 169 additions and 1 deletions
|
|
@ -369,6 +369,48 @@ If neither ``metadata.json`` nor any of the plugins provide an answer to the per
|
|||
|
||||
See :ref:`permissions` for a full list of permission actions included in Datasette core.
|
||||
|
||||
.. _datasette_allowed:
|
||||
|
||||
await .allowed(\*, action, resource, actor=None)
|
||||
------------------------------------------------
|
||||
|
||||
``action`` - string
|
||||
The name of the action that is being permission checked.
|
||||
|
||||
``resource`` - Resource object
|
||||
A Resource object representing the database, table, or other resource. Must be an instance of a Resource class such as ``TableResource``, ``DatabaseResource``, ``QueryResource``, or ``InstanceResource``.
|
||||
|
||||
``actor`` - dictionary, optional
|
||||
The authenticated actor. This is usually ``request.actor``. Defaults to ``None`` for unauthenticated requests.
|
||||
|
||||
This method checks if the given actor has permission to perform the given action on the given resource. All parameters must be passed as keyword arguments.
|
||||
|
||||
This is the modern resource-based permission checking method. It works with Resource objects that provide structured information about what is being accessed.
|
||||
|
||||
Example usage:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from datasette.resources import TableResource, DatabaseResource
|
||||
|
||||
# Check if actor can view a specific table
|
||||
can_view = await datasette.allowed(
|
||||
action="view-table",
|
||||
resource=TableResource(database="fixtures", table="facetable"),
|
||||
actor=request.actor
|
||||
)
|
||||
|
||||
# Check if actor can execute SQL on a database
|
||||
can_execute = await datasette.allowed(
|
||||
action="execute-sql",
|
||||
resource=DatabaseResource(database="fixtures"),
|
||||
actor=request.actor
|
||||
)
|
||||
|
||||
The method returns ``True`` if the permission is granted, ``False`` if denied.
|
||||
|
||||
For legacy string/tuple based permission checking, use :ref:`datasette_permission_allowed` instead.
|
||||
|
||||
.. _datasette_ensure_permissions:
|
||||
|
||||
await .ensure_permissions(actor, permissions)
|
||||
|
|
@ -1001,6 +1043,132 @@ Use the ``format="json"`` (or ``"csv"`` or other formats supported by plugins) a
|
|||
|
||||
These methods each return a ``datasette.utils.PrefixedUrlString`` object, which is a subclass of the Python ``str`` type. This allows the logic that considers the ``base_url`` setting to detect if that prefix has already been applied to the path.
|
||||
|
||||
.. _internals_permission_classes:
|
||||
|
||||
Permission classes and utilities
|
||||
=================================
|
||||
|
||||
.. _internals_permission_sql:
|
||||
|
||||
PermissionSQL class
|
||||
-------------------
|
||||
|
||||
The ``PermissionSQL`` class is used by plugins to contribute SQL-based permission rules through the :ref:`plugin_hook_permission_resources_sql` hook. This enables efficient permission checking across multiple resources by leveraging SQLite's query engine.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from datasette.permissions import PermissionSQL
|
||||
|
||||
@dataclass
|
||||
class PermissionSQL:
|
||||
source: str # Plugin name for auditing
|
||||
sql: str # SQL query returning permission rules
|
||||
params: Dict[str, Any] # Parameters for the SQL query
|
||||
|
||||
**Attributes:**
|
||||
|
||||
``source`` - string
|
||||
An identifier for the source of these permission rules, typically the plugin name. This is used for debugging and auditing.
|
||||
|
||||
``sql`` - string
|
||||
A SQL query that returns permission rules. The query must return rows with the following columns:
|
||||
|
||||
- ``parent`` (TEXT or NULL) - The parent resource identifier (e.g., database name)
|
||||
- ``child`` (TEXT or NULL) - The child resource identifier (e.g., table name)
|
||||
- ``allow`` (INTEGER) - 1 for allow, 0 for deny
|
||||
- ``reason`` (TEXT) - A human-readable explanation of why this permission was granted or denied
|
||||
|
||||
``params`` - dictionary
|
||||
A dictionary of parameters to bind into the SQL query. Parameter names should not include the ``:`` prefix.
|
||||
|
||||
.. _permission_sql_parameters:
|
||||
|
||||
Available SQL parameters
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
When writing SQL for ``PermissionSQL``, the following parameters are automatically available:
|
||||
|
||||
``:actor`` - JSON string or NULL
|
||||
The full actor dictionary serialized as JSON. Use SQLite's ``json_extract()`` function to access fields:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
json_extract(:actor, '$.role') = 'admin'
|
||||
json_extract(:actor, '$.team') = 'engineering'
|
||||
|
||||
``:actor_id`` - string or NULL
|
||||
The actor's ``id`` field, for simple equality comparisons:
|
||||
|
||||
.. code-block:: sql
|
||||
|
||||
:actor_id = 'alice'
|
||||
|
||||
``:action`` - string
|
||||
The action being checked (e.g., ``"view-table"``, ``"insert-row"``, ``"execute-sql"``).
|
||||
|
||||
**Example usage:**
|
||||
|
||||
Here's an example plugin that grants view-table permissions to users with an "analyst" role for tables in the "analytics" database:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
from datasette import hookimpl
|
||||
from datasette.permissions import PermissionSQL
|
||||
|
||||
@hookimpl
|
||||
def permission_resources_sql(datasette, actor, action):
|
||||
if action != "view-table":
|
||||
return None
|
||||
|
||||
return PermissionSQL(
|
||||
source="my_analytics_plugin",
|
||||
sql="""
|
||||
SELECT 'analytics' AS parent,
|
||||
NULL AS child,
|
||||
1 AS allow,
|
||||
'Analysts can view analytics database' AS reason
|
||||
WHERE json_extract(:actor, '$.role') = 'analyst'
|
||||
AND :action = 'view-table'
|
||||
""",
|
||||
params={}
|
||||
)
|
||||
|
||||
A more complex example that uses custom parameters:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
@hookimpl
|
||||
def permission_resources_sql(datasette, actor, action):
|
||||
if not actor:
|
||||
return None
|
||||
|
||||
user_teams = actor.get("teams", [])
|
||||
|
||||
return PermissionSQL(
|
||||
source="team_permissions_plugin",
|
||||
sql="""
|
||||
SELECT
|
||||
team_database AS parent,
|
||||
team_table AS child,
|
||||
1 AS allow,
|
||||
'User is member of team: ' || team_name AS reason
|
||||
FROM team_permissions
|
||||
WHERE user_id = :user_id
|
||||
AND :action IN ('view-table', 'insert-row', 'update-row')
|
||||
""",
|
||||
params={
|
||||
"user_id": actor.get("id")
|
||||
}
|
||||
)
|
||||
|
||||
**Permission resolution rules:**
|
||||
|
||||
When multiple ``PermissionSQL`` objects return conflicting rules for the same resource, Datasette applies the following precedence:
|
||||
|
||||
1. **Specificity**: Child-level rules (with both ``parent`` and ``child``) override parent-level rules (with only ``parent``), which override root-level rules (with neither ``parent`` nor ``child``)
|
||||
2. **Deny over allow**: At the same specificity level, deny (``allow=0``) takes precedence over allow (``allow=1``)
|
||||
3. **Implicit deny**: If no rules match a resource, access is denied by default
|
||||
|
||||
.. _internals_database:
|
||||
|
||||
Database class
|
||||
|
|
|
|||
|
|
@ -1445,7 +1445,7 @@ Example: `datasette-permissions-sql <https://datasette.io/plugins/datasette-perm
|
|||
.. _plugin_hook_permission_resources_sql:
|
||||
|
||||
permission_resources_sql(datasette, actor, action)
|
||||
-------------------------------------------------
|
||||
---------------------------------------------------
|
||||
|
||||
``datasette`` - :ref:`internals_datasette`
|
||||
Access to the Datasette instance.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue