mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Migrate view-query permission to SQL-based system, refs #2510
This change integrates canned queries with Datasette's new SQL-based permissions system by making the following changes: 1. **Default canned_queries plugin hook**: Added a new hookimpl in default_permissions.py that returns canned queries from datasette configuration. This extracts config-reading logic into a plugin hook, allowing QueryResource to discover all queries. 2. **Async resources_sql()**: Converted Resource.resources_sql() from a synchronous class method returning a string to an async method that receives the datasette instance. This allows QueryResource to call plugin hooks and query the database. 3. **QueryResource implementation**: Implemented QueryResource.resources_sql() to gather all canned queries by: - Querying catalog_databases for all databases - Calling canned_queries hooks for each database with actor=None - Building a UNION ALL SQL query of all (database, query_name) pairs - Properly escaping single quotes in resource names 4. **Simplified get_canned_queries()**: Removed config-reading logic since it's now handled by the default plugin hook. 5. **Added view-query to default allow**: Added "view-query" to the default_allow_actions set so canned queries are accessible by default. 6. **Removed xfail markers**: Removed test xfail markers from: - tests/test_canned_queries.py (entire module) - tests/test_html.py (2 tests) - tests/test_permissions.py (1 test) - tests/test_plugins.py (1 test) All canned query tests now pass with the new permission system. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
60ed646d45
commit
82cc3d5c86
10 changed files with 56 additions and 82 deletions
|
|
@ -922,9 +922,7 @@ class Datasette:
|
|||
return self._app_css_hash
|
||||
|
||||
async def get_canned_queries(self, database_name, actor):
|
||||
queries = (
|
||||
((self.config or {}).get("databases") or {}).get(database_name) or {}
|
||||
).get("queries") or {}
|
||||
queries = {}
|
||||
for more_queries in pm.hook.canned_queries(
|
||||
datasette=self,
|
||||
database=database_name,
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ async def permission_resources_sql(datasette, actor, action):
|
|||
"view-database",
|
||||
"view-database-download",
|
||||
"view-table",
|
||||
"view-query",
|
||||
"execute-sql",
|
||||
}
|
||||
if action in default_allow_actions:
|
||||
|
|
@ -335,3 +336,12 @@ def skip_csrf(scope):
|
|||
headers = scope.get("headers") or {}
|
||||
if dict(headers).get(b"content-type") == b"application/json":
|
||||
return True
|
||||
|
||||
|
||||
@hookimpl
|
||||
def canned_queries(datasette, database, actor):
|
||||
"""Return canned queries from datasette configuration."""
|
||||
queries = (
|
||||
((datasette.config or {}).get("databases") or {}).get(database) or {}
|
||||
).get("queries") or {}
|
||||
return queries
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@ class InstanceResource(Resource):
|
|||
super().__init__(parent=None, child=None)
|
||||
|
||||
@classmethod
|
||||
def resources_sql(cls) -> str:
|
||||
async def resources_sql(cls, datasette) -> str:
|
||||
return "SELECT NULL AS parent, NULL AS child"
|
||||
|
||||
|
||||
|
|
@ -27,7 +27,7 @@ class DatabaseResource(Resource):
|
|||
super().__init__(parent=database, child=None)
|
||||
|
||||
@classmethod
|
||||
def resources_sql(cls) -> str:
|
||||
async def resources_sql(cls, datasette) -> str:
|
||||
return """
|
||||
SELECT database_name AS parent, NULL AS child
|
||||
FROM catalog_databases
|
||||
|
|
@ -44,7 +44,7 @@ class TableResource(Resource):
|
|||
super().__init__(parent=database, child=table)
|
||||
|
||||
@classmethod
|
||||
def resources_sql(cls) -> str:
|
||||
async def resources_sql(cls, datasette) -> str:
|
||||
return """
|
||||
SELECT database_name AS parent, table_name AS child
|
||||
FROM catalog_tables
|
||||
|
|
@ -64,6 +64,41 @@ class QueryResource(Resource):
|
|||
super().__init__(parent=database, child=query)
|
||||
|
||||
@classmethod
|
||||
def resources_sql(cls) -> str:
|
||||
# TODO: Need catalog for queries
|
||||
return "SELECT NULL AS parent, NULL AS child WHERE 0"
|
||||
async def resources_sql(cls, datasette) -> str:
|
||||
from datasette.plugins import pm
|
||||
from datasette.utils import await_me_maybe
|
||||
|
||||
# Get all databases from catalog
|
||||
db = datasette.get_internal_database()
|
||||
result = await db.execute("SELECT database_name FROM catalog_databases")
|
||||
databases = [row[0] for row in result.rows]
|
||||
|
||||
# Gather all canned queries from all databases
|
||||
query_pairs = []
|
||||
for database_name in databases:
|
||||
# Call the hook to get queries (including from config via default plugin)
|
||||
for queries_result in pm.hook.canned_queries(
|
||||
datasette=datasette,
|
||||
database=database_name,
|
||||
actor=None, # Get ALL queries for resource enumeration
|
||||
):
|
||||
queries = await await_me_maybe(queries_result)
|
||||
if queries:
|
||||
for query_name in queries.keys():
|
||||
query_pairs.append((database_name, query_name))
|
||||
|
||||
# Build SQL
|
||||
if not query_pairs:
|
||||
return "SELECT NULL AS parent, NULL AS child WHERE 0"
|
||||
|
||||
# Generate UNION ALL query
|
||||
selects = []
|
||||
for db_name, query_name in query_pairs:
|
||||
# Escape single quotes by doubling them
|
||||
db_escaped = db_name.replace("'", "''")
|
||||
query_escaped = query_name.replace("'", "''")
|
||||
selects.append(
|
||||
f"SELECT '{db_escaped}' AS parent, '{query_escaped}' AS child"
|
||||
)
|
||||
|
||||
return " UNION ALL ".join(selects)
|
||||
|
|
|
|||
|
|
@ -177,7 +177,7 @@ async def _build_single_action_sql(
|
|||
raise ValueError(f"Unknown action: {action}")
|
||||
|
||||
# Get base resources SQL from the resource class
|
||||
base_resources_sql = action_obj.resource_class.resources_sql()
|
||||
base_resources_sql = await action_obj.resource_class.resources_sql(datasette)
|
||||
|
||||
# Get all permission rule fragments from plugins via the hook
|
||||
rule_results = pm.hook.permission_resources_sql(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue