diff --git a/datasette/templates/database.html b/datasette/templates/database.html
index e47b241839..fc88003c64 100644
--- a/datasette/templates/database.html
+++ b/datasette/templates/database.html
@@ -60,7 +60,7 @@
Views
Queries
{% endif %}
diff --git a/datasette/utils/__init__.py b/datasette/utils/__init__.py
index eb118f38e5..077728f4ce 100644
--- a/datasette/utils/__init__.py
+++ b/datasette/utils/__init__.py
@@ -857,6 +857,7 @@ def call_with_supported_arguments(fn, **kwargs):
def actor_matches_allow(actor, allow):
+ actor = actor or {}
if allow is None:
return True
for key, values in allow.items():
diff --git a/datasette/views/database.py b/datasette/views/database.py
index 558dd0f09a..abc7d3bb8a 100644
--- a/datasette/views/database.py
+++ b/datasette/views/database.py
@@ -2,6 +2,7 @@
import jinja2
from datasette.utils import (
+ actor_matches_allow,
to_css_class,
validate_sql_select,
is_url,
@@ -53,6 +54,16 @@ async def data(self, request, database, hash, default_labels=False, _size=None):
)
tables.sort(key=lambda t: (t["hidden"], t["name"]))
+ canned_queries = [
+ dict(
+ query,
+ requires_auth=not actor_matches_allow(None, query.get("allow", None)),
+ )
+ for query in self.ds.get_canned_queries(database)
+ if actor_matches_allow(
+ request.scope.get("actor", None), query.get("allow", None)
+ )
+ ]
return (
{
"database": database,
@@ -60,7 +71,7 @@ async def data(self, request, database, hash, default_labels=False, _size=None):
"tables": tables,
"hidden_count": len([t for t in tables if t["hidden"]]),
"views": views,
- "queries": self.ds.get_canned_queries(database),
+ "queries": canned_queries,
},
{
"show_hidden": request.args.get("_show_hidden"),
diff --git a/tests/test_canned_write.py b/tests/test_canned_write.py
index be838063f3..5b5756b0d1 100644
--- a/tests/test_canned_write.py
+++ b/tests/test_canned_write.py
@@ -24,6 +24,7 @@ def canned_write_client():
"sql": "delete from names where rowid = :rowid",
"write": True,
"on_success_message": "Name deleted",
+ "allow": {"id": "root"},
},
"update_name": {
"sql": "update names set name = :name where rowid = :rowid",
@@ -52,7 +53,11 @@ def test_insert(canned_write_client):
def test_custom_success_message(canned_write_client):
response = canned_write_client.post(
- "/data/delete_name", {"rowid": 1}, allow_redirects=False, csrftoken_from=True
+ "/data/delete_name",
+ {"rowid": 1},
+ cookies={"ds_actor": canned_write_client.ds.sign({"id": "root"}, "actor")},
+ allow_redirects=False,
+ csrftoken_from=True,
)
assert 302 == response.status
messages = canned_write_client.ds.unsign(
@@ -93,3 +98,27 @@ def test_insert_error(canned_write_client):
def test_custom_params(canned_write_client):
response = canned_write_client.get("/data/update_name?extra=foo")
assert '' in response.text
+
+
+def test_canned_query_permissions_on_database_page(canned_write_client):
+ # Without auth only shows three queries
+ query_names = [
+ q["name"] for q in canned_write_client.get("/data.json").json["queries"]
+ ]
+ assert ["add_name", "add_name_specify_id", "update_name"] == query_names
+
+ # With auth shows four
+ response = canned_write_client.get(
+ "/data.json",
+ cookies={"ds_actor": canned_write_client.ds.sign({"id": "root"}, "actor")},
+ )
+ assert 200 == response.status
+ assert [
+ {"name": "add_name", "requires_auth": False},
+ {"name": "add_name_specify_id", "requires_auth": False},
+ {"name": "delete_name", "requires_auth": True},
+ {"name": "update_name", "requires_auth": False},
+ ] == [
+ {"name": q["name"], "requires_auth": q["requires_auth"]}
+ for q in response.json["queries"]
+ ]
diff --git a/tests/test_utils.py b/tests/test_utils.py
index 7c24648ad5..975ed0fdf0 100644
--- a/tests/test_utils.py
+++ b/tests/test_utils.py
@@ -466,6 +466,9 @@ def test_multi_params(data, should_raise):
[
({"id": "root"}, None, True),
({"id": "root"}, {}, False),
+ (None, None, True),
+ (None, {}, False),
+ (None, {"id": "root"}, False),
# Special "*" value for any key:
({"id": "root"}, {"id": "*"}, True),
({}, {"id": "*"}, False),