Skip to content

Commit

Permalink
Implemented datasette.permission_allowed(), refs #699
Browse files Browse the repository at this point in the history
  • Loading branch information
simonw committed Jun 1, 2020
1 parent 461c828 commit 9315bac
Show file tree
Hide file tree
Showing 5 changed files with 75 additions and 4 deletions.
19 changes: 19 additions & 0 deletions datasette/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,25 @@ def _prepare_connection(self, conn, database):
# pylint: disable=no-member
pm.hook.prepare_connection(conn=conn, database=database, datasette=self)

async def permission_allowed(
self, actor, action, resource_type=None, resource_identifier=None, default=False
):
"Check permissions using the permissions_allowed plugin hook"
for check in pm.hook.permission_allowed(
datasette=self,
actor=actor,
action=action,
resource_type=resource_type,
resource_identifier=resource_identifier,
):
if callable(check):
check = check()
if asyncio.iscoroutine(check):
check = await check
if check is not None:
return check
return default

async def execute(
self,
db_name,
Expand Down
19 changes: 19 additions & 0 deletions docs/internals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,25 @@ This method lets you read plugin configuration values that were set in ``metadat

Renders a `Jinja template <https://jinja.palletsprojects.com/en/2.11.x/>`__ using Datasette's preconfigured instance of Jinja and returns the resulting string. The template will have access to Datasette's default template functions and any functions that have been made available by other plugins.

await .permission_allowed(actor, action, resource_type=None, resource_identifier=None, default=False)
-----------------------------------------------------------------------------------------------------

``actor`` - dictionary
The authenticated actor. This is usually ``request.scope.get("actor")``.

``action`` - string
The name of the action that is being permission checked.

``resource_type`` - string, optional
The type of resource being checked, e.g. ``"table"``.

``resource_identifier`` - string, optional
The resource identifier, e.g. the name of the table.

Check if the given actor has permission to perform the given action on the given resource. This uses plugins that implement the :ref:`plugin_permission_allowed` plugin hook to decide if the action is allowed or not.

If none of the plugins express an opinion, the return value will be the ``default`` argument. This is deny, but you can pass ``default=True`` to default allow instead.

.. _datasette_get_database:

.get_database(name)
Expand Down
8 changes: 8 additions & 0 deletions tests/plugins/my_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,11 @@ def actor_from_request(datasette, request):
return {"id": "bot"}
else:
return None


@hookimpl
def permission_allowed(actor, action):
if action == "this_is_allowed":
return True
elif action == "this_is_denied":
return False
13 changes: 13 additions & 0 deletions tests/plugins/my_plugin_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,16 @@ async def inner():
return None

return inner


@hookimpl
def permission_allowed(datasette, actor, action):
# Testing asyncio version of permission_allowed
async def inner():
assert 2 == (await datasette.get_database().execute("select 1 + 1")).first()[0]
if action == "this_is_allowed_async":
return True
elif action == "this_is_denied_async":
return False

return inner
20 changes: 16 additions & 4 deletions tests/test_plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -523,7 +523,19 @@ def test_actor_from_request_async(app_client):
assert {"id": "bot2", "1+1": 2} == app_client.ds._last_request.scope["actor"]


@pytest.mark.xfail
def test_permission_allowed(app_client):
# TODO
assert False
@pytest.mark.asyncio
@pytest.mark.parametrize(
"action,expected",
[
("this_is_allowed", True),
("this_is_denied", False),
("this_is_allowed_async", True),
("this_is_denied_async", False),
("no_match", None),
],
)
async def test_permission_allowed(app_client, action, expected):
actual = await app_client.ds.permission_allowed(
{"id": "actor"}, action, default=None
)
assert expected == actual

0 comments on commit 9315bac

Please sign in to comment.