diff --git a/datasette/views/stored_queries.py b/datasette/views/stored_queries.py index b3813f14..1a2c5d00 100644 --- a/datasette/views/stored_queries.py +++ b/datasette/views/stored_queries.py @@ -413,6 +413,8 @@ class QueryUpdateView(BaseView): actor=request.actor, ): return _error(["Permission denied: need update-query"], 403) + if existing.get("is_trusted"): + return _error(["Trusted queries cannot be updated using the API"], 403) try: data, _ = await _json_or_form_payload(request) diff --git a/tests/test_queries.py b/tests/test_queries.py index 4f2fd1ff..6b2262e0 100644 --- a/tests/test_queries.py +++ b/tests/test_queries.py @@ -961,6 +961,58 @@ async def test_query_update_and_delete_api(): assert await ds.get_query("data", "editable") is None +@pytest.mark.asyncio +async def test_query_update_api_rejects_trusted_queries_but_internal_update_allowed(): + ds = Datasette( + memory=True, + default_deny=True, + config={ + "databases": { + "data": { + "permissions": { + "execute-sql": {"id": "editor"}, + "update-query": {"id": "editor"}, + }, + "queries": { + "trusted_report": { + "sql": "select 1 as one", + "title": "Original", + }, + }, + } + } + }, + ) + ds.add_memory_database("query_update_trusted_api", name="data") + await ds.invoke_startup() + + response = await ds.client.post( + "/data/trusted_report/-/update", + actor={"id": "editor"}, + json={"update": {"sql": "select 2 as two", "title": "Edited"}}, + ) + + assert response.status_code == 403 + assert response.json()["errors"] == [ + "Trusted queries cannot be updated using the API" + ] + query = await ds.get_query("data", "trusted_report") + assert query["is_trusted"] is True + assert query["sql"] == "select 1 as one" + assert query["title"] == "Original" + + await ds.update_query( + "data", + "trusted_report", + sql="select 3 as three", + title="Internal", + ) + query = await ds.get_query("data", "trusted_report") + assert query["is_trusted"] is True + assert query["sql"] == "select 3 as three" + assert query["title"] == "Internal" + + @pytest.mark.asyncio async def test_query_store_api_rejects_magic_parameters(): ds = Datasette(memory=True, default_deny=True)