Skip to content

Commit

Permalink
BaseView.check_permissions is now datasette.ensure_permissions, closes
Browse files Browse the repository at this point in the history
  • Loading branch information
simonw committed Mar 21, 2022
1 parent 4a4164b commit e627510
Show file tree
Hide file tree
Showing 5 changed files with 71 additions and 36 deletions.
35 changes: 35 additions & 0 deletions datasette/app.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import asyncio
from typing import Sequence, Union, Tuple
import asgi_csrf
import collections
import datetime
Expand Down Expand Up @@ -628,6 +629,40 @@ async def permission_allowed(self, actor, action, resource=None, default=False):
)
return result

async def ensure_permissions(
self,
actor: dict,
permissions: Sequence[Union[Tuple[str, Union[str, Tuple[str, str]]], str]],
):
"""
permissions is a list of (action, resource) tuples or 'action' strings
Raises datasette.Forbidden() if any of the checks fail
"""
for permission in permissions:
if isinstance(permission, str):
action = permission
resource = None
elif isinstance(permission, (tuple, list)) and len(permission) == 2:
action, resource = permission
else:
assert (
False
), "permission should be string or tuple of two items: {}".format(
repr(permission)
)
ok = await self.permission_allowed(
actor,
action,
resource=resource,
default=None,
)
if ok is not None:
if ok:
return
else:
raise Forbidden(action)

async def execute(
self,
db_name,
Expand Down
26 changes: 0 additions & 26 deletions datasette/views/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,32 +76,6 @@ async def check_permission(self, request, action, resource=None):
if not ok:
raise Forbidden(action)

async def check_permissions(self, request, permissions):
"""permissions is a list of (action, resource) tuples or 'action' strings"""
for permission in permissions:
if isinstance(permission, str):
action = permission
resource = None
elif isinstance(permission, (tuple, list)) and len(permission) == 2:
action, resource = permission
else:
assert (
False
), "permission should be string or tuple of two items: {}".format(
repr(permission)
)
ok = await self.ds.permission_allowed(
request.actor,
action,
resource=resource,
default=None,
)
if ok is not None:
if ok:
return
else:
raise Forbidden(action)

def database_color(self, database):
return "ff0000"

Expand Down
12 changes: 6 additions & 6 deletions datasette/views/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ async def data(self, request, default_labels=False, _size=None):
raise NotFound("Database not found: {}".format(database_route))
database = db.name

await self.check_permissions(
request,
await self.ds.ensure_permissions(
request.actor,
[
("view-database", database),
"view-instance",
Expand Down Expand Up @@ -164,8 +164,8 @@ class DatabaseDownload(DataView):

async def get(self, request):
database = tilde_decode(request.url_vars["database"])
await self.check_permissions(
request,
await self.ds.ensure_permissions(
request.actor,
[
("view-database-download", database),
("view-database", database),
Expand Down Expand Up @@ -217,8 +217,8 @@ async def data(
private = False
if canned_query:
# Respect canned query permissions
await self.check_permissions(
request,
await self.ds.ensure_permissions(
request.actor,
[
("view-query", (database, canned_query)),
("view-database", database),
Expand Down
8 changes: 4 additions & 4 deletions datasette/views/table.py
Original file line number Diff line number Diff line change
Expand Up @@ -360,8 +360,8 @@ async def data(
raise NotFound(f"Table not found: {table}")

# Ensure user has permission to view this table
await self.check_permissions(
request,
await self.ds.ensure_permissions(
request.actor,
[
("view-table", (database, table)),
("view-database", database),
Expand Down Expand Up @@ -950,8 +950,8 @@ async def data(self, request, default_labels=False):
except KeyError:
raise NotFound("Database not found: {}".format(database_route))
database = db.name
await self.check_permissions(
request,
await self.ds.ensure_permissions(
request.actor,
[
("view-table", (database, table)),
("view-database", database),
Expand Down
26 changes: 26 additions & 0 deletions docs/internals.rst
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,32 @@ 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_permission_allowed:

await .ensure_permissions(actor, permissions)
---------------------------------------------

``actor`` - dictionary
The authenticated actor. This is usually ``request.actor``.

``permissions`` - list
A list of permissions to check. Each permission in that list can be a string ``action`` name or a 2-tuple of ``(action, resource)``.

This method allows multiple permissions to be checked at onced. It raises a ``datasette.Forbidden`` exception if any of the checks are denied before one of them is explicitly granted.

This is useful when you need to check multiple permissions at once. For example, an actor should be able to view a table if either one of the following checks returns ``True`` or not a single one of them returns ``False``:

.. code-block:: python
await self.ds.ensure_permissions(
request.actor,
[
("view-table", (database, table)),
("view-database", database),
"view-instance",
]
)
.. _datasette_get_database:

.get_database(name)
Expand Down

0 comments on commit e627510

Please sign in to comment.