mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Refactor AllowedResourcesView to use datasette.allowed_resources()
Refs https://github.com/simonw/datasette/issues/2527#issuecomment-3444586698
This commit is contained in:
parent
e8b79970fb
commit
4d03e8c12e
1 changed files with 49 additions and 157 deletions
|
|
@ -5,13 +5,9 @@ from datasette.utils.asgi import Response, Forbidden
|
||||||
from datasette.utils import (
|
from datasette.utils import (
|
||||||
actor_matches_allow,
|
actor_matches_allow,
|
||||||
add_cors_headers,
|
add_cors_headers,
|
||||||
await_me_maybe,
|
|
||||||
tilde_encode,
|
tilde_encode,
|
||||||
tilde_decode,
|
tilde_decode,
|
||||||
)
|
)
|
||||||
from datasette.permissions import PermissionSQL
|
|
||||||
from datasette.utils.permissions import resolve_permissions_from_catalog
|
|
||||||
from datasette.plugins import pm
|
|
||||||
from .base import BaseView, View
|
from .base import BaseView, View
|
||||||
import secrets
|
import secrets
|
||||||
import urllib
|
import urllib
|
||||||
|
|
@ -263,172 +259,68 @@ class AllowedResourcesView(BaseView):
|
||||||
page_size = max_page_size
|
page_size = max_page_size
|
||||||
offset = (page - 1) * page_size
|
offset = (page - 1) * page_size
|
||||||
|
|
||||||
candidate_sql, candidate_params = self.CANDIDATE_SQL[action]
|
# Use the simplified allowed_resources method
|
||||||
|
try:
|
||||||
db = self.ds.get_internal_database()
|
allowed_resources = await self.ds.allowed_resources(
|
||||||
required_tables = set()
|
|
||||||
if "catalog_tables" in candidate_sql:
|
|
||||||
required_tables.add("catalog_tables")
|
|
||||||
if "catalog_databases" in candidate_sql:
|
|
||||||
required_tables.add("catalog_databases")
|
|
||||||
|
|
||||||
for table in required_tables:
|
|
||||||
if not await db.table_exists(table):
|
|
||||||
headers = {}
|
|
||||||
if self.ds.cors:
|
|
||||||
add_cors_headers(headers)
|
|
||||||
return Response.json(
|
|
||||||
{
|
|
||||||
"action": action,
|
|
||||||
"actor_id": (actor or {}).get("id") if actor else None,
|
|
||||||
"page": page,
|
|
||||||
"page_size": page_size,
|
|
||||||
"total": 0,
|
|
||||||
"items": [],
|
|
||||||
},
|
|
||||||
headers=headers,
|
|
||||||
)
|
|
||||||
|
|
||||||
# Check if this action requires another action
|
|
||||||
action_obj = self.ds.actions.get(action)
|
|
||||||
if action_obj and action_obj.also_requires:
|
|
||||||
# Need to combine results from both actions
|
|
||||||
# Get allowed resources for the main action
|
|
||||||
plugins = []
|
|
||||||
for block in pm.hook.permission_resources_sql(
|
|
||||||
datasette=self.ds,
|
|
||||||
actor=actor,
|
|
||||||
action=action,
|
action=action,
|
||||||
):
|
|
||||||
block = await await_me_maybe(block)
|
|
||||||
if block is None:
|
|
||||||
continue
|
|
||||||
if isinstance(block, (list, tuple)):
|
|
||||||
candidates = block
|
|
||||||
else:
|
|
||||||
candidates = [block]
|
|
||||||
for candidate in candidates:
|
|
||||||
if candidate is None:
|
|
||||||
continue
|
|
||||||
plugins.append(candidate)
|
|
||||||
|
|
||||||
main_rows = await resolve_permissions_from_catalog(
|
|
||||||
db,
|
|
||||||
actor=actor,
|
actor=actor,
|
||||||
plugins=plugins,
|
parent=parent_filter,
|
||||||
action=action,
|
|
||||||
candidate_sql=candidate_sql,
|
|
||||||
candidate_params=candidate_params,
|
|
||||||
implicit_deny=True,
|
|
||||||
)
|
)
|
||||||
main_allowed = {
|
except Exception:
|
||||||
(row["parent"], row["child"]) for row in main_rows if row["allow"] == 1
|
# If catalog tables don't exist yet, return empty results
|
||||||
|
headers = {}
|
||||||
|
if self.ds.cors:
|
||||||
|
add_cors_headers(headers)
|
||||||
|
return Response.json(
|
||||||
|
{
|
||||||
|
"action": action,
|
||||||
|
"actor_id": actor_id,
|
||||||
|
"page": page,
|
||||||
|
"page_size": page_size,
|
||||||
|
"total": 0,
|
||||||
|
"items": [],
|
||||||
|
},
|
||||||
|
headers=headers,
|
||||||
|
)
|
||||||
|
|
||||||
|
# Convert to list of dicts with resource path
|
||||||
|
allowed_rows = []
|
||||||
|
for resource in allowed_resources:
|
||||||
|
parent_val = resource.parent
|
||||||
|
child_val = resource.child
|
||||||
|
|
||||||
|
# Build resource path
|
||||||
|
if parent_val is None:
|
||||||
|
resource_path = "/"
|
||||||
|
elif child_val is None:
|
||||||
|
resource_path = f"/{parent_val}"
|
||||||
|
else:
|
||||||
|
resource_path = f"/{parent_val}/{child_val}"
|
||||||
|
|
||||||
|
row = {
|
||||||
|
"parent": parent_val,
|
||||||
|
"child": child_val,
|
||||||
|
"resource": resource_path,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Get allowed resources for the required action
|
# Add debug fields if available
|
||||||
required_action = action_obj.also_requires
|
if has_debug_permission and hasattr(resource, "_reason"):
|
||||||
required_candidate_sql, required_candidate_params = self.CANDIDATE_SQL.get(
|
row["reason"] = resource._reason
|
||||||
required_action, (None, None)
|
if has_debug_permission and hasattr(resource, "_source_plugin"):
|
||||||
)
|
row["source_plugin"] = resource._source_plugin
|
||||||
if not required_candidate_sql:
|
|
||||||
# If the required action doesn't have candidate SQL, deny everything
|
|
||||||
allowed_rows = []
|
|
||||||
else:
|
|
||||||
required_plugins = []
|
|
||||||
for block in pm.hook.permission_resources_sql(
|
|
||||||
datasette=self.ds,
|
|
||||||
actor=actor,
|
|
||||||
action=required_action,
|
|
||||||
):
|
|
||||||
block = await await_me_maybe(block)
|
|
||||||
if block is None:
|
|
||||||
continue
|
|
||||||
if isinstance(block, (list, tuple)):
|
|
||||||
candidates = block
|
|
||||||
else:
|
|
||||||
candidates = [block]
|
|
||||||
for candidate in candidates:
|
|
||||||
if candidate is None:
|
|
||||||
continue
|
|
||||||
required_plugins.append(candidate)
|
|
||||||
|
|
||||||
required_rows = await resolve_permissions_from_catalog(
|
allowed_rows.append(row)
|
||||||
db,
|
|
||||||
actor=actor,
|
|
||||||
plugins=required_plugins,
|
|
||||||
action=required_action,
|
|
||||||
candidate_sql=required_candidate_sql,
|
|
||||||
candidate_params=required_candidate_params,
|
|
||||||
implicit_deny=True,
|
|
||||||
)
|
|
||||||
required_allowed = {
|
|
||||||
(row["parent"], row["child"])
|
|
||||||
for row in required_rows
|
|
||||||
if row["allow"] == 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Intersect the two sets - only resources allowed by BOTH actions
|
# Apply child filter if specified
|
||||||
allowed_resources = main_allowed & required_allowed
|
|
||||||
|
|
||||||
# Get full row data for the allowed resources
|
|
||||||
allowed_rows = [
|
|
||||||
row
|
|
||||||
for row in main_rows
|
|
||||||
if row["allow"] == 1
|
|
||||||
and (row["parent"], row["child"]) in allowed_resources
|
|
||||||
]
|
|
||||||
else:
|
|
||||||
# No also_requires, use normal path
|
|
||||||
plugins = []
|
|
||||||
for block in pm.hook.permission_resources_sql(
|
|
||||||
datasette=self.ds,
|
|
||||||
actor=actor,
|
|
||||||
action=action,
|
|
||||||
):
|
|
||||||
block = await await_me_maybe(block)
|
|
||||||
if block is None:
|
|
||||||
continue
|
|
||||||
if isinstance(block, (list, tuple)):
|
|
||||||
candidates = block
|
|
||||||
else:
|
|
||||||
candidates = [block]
|
|
||||||
for candidate in candidates:
|
|
||||||
if candidate is None:
|
|
||||||
continue
|
|
||||||
plugins.append(candidate)
|
|
||||||
|
|
||||||
rows = await resolve_permissions_from_catalog(
|
|
||||||
db,
|
|
||||||
actor=actor,
|
|
||||||
plugins=plugins,
|
|
||||||
action=action,
|
|
||||||
candidate_sql=candidate_sql,
|
|
||||||
candidate_params=candidate_params,
|
|
||||||
implicit_deny=True,
|
|
||||||
)
|
|
||||||
|
|
||||||
allowed_rows = [row for row in rows if row["allow"] == 1]
|
|
||||||
if parent_filter is not None:
|
|
||||||
allowed_rows = [
|
|
||||||
row for row in allowed_rows if row["parent"] == parent_filter
|
|
||||||
]
|
|
||||||
if child_filter is not None:
|
if child_filter is not None:
|
||||||
allowed_rows = [row for row in allowed_rows if row["child"] == child_filter]
|
allowed_rows = [row for row in allowed_rows if row["child"] == child_filter]
|
||||||
|
|
||||||
|
# Pagination
|
||||||
total = len(allowed_rows)
|
total = len(allowed_rows)
|
||||||
paged_rows = allowed_rows[offset : offset + page_size]
|
paged_rows = allowed_rows[offset : offset + page_size]
|
||||||
|
|
||||||
items = []
|
# Items are already in the right format
|
||||||
for row in paged_rows:
|
items = paged_rows
|
||||||
item = {
|
|
||||||
"parent": row["parent"],
|
|
||||||
"child": row["child"],
|
|
||||||
"resource": row["resource"],
|
|
||||||
}
|
|
||||||
# Only include sensitive fields if user has permissions-debug
|
|
||||||
if has_debug_permission:
|
|
||||||
item["reason"] = row["reason"]
|
|
||||||
item["source_plugin"] = row["source_plugin"]
|
|
||||||
items.append(item)
|
|
||||||
|
|
||||||
def build_page_url(page_number):
|
def build_page_url(page_number):
|
||||||
pairs = []
|
pairs = []
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue