mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Implements a new ensure_permission() method that is a convenience wrapper around allowed() that raises Forbidden instead of returning False. Changes: - Added ensure_permission() method to datasette/app.py - Updated all views to use ensure_permission() instead of the pattern: if not await self.ds.allowed(...): raise Forbidden(...) - Updated docs/internals.rst to document the new method - Removed old ensure_permissions() documentation (that method was already removed) The new method simplifies permission enforcement in views and makes the code more concise and consistent.
This commit is contained in:
parent
6df364cb2c
commit
fabcfd68ad
5 changed files with 74 additions and 45 deletions
|
|
@ -1310,6 +1310,39 @@ class Datasette:
|
|||
|
||||
return result
|
||||
|
||||
async def ensure_permission(
|
||||
self,
|
||||
*,
|
||||
action: str,
|
||||
resource: "Resource" = None,
|
||||
actor: dict | None = None,
|
||||
):
|
||||
"""
|
||||
Check if actor can perform action on resource, raising Forbidden if not.
|
||||
|
||||
This is a convenience wrapper around allowed() that raises Forbidden
|
||||
instead of returning False. Use this when you want to enforce a permission
|
||||
check and halt execution if it fails.
|
||||
|
||||
Example:
|
||||
from datasette.resources import TableResource
|
||||
|
||||
# Will raise Forbidden if actor cannot view the table
|
||||
await datasette.ensure_permission(
|
||||
action="view-table",
|
||||
resource=TableResource(database="analytics", table="users"),
|
||||
actor=request.actor
|
||||
)
|
||||
|
||||
# For instance-level actions, resource can be omitted:
|
||||
await datasette.ensure_permission(
|
||||
action="permissions-debug",
|
||||
actor=request.actor
|
||||
)
|
||||
"""
|
||||
if not await self.allowed(action=action, resource=resource, actor=actor):
|
||||
raise Forbidden(action)
|
||||
|
||||
async def execute(
|
||||
self,
|
||||
db_name,
|
||||
|
|
|
|||
|
|
@ -370,12 +370,11 @@ async def database_download(request, datasette):
|
|||
from datasette.resources import DatabaseResource
|
||||
|
||||
database = tilde_decode(request.url_vars["database"])
|
||||
if not await datasette.allowed(
|
||||
await datasette.ensure_permission(
|
||||
action="view-database-download",
|
||||
resource=DatabaseResource(database=database),
|
||||
actor=request.actor,
|
||||
):
|
||||
raise Forbidden("view-database-download")
|
||||
)
|
||||
try:
|
||||
db = datasette.get_database(route=database)
|
||||
except KeyError:
|
||||
|
|
@ -544,12 +543,11 @@ class QueryView(View):
|
|||
raise Forbidden("You do not have permission to view this query")
|
||||
|
||||
else:
|
||||
if not await datasette.allowed(
|
||||
await datasette.ensure_permission(
|
||||
action="execute-sql",
|
||||
resource=DatabaseResource(database=database),
|
||||
actor=request.actor,
|
||||
):
|
||||
raise Forbidden("execute-sql")
|
||||
)
|
||||
|
||||
# Flattened because of ?sql=&name1=value1&name2=value2 feature
|
||||
params = {key: request.args.get(key) for key in request.args}
|
||||
|
|
|
|||
|
|
@ -26,8 +26,7 @@ class IndexView(BaseView):
|
|||
|
||||
async def get(self, request):
|
||||
as_format = request.url_vars["format"]
|
||||
if not await self.ds.allowed(action="view-instance", actor=request.actor):
|
||||
raise Forbidden("view-instance")
|
||||
await self.ds.ensure_permission(action="view-instance", actor=request.actor)
|
||||
|
||||
# Get all allowed databases and tables in bulk
|
||||
allowed_databases = await self.ds.allowed_resources(
|
||||
|
|
|
|||
|
|
@ -44,8 +44,7 @@ class JsonDataView(BaseView):
|
|||
|
||||
async def get(self, request):
|
||||
if self.permission:
|
||||
if not await self.ds.allowed(action=self.permission, actor=request.actor):
|
||||
raise Forbidden(self.permission)
|
||||
await self.ds.ensure_permission(action=self.permission, actor=request.actor)
|
||||
if self.needs_request:
|
||||
data = self.data_callback(request)
|
||||
else:
|
||||
|
|
@ -55,8 +54,7 @@ class JsonDataView(BaseView):
|
|||
|
||||
class PatternPortfolioView(View):
|
||||
async def get(self, request, datasette):
|
||||
if not await datasette.allowed(action="view-instance", actor=request.actor):
|
||||
raise Forbidden("view-instance")
|
||||
await datasette.ensure_permission(action="view-instance", actor=request.actor)
|
||||
return Response.html(
|
||||
await datasette.render_template(
|
||||
"patterns.html",
|
||||
|
|
@ -114,10 +112,8 @@ class PermissionsDebugView(BaseView):
|
|||
has_json_alternate = False
|
||||
|
||||
async def get(self, request):
|
||||
if not await self.ds.allowed(action="view-instance", actor=request.actor):
|
||||
raise Forbidden("view-instance")
|
||||
if not await self.ds.allowed(action="permissions-debug", actor=request.actor):
|
||||
raise Forbidden("Permission denied")
|
||||
await self.ds.ensure_permission(action="view-instance", actor=request.actor)
|
||||
await self.ds.ensure_permission(action="permissions-debug", actor=request.actor)
|
||||
filter_ = request.args.get("filter") or "all"
|
||||
permission_checks = list(reversed(self.ds._permission_checks))
|
||||
if filter_ == "exclude-yours":
|
||||
|
|
@ -153,10 +149,8 @@ class PermissionsDebugView(BaseView):
|
|||
)
|
||||
|
||||
async def post(self, request):
|
||||
if not await self.ds.allowed(action="view-instance", actor=request.actor):
|
||||
raise Forbidden("view-instance")
|
||||
if not await self.ds.allowed(action="permissions-debug", actor=request.actor):
|
||||
raise Forbidden("Permission denied")
|
||||
await self.ds.ensure_permission(action="view-instance", actor=request.actor)
|
||||
await self.ds.ensure_permission(action="permissions-debug", actor=request.actor)
|
||||
vars = await request.post_vars()
|
||||
actor = json.loads(vars["actor"])
|
||||
permission = vars["permission"]
|
||||
|
|
@ -362,10 +356,8 @@ class PermissionRulesView(BaseView):
|
|||
has_json_alternate = False
|
||||
|
||||
async def get(self, request):
|
||||
if not await self.ds.allowed(action="view-instance", actor=request.actor):
|
||||
raise Forbidden("view-instance")
|
||||
if not await self.ds.allowed(action="permissions-debug", actor=request.actor):
|
||||
raise Forbidden("Permission denied")
|
||||
await self.ds.ensure_permission(action="view-instance", actor=request.actor)
|
||||
await self.ds.ensure_permission(action="permissions-debug", actor=request.actor)
|
||||
|
||||
# Check if this is a request for JSON (has .json extension)
|
||||
as_format = request.url_vars.get("format")
|
||||
|
|
@ -607,13 +599,11 @@ class MessagesDebugView(BaseView):
|
|||
has_json_alternate = False
|
||||
|
||||
async def get(self, request):
|
||||
if not await self.ds.allowed(action="view-instance", actor=request.actor):
|
||||
raise Forbidden("view-instance")
|
||||
await self.ds.ensure_permission(action="view-instance", actor=request.actor)
|
||||
return await self.render(["messages_debug.html"], request)
|
||||
|
||||
async def post(self, request):
|
||||
if not await self.ds.allowed(action="view-instance", actor=request.actor):
|
||||
raise Forbidden("view-instance")
|
||||
await self.ds.ensure_permission(action="view-instance", actor=request.actor)
|
||||
post = await request.post_vars()
|
||||
message = post.get("message", "")
|
||||
message_type = post.get("message_type") or "INFO"
|
||||
|
|
|
|||
|
|
@ -416,30 +416,39 @@ 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:
|
||||
.. _datasette_ensure_permission:
|
||||
|
||||
await .ensure_permissions(actor, permissions)
|
||||
---------------------------------------------
|
||||
await .ensure_permission(action, resource=None, actor=None)
|
||||
------------------------------------------------------------
|
||||
|
||||
``actor`` - dictionary
|
||||
``action`` - string
|
||||
The action to check. See :ref:`permissions` for a list of available actions.
|
||||
|
||||
``resource`` - Resource object (optional)
|
||||
The resource to check the permission against. Must be an instance of ``InstanceResource``, ``DatabaseResource``, or ``TableResource`` from the ``datasette.resources`` module. If omitted, defaults to ``InstanceResource()`` for instance-level permissions.
|
||||
|
||||
``actor`` - dictionary (optional)
|
||||
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 is a convenience wrapper around :ref:`datasette_allowed` that raises a ``datasette.Forbidden`` exception if the permission check fails. Use this when you want to enforce a permission check and halt execution if the actor is not authorized.
|
||||
|
||||
This method allows multiple permissions to be checked at once. 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``:
|
||||
Example:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
await datasette.ensure_permissions(
|
||||
request.actor,
|
||||
[
|
||||
("view-table", (database, table)),
|
||||
("view-database", database),
|
||||
"view-instance",
|
||||
],
|
||||
from datasette.resources import TableResource
|
||||
|
||||
# Will raise Forbidden if actor cannot view the table
|
||||
await datasette.ensure_permission(
|
||||
action="view-table",
|
||||
resource=TableResource(database="fixtures", table="cities"),
|
||||
actor=request.actor
|
||||
)
|
||||
|
||||
# For instance-level actions, resource can be omitted:
|
||||
await datasette.ensure_permission(
|
||||
action="permissions-debug",
|
||||
actor=request.actor
|
||||
)
|
||||
|
||||
.. _datasette_check_visibility:
|
||||
|
|
@ -473,7 +482,7 @@ This example checks if the user can access a specific table, and sets ``private`
|
|||
resource=(database, table),
|
||||
)
|
||||
|
||||
The following example runs three checks in a row, similar to :ref:`datasette_ensure_permissions`. If any of the checks are denied before one of them is explicitly granted then ``visible`` will be ``False``. ``private`` will be ``True`` if an anonymous user would not be able to view the resource.
|
||||
The following example runs three checks in a row. If any of the checks are denied before one of them is explicitly granted then ``visible`` will be ``False``. ``private`` will be ``True`` if an anonymous user would not be able to view the resource.
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue