Remove ensure_permissions() and simplify check_visibility()

This commit removes the ensure_permissions() method entirely and updates
all code to use direct allowed() checks instead.

Key changes:
- Removed ensure_permissions() method from datasette/app.py
- Simplified check_visibility() to check single permissions directly
- Replaced all ensure_permissions() calls with direct allowed() checks
- Updated all check_visibility() calls to use only primary permission
- Added Forbidden import to index.py

Why this change:
- ensure_permissions() used OR logic (any permission passes) which
  conflicted with explicit denies in the config
- For example, check_visibility() called ensure_permissions() with
  ["view-database", "view-instance"] and if view-instance passed,
  it would show pages even with explicit database deny
- The new approach checks only the specific permission needed for
  each resource, respecting explicit denies

Test improvements: 64 failures → 41 failures

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Simon Willison 2025-10-24 15:24:10 -07:00
commit 6584c9e03f
7 changed files with 83 additions and 128 deletions

View file

@ -85,21 +85,23 @@ ALLOW_ROOT = {"allow": {"id": "root"}}
@pytest.mark.asyncio
@pytest.mark.parametrize(
"actor,config,permissions,should_allow,expected_private",
"actor,config,action,resource,should_allow,expected_private",
(
(None, ALLOW_ROOT, ["view-instance"], False, False),
(ROOT, ALLOW_ROOT, ["view-instance"], True, True),
(None, ALLOW_ROOT, "view-instance", None, False, False),
(ROOT, ALLOW_ROOT, "view-instance", None, True, True),
(
None,
{"databases": {"_memory": ALLOW_ROOT}},
[("view-database", "_memory")],
"view-database",
"_memory",
False,
False,
),
(
ROOT,
{"databases": {"_memory": ALLOW_ROOT}},
[("view-database", "_memory")],
"view-database",
"_memory",
True,
True,
),
@ -107,24 +109,19 @@ ALLOW_ROOT = {"allow": {"id": "root"}}
(
ROOT,
{"allow": True},
["view-instance"],
"view-instance",
None,
True,
False,
),
),
)
async def test_datasette_ensure_permissions_check_visibility(
actor, config, permissions, should_allow, expected_private
async def test_datasette_check_visibility(
actor, config, action, resource, should_allow, expected_private
):
ds = Datasette([], memory=True, config=config)
await ds.invoke_startup()
if not should_allow:
with pytest.raises(Forbidden):
await ds.ensure_permissions(actor, permissions)
else:
await ds.ensure_permissions(actor, permissions)
# And try check_visibility too:
visible, private = await ds.check_visibility(actor, permissions=permissions)
visible, private = await ds.check_visibility(actor, action=action, resource=resource)
assert visible == should_allow
assert private == expected_private