mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Refactor check_visibility() to use Resource objects, refs #2537
Updated check_visibility() method signature to accept Resource objects (DatabaseResource, TableResource, QueryResource) instead of plain strings and tuples. Changes: - Updated check_visibility() signature to only accept Resource objects - Added validation with helpful error message for incorrect types - Updated all check_visibility() calls throughout the codebase: - datasette/views/database.py: Use DatabaseResource and QueryResource - datasette/views/special.py: Use DatabaseResource and TableResource - datasette/views/row.py: Use TableResource - datasette/views/table.py: Use TableResource - datasette/app.py: Use TableResource in expand_foreign_keys - Updated tests to use Resource objects - Updated documentation in docs/internals.rst: - Removed outdated permissions parameter - Updated examples to use Resource objects 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
653475edde
commit
95286fbb60
7 changed files with 34 additions and 47 deletions
|
|
@ -1072,7 +1072,7 @@ class Datasette:
|
||||||
self,
|
self,
|
||||||
actor: dict,
|
actor: dict,
|
||||||
action: str,
|
action: str,
|
||||||
resource: Optional[Union[str, Tuple[str, str]]] = None,
|
resource: Optional["Resource"] = None,
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Check if actor can see a resource and if it's private.
|
Check if actor can see a resource and if it's private.
|
||||||
|
|
@ -1081,26 +1081,22 @@ class Datasette:
|
||||||
- visible: bool - can the actor see it?
|
- visible: bool - can the actor see it?
|
||||||
- private: bool - if visible, can anonymous users NOT see it?
|
- private: bool - if visible, can anonymous users NOT see it?
|
||||||
"""
|
"""
|
||||||
# Convert old-style resource to Resource object
|
from datasette.permissions import Resource
|
||||||
if resource is None:
|
|
||||||
resource_obj = None
|
# Validate that resource is a Resource object or None
|
||||||
elif isinstance(resource, str):
|
if resource is not None and not isinstance(resource, Resource):
|
||||||
# Database resource
|
raise TypeError(
|
||||||
resource_obj = self.resource_for_action(action, parent=resource, child=None)
|
f"resource must be a Resource object or None, not {type(resource).__name__}. "
|
||||||
elif isinstance(resource, tuple) and len(resource) == 2:
|
f"Use DatabaseResource(database=...), TableResource(database=..., table=...), "
|
||||||
# Database + child resource (table or query)
|
f"or QueryResource(database=..., query=...) instead."
|
||||||
resource_obj = self.resource_for_action(
|
|
||||||
action, parent=resource[0], child=resource[1]
|
|
||||||
)
|
)
|
||||||
else:
|
|
||||||
resource_obj = None
|
|
||||||
|
|
||||||
# Check if actor can see it
|
# Check if actor can see it
|
||||||
if not await self.allowed(action=action, resource=resource_obj, actor=actor):
|
if not await self.allowed(action=action, resource=resource, actor=actor):
|
||||||
return False, False
|
return False, False
|
||||||
|
|
||||||
# Check if anonymous user can see it (for "private" flag)
|
# Check if anonymous user can see it (for "private" flag)
|
||||||
if not await self.allowed(action=action, resource=resource_obj, actor=None):
|
if not await self.allowed(action=action, resource=resource, actor=None):
|
||||||
# Actor can see it but anonymous cannot - it's private
|
# Actor can see it but anonymous cannot - it's private
|
||||||
return True, True
|
return True, True
|
||||||
|
|
||||||
|
|
@ -1386,12 +1382,14 @@ class Datasette:
|
||||||
except IndexError:
|
except IndexError:
|
||||||
return {}
|
return {}
|
||||||
# Ensure user has permission to view the referenced table
|
# Ensure user has permission to view the referenced table
|
||||||
|
from datasette.resources import TableResource
|
||||||
|
|
||||||
other_table = fk["other_table"]
|
other_table = fk["other_table"]
|
||||||
other_column = fk["other_column"]
|
other_column = fk["other_column"]
|
||||||
visible, _ = await self.check_visibility(
|
visible, _ = await self.check_visibility(
|
||||||
actor,
|
actor,
|
||||||
action="view-table",
|
action="view-table",
|
||||||
resource=(database, other_table),
|
resource=TableResource(database=database, table=other_table),
|
||||||
)
|
)
|
||||||
if not visible:
|
if not visible:
|
||||||
return {}
|
return {}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ from typing import List
|
||||||
|
|
||||||
from datasette.events import AlterTableEvent, CreateTableEvent, InsertRowsEvent
|
from datasette.events import AlterTableEvent, CreateTableEvent, InsertRowsEvent
|
||||||
from datasette.database import QueryInterrupted
|
from datasette.database import QueryInterrupted
|
||||||
from datasette.resources import DatabaseResource
|
from datasette.resources import DatabaseResource, QueryResource
|
||||||
from datasette.utils import (
|
from datasette.utils import (
|
||||||
add_cors_headers,
|
add_cors_headers,
|
||||||
await_me_maybe,
|
await_me_maybe,
|
||||||
|
|
@ -51,7 +51,7 @@ class DatabaseView(View):
|
||||||
visible, private = await datasette.check_visibility(
|
visible, private = await datasette.check_visibility(
|
||||||
request.actor,
|
request.actor,
|
||||||
action="view-database",
|
action="view-database",
|
||||||
resource=database,
|
resource=DatabaseResource(database=database),
|
||||||
)
|
)
|
||||||
if not visible:
|
if not visible:
|
||||||
raise Forbidden("You do not have permission to view this database")
|
raise Forbidden("You do not have permission to view this database")
|
||||||
|
|
@ -541,7 +541,7 @@ class QueryView(View):
|
||||||
visible, private = await datasette.check_visibility(
|
visible, private = await datasette.check_visibility(
|
||||||
request.actor,
|
request.actor,
|
||||||
action="view-query",
|
action="view-query",
|
||||||
resource=(database, canned_query["name"]),
|
resource=QueryResource(database=database, query=canned_query["name"]),
|
||||||
)
|
)
|
||||||
if not visible:
|
if not visible:
|
||||||
raise Forbidden("You do not have permission to view this query")
|
raise Forbidden("You do not have permission to view this query")
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,7 @@ class RowView(DataView):
|
||||||
visible, private = await self.ds.check_visibility(
|
visible, private = await self.ds.check_visibility(
|
||||||
request.actor,
|
request.actor,
|
||||||
action="view-table",
|
action="view-table",
|
||||||
resource=(database, table),
|
resource=TableResource(database=database, table=table),
|
||||||
)
|
)
|
||||||
if not visible:
|
if not visible:
|
||||||
raise Forbidden("You do not have permission to view this table")
|
raise Forbidden("You do not have permission to view this table")
|
||||||
|
|
|
||||||
|
|
@ -789,7 +789,9 @@ class ApiExplorerView(BaseView):
|
||||||
if name == "_internal":
|
if name == "_internal":
|
||||||
continue
|
continue
|
||||||
database_visible, _ = await self.ds.check_visibility(
|
database_visible, _ = await self.ds.check_visibility(
|
||||||
request.actor, action="view-database", resource=name
|
request.actor,
|
||||||
|
action="view-database",
|
||||||
|
resource=DatabaseResource(database=name),
|
||||||
)
|
)
|
||||||
if not database_visible:
|
if not database_visible:
|
||||||
continue
|
continue
|
||||||
|
|
@ -799,7 +801,7 @@ class ApiExplorerView(BaseView):
|
||||||
visible, _ = await self.ds.check_visibility(
|
visible, _ = await self.ds.check_visibility(
|
||||||
request.actor,
|
request.actor,
|
||||||
action="view-table",
|
action="view-table",
|
||||||
resource=(name, table),
|
resource=TableResource(database=name, table=table),
|
||||||
)
|
)
|
||||||
if not visible:
|
if not visible:
|
||||||
continue
|
continue
|
||||||
|
|
|
||||||
|
|
@ -978,7 +978,7 @@ async def table_view_data(
|
||||||
visible, private = await datasette.check_visibility(
|
visible, private = await datasette.check_visibility(
|
||||||
request.actor,
|
request.actor,
|
||||||
action="view-table",
|
action="view-table",
|
||||||
resource=(database_name, table_name),
|
resource=TableResource(database=database_name, table=table_name),
|
||||||
)
|
)
|
||||||
if not visible:
|
if not visible:
|
||||||
raise Forbidden("You do not have permission to view this table")
|
raise Forbidden("You do not have permission to view this table")
|
||||||
|
|
|
||||||
|
|
@ -454,20 +454,17 @@ Example:
|
||||||
|
|
||||||
.. _datasette_check_visibility:
|
.. _datasette_check_visibility:
|
||||||
|
|
||||||
await .check_visibility(actor, action=None, resource=None, permissions=None)
|
await .check_visibility(actor, action, resource=None)
|
||||||
----------------------------------------------------------------------------
|
-----------------------------------------------------
|
||||||
|
|
||||||
``actor`` - dictionary
|
``actor`` - dictionary
|
||||||
The authenticated actor. This is usually ``request.actor``.
|
The authenticated actor. This is usually ``request.actor``.
|
||||||
|
|
||||||
``action`` - string, optional
|
``action`` - string
|
||||||
The name of the action that is being permission checked.
|
The name of the action that is being permission checked.
|
||||||
|
|
||||||
``resource`` - string or tuple, optional
|
``resource`` - Resource object, optional
|
||||||
The resource, e.g. the name of the database, or a tuple of two strings containing the name of the database and the name of the table. Only some permissions apply to a resource.
|
The resource being checked, as a Resource object such as ``DatabaseResource(database=...)``, ``TableResource(database=..., table=...)``, or ``QueryResource(database=..., query=...)``. Only some permissions apply to a resource.
|
||||||
|
|
||||||
``permissions`` - list of ``action`` strings or ``(action, resource)`` tuples, optional
|
|
||||||
Provide this instead of ``action`` and ``resource`` to check multiple permissions at once.
|
|
||||||
|
|
||||||
This convenience method can be used to answer the question "should this item be considered private, in that it is visible to me but it is not visible to anonymous users?"
|
This convenience method can be used to answer the question "should this item be considered private, in that it is visible to me but it is not visible to anonymous users?"
|
||||||
|
|
||||||
|
|
@ -477,23 +474,12 @@ This example checks if the user can access a specific table, and sets ``private`
|
||||||
|
|
||||||
.. code-block:: python
|
.. code-block:: python
|
||||||
|
|
||||||
|
from datasette.resources import TableResource
|
||||||
|
|
||||||
visible, private = await datasette.check_visibility(
|
visible, private = await datasette.check_visibility(
|
||||||
request.actor,
|
request.actor,
|
||||||
action="view-table",
|
action="view-table",
|
||||||
resource=(database, table),
|
resource=TableResource(database=database, table=table),
|
||||||
)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
visible, private = await datasette.check_visibility(
|
|
||||||
request.actor,
|
|
||||||
permissions=[
|
|
||||||
("view-table", (database, table)),
|
|
||||||
("view-database", database),
|
|
||||||
"view-instance",
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
|
|
||||||
.. _datasette_create_token:
|
.. _datasette_create_token:
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ Tests for the datasette.app.Datasette class
|
||||||
import dataclasses
|
import dataclasses
|
||||||
from datasette import Forbidden, Context
|
from datasette import Forbidden, Context
|
||||||
from datasette.app import Datasette, Database
|
from datasette.app import Datasette, Database
|
||||||
|
from datasette.resources import DatabaseResource
|
||||||
from itsdangerous import BadSignature
|
from itsdangerous import BadSignature
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
|
|
@ -93,7 +94,7 @@ ALLOW_ROOT = {"allow": {"id": "root"}}
|
||||||
None,
|
None,
|
||||||
{"databases": {"_memory": ALLOW_ROOT}},
|
{"databases": {"_memory": ALLOW_ROOT}},
|
||||||
"view-database",
|
"view-database",
|
||||||
"_memory",
|
DatabaseResource(database="_memory"),
|
||||||
False,
|
False,
|
||||||
False,
|
False,
|
||||||
),
|
),
|
||||||
|
|
@ -101,7 +102,7 @@ ALLOW_ROOT = {"allow": {"id": "root"}}
|
||||||
ROOT,
|
ROOT,
|
||||||
{"databases": {"_memory": ALLOW_ROOT}},
|
{"databases": {"_memory": ALLOW_ROOT}},
|
||||||
"view-database",
|
"view-database",
|
||||||
"_memory",
|
DatabaseResource(database="_memory"),
|
||||||
True,
|
True,
|
||||||
True,
|
True,
|
||||||
),
|
),
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue