Transform actor restrictions into SQL permission rules

Actor restrictions (_r) now integrate with the SQL permission layer via
the permission_resources_sql() hook instead of acting as a post-filter.

This fixes the issue where allowed_resources() didn't respect restrictions,
causing incorrect database/table listings at /.json and /database.json
endpoints for restricted actors.

Key changes:
- Add _restriction_permission_rules() function to generate SQL rules from _r
- Restrictions create global DENY + specific ALLOW rules using allowlist
- Restrictions act as gating filter BEFORE config/root/default permissions
- Remove post-filter check from allowed() method (now redundant)
- Skip default allow rules when actor has restrictions
- Add comprehensive tests for restriction filtering behavior

The cascading permission logic (child → parent → global) ensures that
allowlisted resources override the global deny, while non-allowlisted
resources are blocked.

Closes #2534
This commit is contained in:
Simon Willison 2025-10-25 15:43:14 -07:00
commit fb9cd5c72c
3 changed files with 282 additions and 39 deletions

View file

@ -1290,25 +1290,6 @@ class Datasette:
child=resource.child,
)
# Check actor restrictions after SQL permissions
# If the SQL check says "yes" but actor has restrictions, verify action is allowed
if result and actor and "_r" in actor:
from datasette.default_permissions import restrictions_allow_action
# Convert Resource to old-style format for restrictions check
if resource.parent and resource.child:
old_style_resource = (resource.parent, resource.child)
elif resource.parent:
old_style_resource = resource.parent
else:
old_style_resource = None
# If restrictions don't allow this action, deny it
if not restrictions_allow_action(
self, actor["_r"], action, old_style_resource
):
result = False
# Log the permission check for debugging
self._permission_checks.append(
PermissionCheck(