mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
BaseView.check_permissions is now datasette.ensure_permissions, closes #1675
Refs #1660
This commit is contained in:
parent
4a4164b811
commit
e627510b76
5 changed files with 71 additions and 36 deletions
|
|
@ -1,4 +1,5 @@
|
||||||
import asyncio
|
import asyncio
|
||||||
|
from typing import Sequence, Union, Tuple
|
||||||
import asgi_csrf
|
import asgi_csrf
|
||||||
import collections
|
import collections
|
||||||
import datetime
|
import datetime
|
||||||
|
|
@ -628,6 +629,40 @@ class Datasette:
|
||||||
)
|
)
|
||||||
return result
|
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(
|
async def execute(
|
||||||
self,
|
self,
|
||||||
db_name,
|
db_name,
|
||||||
|
|
|
||||||
|
|
@ -76,32 +76,6 @@ class BaseView:
|
||||||
if not ok:
|
if not ok:
|
||||||
raise Forbidden(action)
|
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):
|
def database_color(self, database):
|
||||||
return "ff0000"
|
return "ff0000"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -39,8 +39,8 @@ class DatabaseView(DataView):
|
||||||
raise NotFound("Database not found: {}".format(database_route))
|
raise NotFound("Database not found: {}".format(database_route))
|
||||||
database = db.name
|
database = db.name
|
||||||
|
|
||||||
await self.check_permissions(
|
await self.ds.ensure_permissions(
|
||||||
request,
|
request.actor,
|
||||||
[
|
[
|
||||||
("view-database", database),
|
("view-database", database),
|
||||||
"view-instance",
|
"view-instance",
|
||||||
|
|
@ -164,8 +164,8 @@ class DatabaseDownload(DataView):
|
||||||
|
|
||||||
async def get(self, request):
|
async def get(self, request):
|
||||||
database = tilde_decode(request.url_vars["database"])
|
database = tilde_decode(request.url_vars["database"])
|
||||||
await self.check_permissions(
|
await self.ds.ensure_permissions(
|
||||||
request,
|
request.actor,
|
||||||
[
|
[
|
||||||
("view-database-download", database),
|
("view-database-download", database),
|
||||||
("view-database", database),
|
("view-database", database),
|
||||||
|
|
@ -217,8 +217,8 @@ class QueryView(DataView):
|
||||||
private = False
|
private = False
|
||||||
if canned_query:
|
if canned_query:
|
||||||
# Respect canned query permissions
|
# Respect canned query permissions
|
||||||
await self.check_permissions(
|
await self.ds.ensure_permissions(
|
||||||
request,
|
request.actor,
|
||||||
[
|
[
|
||||||
("view-query", (database, canned_query)),
|
("view-query", (database, canned_query)),
|
||||||
("view-database", database),
|
("view-database", database),
|
||||||
|
|
|
||||||
|
|
@ -360,8 +360,8 @@ class TableView(RowTableShared):
|
||||||
raise NotFound(f"Table not found: {table}")
|
raise NotFound(f"Table not found: {table}")
|
||||||
|
|
||||||
# Ensure user has permission to view this table
|
# Ensure user has permission to view this table
|
||||||
await self.check_permissions(
|
await self.ds.ensure_permissions(
|
||||||
request,
|
request.actor,
|
||||||
[
|
[
|
||||||
("view-table", (database, table)),
|
("view-table", (database, table)),
|
||||||
("view-database", database),
|
("view-database", database),
|
||||||
|
|
@ -950,8 +950,8 @@ class RowView(RowTableShared):
|
||||||
except KeyError:
|
except KeyError:
|
||||||
raise NotFound("Database not found: {}".format(database_route))
|
raise NotFound("Database not found: {}".format(database_route))
|
||||||
database = db.name
|
database = db.name
|
||||||
await self.check_permissions(
|
await self.ds.ensure_permissions(
|
||||||
request,
|
request.actor,
|
||||||
[
|
[
|
||||||
("view-table", (database, table)),
|
("view-table", (database, table)),
|
||||||
("view-database", database),
|
("view-database", database),
|
||||||
|
|
|
||||||
|
|
@ -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.
|
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:
|
.. _datasette_get_database:
|
||||||
|
|
||||||
.get_database(name)
|
.get_database(name)
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue