datasette/tests/test_actions_sql.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

328 lines
11 KiB
Python
Raw Normal View History

Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
"""
Tests for the new Resource-based permission system.
These tests verify:
1. The new Datasette.allowed_resources() method (with pagination)
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
2. The new Datasette.allowed() method
3. The include_reasons parameter for debugging
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
4. That SQL does the heavy lifting (no Python filtering)
"""
import pytest
import pytest_asyncio
from datasette.app import Datasette
from datasette.permissions import PermissionSQL
from datasette.resources import DatabaseResource, QueryResource, TableResource
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
from datasette import hookimpl
def test_resource_string_representations():
assert str(DatabaseResource("content")) == "content"
assert repr(DatabaseResource("content")) == (
"DatabaseResource(parent='content', child=None)"
)
assert str(TableResource("content", "dogs")) == "content/dogs"
assert repr(TableResource("content", "dogs")) == (
"TableResource(parent='content', child='dogs')"
)
assert str(QueryResource("content", "insert-a-dog")) == "content/insert-a-dog"
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
# Test plugin that provides permission rules
class PermissionRulesPlugin:
def __init__(self, rules_callback):
self.rules_callback = rules_callback
@hookimpl
def permission_resources_sql(self, datasette, actor, action):
"""Return permission rules based on the callback"""
return self.rules_callback(datasette, actor, action)
@pytest_asyncio.fixture
async def test_ds():
"""Create a test Datasette instance with sample data"""
ds = Datasette()
await ds.invoke_startup()
# Add test databases with some tables
db = ds.add_memory_database("analytics")
await db.execute_write("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY)")
await db.execute_write("CREATE TABLE IF NOT EXISTS events (id INTEGER PRIMARY KEY)")
await db.execute_write(
"CREATE TABLE IF NOT EXISTS sensitive (id INTEGER PRIMARY KEY)"
)
db2 = ds.add_memory_database("production")
await db2.execute_write(
"CREATE TABLE IF NOT EXISTS customers (id INTEGER PRIMARY KEY)"
)
await db2.execute_write(
"CREATE TABLE IF NOT EXISTS orders (id INTEGER PRIMARY KEY)"
)
# Refresh schemas to populate catalog_tables in internal database
await ds._refresh_schemas()
return ds
@pytest.mark.asyncio
async def test_allowed_resources_global_allow(test_ds):
"""Test allowed_resources() with a global allow rule"""
def rules_callback(datasette, actor, action):
if actor and actor.get("id") == "alice":
sql = "SELECT NULL AS parent, NULL AS child, 1 AS allow, 'global: alice has access' AS reason"
return PermissionSQL(sql=sql)
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
return None
plugin = PermissionRulesPlugin(rules_callback)
2025-11-13 10:31:03 -08:00
test_ds.pm.register(plugin, name="test_plugin")
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
try:
# Use the new allowed_resources() method
result = await test_ds.allowed_resources("view-table", {"id": "alice"})
tables = result.resources
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
# Alice should see all tables
assert len(tables) == 5
assert all(isinstance(t, TableResource) for t in tables)
# Check specific tables are present
table_set = set((t.parent, t.child) for t in tables)
assert ("analytics", "events") in table_set
assert ("analytics", "users") in table_set
assert ("analytics", "sensitive") in table_set
assert ("production", "customers") in table_set
assert ("production", "orders") in table_set
finally:
2025-11-13 10:31:03 -08:00
test_ds.pm.unregister(plugin, name="test_plugin")
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
@pytest.mark.asyncio
async def test_allowed_specific_resource(test_ds):
"""Test allowed() method checks specific resource efficiently"""
def rules_callback(datasette, actor, action):
if actor and actor.get("role") == "analyst":
# Allow analytics database, deny everything else (global deny)
sql = """
SELECT NULL AS parent, NULL AS child, 0 AS allow, 'global deny' AS reason
UNION ALL
SELECT 'analytics' AS parent, NULL AS child, 1 AS allow, 'analyst access' AS reason
"""
return PermissionSQL(sql=sql)
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
return None
plugin = PermissionRulesPlugin(rules_callback)
2025-11-13 10:31:03 -08:00
test_ds.pm.register(plugin, name="test_plugin")
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
try:
actor = {"id": "bob", "role": "analyst"}
# Check specific resources using allowed()
# This should use SQL WHERE clause, not fetch all resources
assert await test_ds.allowed(
action="view-table",
resource=TableResource("analytics", "users"),
actor=actor,
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
)
assert await test_ds.allowed(
action="view-table",
resource=TableResource("analytics", "events"),
actor=actor,
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
)
assert not await test_ds.allowed(
action="view-table",
resource=TableResource("production", "orders"),
actor=actor,
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
)
finally:
2025-11-13 10:31:03 -08:00
test_ds.pm.unregister(plugin, name="test_plugin")
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
@pytest.mark.asyncio
async def test_allowed_resources_include_reasons(test_ds):
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
def rules_callback(datasette, actor, action):
if actor and actor.get("role") == "analyst":
sql = """
SELECT 'analytics' AS parent, NULL AS child, 1 AS allow,
'parent: analyst access to analytics' AS reason
UNION ALL
SELECT 'analytics' AS parent, 'sensitive' AS child, 0 AS allow,
'child: sensitive data denied' AS reason
"""
return PermissionSQL(sql=sql)
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
return None
plugin = PermissionRulesPlugin(rules_callback)
2025-11-13 10:31:03 -08:00
test_ds.pm.register(plugin, name="test_plugin")
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
try:
# Use allowed_resources with include_reasons to get debugging info
result = await test_ds.allowed_resources(
"view-table", {"id": "bob", "role": "analyst"}, include_reasons=True
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
)
allowed = result.resources
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
# Should get analytics tables except sensitive
assert len(allowed) >= 2 # At least users and events
# Check we can access both resource and reason
for resource in allowed:
assert isinstance(resource, TableResource)
assert isinstance(resource.reasons, list)
if resource.parent == "analytics":
# Should mention parent-level reason in at least one of the reasons
reasons_text = " ".join(resource.reasons).lower()
assert "analyst access" in reasons_text
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
finally:
2025-11-13 10:31:03 -08:00
test_ds.pm.unregister(plugin, name="test_plugin")
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
@pytest.mark.asyncio
async def test_child_deny_overrides_parent_allow(test_ds):
"""Test that child-level DENY beats parent-level ALLOW"""
def rules_callback(datasette, actor, action):
if actor and actor.get("role") == "analyst":
sql = """
SELECT 'analytics' AS parent, NULL AS child, 1 AS allow,
'parent: allow analytics' AS reason
UNION ALL
SELECT 'analytics' AS parent, 'sensitive' AS child, 0 AS allow,
'child: deny sensitive' AS reason
"""
return PermissionSQL(sql=sql)
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
return None
plugin = PermissionRulesPlugin(rules_callback)
2025-11-13 10:31:03 -08:00
test_ds.pm.register(plugin, name="test_plugin")
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
try:
actor = {"id": "bob", "role": "analyst"}
result = await test_ds.allowed_resources("view-table", actor)
tables = result.resources
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
# Should see analytics tables except sensitive
analytics_tables = [t for t in tables if t.parent == "analytics"]
assert len(analytics_tables) >= 2
table_names = {t.child for t in analytics_tables}
assert "users" in table_names
assert "events" in table_names
assert "sensitive" not in table_names
# Verify with allowed() method
assert await test_ds.allowed(
action="view-table",
resource=TableResource("analytics", "users"),
actor=actor,
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
)
assert not await test_ds.allowed(
action="view-table",
resource=TableResource("analytics", "sensitive"),
actor=actor,
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
)
finally:
2025-11-13 10:31:03 -08:00
test_ds.pm.unregister(plugin, name="test_plugin")
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
@pytest.mark.asyncio
async def test_child_allow_overrides_parent_deny(test_ds):
"""Test that child-level ALLOW beats parent-level DENY"""
def rules_callback(datasette, actor, action):
if actor and actor.get("id") == "carol":
sql = """
SELECT 'production' AS parent, NULL AS child, 0 AS allow,
'parent: deny production' AS reason
UNION ALL
SELECT 'production' AS parent, 'orders' AS child, 1 AS allow,
'child: carol can see orders' AS reason
"""
return PermissionSQL(sql=sql)
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
return None
plugin = PermissionRulesPlugin(rules_callback)
2025-11-13 10:31:03 -08:00
test_ds.pm.register(plugin, name="test_plugin")
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
try:
actor = {"id": "carol"}
result = await test_ds.allowed_resources("view-table", actor)
tables = result.resources
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
# Should only see production.orders
production_tables = [t for t in tables if t.parent == "production"]
assert len(production_tables) == 1
assert production_tables[0].child == "orders"
# Verify with allowed() method
assert await test_ds.allowed(
action="view-table",
resource=TableResource("production", "orders"),
actor=actor,
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
)
assert not await test_ds.allowed(
action="view-table",
resource=TableResource("production", "customers"),
actor=actor,
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
)
finally:
2025-11-13 10:31:03 -08:00
test_ds.pm.unregister(plugin, name="test_plugin")
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
@pytest.mark.asyncio
async def test_sql_does_filtering_not_python(test_ds):
"""
Verify that allowed() uses SQL WHERE clause, not Python filtering.
This test doesn't actually verify the SQL itself (that would require
query introspection), but it demonstrates the API contract.
"""
def rules_callback(datasette, actor, action):
# Deny everything by default, allow only analytics.users specifically
sql = """
SELECT NULL AS parent, NULL AS child, 0 AS allow,
'global deny' AS reason
UNION ALL
SELECT 'analytics' AS parent, 'users' AS child, 1 AS allow,
'specific allow' AS reason
"""
return PermissionSQL(sql=sql)
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
plugin = PermissionRulesPlugin(rules_callback)
2025-11-13 10:31:03 -08:00
test_ds.pm.register(plugin, name="test_plugin")
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
try:
actor = {"id": "dave"}
# allowed() should execute a targeted SQL query
# NOT fetch all resources and filter in Python
assert await test_ds.allowed(
action="view-table",
resource=TableResource("analytics", "users"),
actor=actor,
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
)
assert not await test_ds.allowed(
action="view-table",
resource=TableResource("analytics", "events"),
actor=actor,
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
)
# allowed_resources() should also use SQL filtering
result = await test_ds.allowed_resources("view-table", actor)
tables = result.resources
Implement resource-based permission system with SQL-driven access control This introduces a new hierarchical permission system that uses SQL queries for efficient permission checking across resources. The system replaces the older permission_allowed() pattern with a more flexible resource-based approach. Core changes: - New Resource ABC and Action dataclass in datasette/permissions.py * Resources represent hierarchical entities (instance, database, table) * Each resource type implements resources_sql() to list all instances * Actions define operations on resources with cascading rules - New plugin hook: register_actions(datasette) * Plugins register actions with their associated resource types * Replaces register_permissions() and register_resource_types() * See docs/plugin_hooks.rst for full documentation - Three new Datasette methods for permission checks: * allowed_resources(action, actor) - returns list[Resource] * allowed_resources_with_reasons(action, actor) - for debugging * allowed(action, resource, actor) - checks single resource * All use SQL for filtering, never Python iteration - New /-/tables endpoint (TablesView) * Returns JSON list of tables user can view * Supports ?q= parameter for regex filtering * Format: {"matches": [{"name": "db/table", "url": "/db/table"}]} * Respects all permission rules from configuration and plugins - SQL-based permission evaluation (datasette/utils/actions_sql.py) * Cascading rules: child-level → parent-level → global-level * DENY beats ALLOW at same specificity * Uses CTEs for efficient SQL-only filtering * Combines permission_resources_sql() hook results - Default actions in datasette/default_actions.py * InstanceResource, DatabaseResource, TableResource, QueryResource * Core actions: view-instance, view-database, view-table, etc. - Fixed default_permissions.py to handle database-level allow blocks * Now creates parent-level rules for view-table action * Fixes: datasette ... -s databases.fixtures.allow.id root Documentation: - Comprehensive register_actions() hook documentation - Detailed resources_sql() method explanation - /-/tables endpoint documentation in docs/introspection.rst - Deprecated register_permissions() with migration guide Tests: - tests/test_actions_sql.py: 7 tests for core permission API - tests/test_tables_endpoint.py: 13 tests for /-/tables endpoint - All 118 documentation tests pass - Tests verify SQL does filtering (not Python) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
2025-10-20 15:59:37 -07:00
assert len(tables) == 1
assert tables[0].parent == "analytics"
assert tables[0].child == "users"
finally:
2025-11-13 10:31:03 -08:00
test_ds.pm.unregister(plugin, name="test_plugin")