mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
parent
8bc9b1ee03
commit
a508fc4a8e
4 changed files with 10 additions and 76 deletions
|
|
@ -1314,72 +1314,6 @@ This example plugin causes 0 results to be returned if ``?_nothing=1`` is added
|
||||||
|
|
||||||
Example: `datasette-leaflet-freedraw <https://datasette.io/plugins/datasette-leaflet-freedraw>`_
|
Example: `datasette-leaflet-freedraw <https://datasette.io/plugins/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 <authentication_permissions>` for a full list of permissions that are included in Datasette core.
|
|
||||||
|
|
||||||
Example: `datasette-permissions-sql <https://datasette.io/plugins/datasette-permissions-sql>`_
|
|
||||||
|
|
||||||
.. _plugin_hook_permission_resources_sql:
|
.. _plugin_hook_permission_resources_sql:
|
||||||
|
|
||||||
permission_resources_sql(datasette, actor, action)
|
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
|
.. code-block:: python
|
||||||
|
|
||||||
from datasette import hookimpl
|
from datasette import hookimpl
|
||||||
|
from datasette.resources import DatabaseResource
|
||||||
|
|
||||||
|
|
||||||
@hookimpl
|
@hookimpl
|
||||||
def database_actions(datasette, actor, database):
|
def database_actions(datasette, actor, database):
|
||||||
async def inner():
|
async def inner():
|
||||||
if not await datasette.permission_allowed(
|
if not await datasette.allowed(
|
||||||
actor,
|
actor,
|
||||||
"edit-schema",
|
"edit-schema",
|
||||||
resource=database,
|
resource=DatabaseResource("database"),
|
||||||
default=False,
|
|
||||||
):
|
):
|
||||||
return []
|
return []
|
||||||
return [
|
return [
|
||||||
|
|
|
||||||
|
|
@ -469,7 +469,7 @@ def register_actions(datasette):
|
||||||
description="View a collection",
|
description="View a collection",
|
||||||
resource_class=DatabaseResource,
|
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(
|
Action(
|
||||||
name="this_is_allowed",
|
name="this_is_allowed",
|
||||||
abbr=None,
|
abbr=None,
|
||||||
|
|
@ -553,7 +553,7 @@ def register_actions(datasette):
|
||||||
def permission_resources_sql(datasette, actor, action):
|
def permission_resources_sql(datasette, actor, action):
|
||||||
from datasette.permissions import PermissionSQL
|
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":
|
if action == "this_is_allowed":
|
||||||
return PermissionSQL.allow(reason="test plugin allows this_is_allowed")
|
return PermissionSQL.allow(reason="test plugin allows this_is_allowed")
|
||||||
elif action == "this_is_denied":
|
elif action == "this_is_denied":
|
||||||
|
|
|
||||||
|
|
@ -935,7 +935,7 @@ async def test_edit_sql_link_on_canned_queries(ds_client, path, expected):
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"permission_allowed",
|
"has_permission",
|
||||||
[
|
[
|
||||||
pytest.param(
|
pytest.param(
|
||||||
True,
|
True,
|
||||||
|
|
@ -943,15 +943,15 @@ async def test_edit_sql_link_on_canned_queries(ds_client, path, expected):
|
||||||
False,
|
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(
|
with make_app_client(
|
||||||
config={
|
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"}}},
|
"databases": {"fixtures": {"queries": {"simple": "select 1 + 1"}}},
|
||||||
}
|
}
|
||||||
) as client:
|
) as client:
|
||||||
response = client.get("/fixtures/simple")
|
response = client.get("/fixtures/simple")
|
||||||
if permission_allowed:
|
if has_permission:
|
||||||
assert "Edit SQL" in response.text
|
assert "Edit SQL" in response.text
|
||||||
else:
|
else:
|
||||||
assert "Edit SQL" not in response.text
|
assert "Edit SQL" not in response.text
|
||||||
|
|
|
||||||
|
|
@ -677,7 +677,7 @@ async def test_existing_scope_actor_respected(ds_client):
|
||||||
("this_is_denied_async", False),
|
("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
|
# Test actions and permission logic are defined in tests/plugins/my_plugin.py
|
||||||
ds = Datasette(plugins_dir=PLUGINS_DIR)
|
ds = Datasette(plugins_dir=PLUGINS_DIR)
|
||||||
await ds.invoke_startup()
|
await ds.invoke_startup()
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue