kopia lustrzana https://github.com/simonw/datasette
Database list on index page respects table/view permissions, refs #811
rodzic
dcec89270a
commit
5598c5de01
|
@ -22,7 +22,7 @@
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</p>
|
</p>
|
||||||
<p>{% for table in database.tables_and_views_truncated %}<a href="{{ database.path }}/{{ table.name|quote_plus
|
<p>{% for table in database.tables_and_views_truncated %}<a href="{{ database.path }}/{{ table.name|quote_plus
|
||||||
}}"{% if table.count %} title="{{ table.count }} rows"{% endif %}>{{ table.name }}</a>{% if not loop.last %}, {% endif %}{% endfor %}{% if database.tables_and_views_more %}, <a href="{{ database.path }}">...</a>{% endif %}</p>
|
}}"{% if table.count %} title="{{ table.count }} rows"{% endif %}>{{ table.name }}</a>{% if table.private %} 🔒{% endif %}{% if not loop.last %}, {% endif %}{% endfor %}{% if database.tables_and_views_more %}, <a href="{{ database.path }}">...</a>{% endif %}</p>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -25,14 +25,22 @@ class IndexView(BaseView):
|
||||||
await self.check_permission(request, "view-instance")
|
await self.check_permission(request, "view-instance")
|
||||||
databases = []
|
databases = []
|
||||||
for name, db in self.ds.databases.items():
|
for name, db in self.ds.databases.items():
|
||||||
visible, private = await check_visibility(
|
visible, database_private = await check_visibility(
|
||||||
self.ds, request.actor, "view-database", "database", name,
|
self.ds, request.actor, "view-database", "database", name,
|
||||||
)
|
)
|
||||||
if not visible:
|
if not visible:
|
||||||
continue
|
continue
|
||||||
table_names = await db.table_names()
|
table_names = await db.table_names()
|
||||||
hidden_table_names = set(await db.hidden_table_names())
|
hidden_table_names = set(await db.hidden_table_names())
|
||||||
views = await db.view_names()
|
|
||||||
|
views = []
|
||||||
|
for view_name in await db.view_names():
|
||||||
|
visible, private = await check_visibility(
|
||||||
|
self.ds, request.actor, "view-table", "table", (name, view_name),
|
||||||
|
)
|
||||||
|
if visible:
|
||||||
|
views.append({"name": view_name, "private": private})
|
||||||
|
|
||||||
# Perform counts only for immutable or DBS with <= COUNT_TABLE_LIMIT tables
|
# Perform counts only for immutable or DBS with <= COUNT_TABLE_LIMIT tables
|
||||||
table_counts = {}
|
table_counts = {}
|
||||||
if not db.is_mutable or db.size < COUNT_DB_SIZE_LIMIT:
|
if not db.is_mutable or db.size < COUNT_DB_SIZE_LIMIT:
|
||||||
|
@ -40,8 +48,14 @@ class IndexView(BaseView):
|
||||||
# If any of these are None it means at least one timed out - ignore them all
|
# If any of these are None it means at least one timed out - ignore them all
|
||||||
if any(v is None for v in table_counts.values()):
|
if any(v is None for v in table_counts.values()):
|
||||||
table_counts = {}
|
table_counts = {}
|
||||||
|
|
||||||
tables = {}
|
tables = {}
|
||||||
for table in table_names:
|
for table in table_names:
|
||||||
|
visible, private = await check_visibility(
|
||||||
|
self.ds, request.actor, "view-table", "table", (name, table),
|
||||||
|
)
|
||||||
|
if not visible:
|
||||||
|
continue
|
||||||
table_columns = await db.table_columns(table)
|
table_columns = await db.table_columns(table)
|
||||||
tables[table] = {
|
tables[table] = {
|
||||||
"name": table,
|
"name": table,
|
||||||
|
@ -51,6 +65,7 @@ class IndexView(BaseView):
|
||||||
"hidden": table in hidden_table_names,
|
"hidden": table in hidden_table_names,
|
||||||
"fts_table": await db.fts_table(table),
|
"fts_table": await db.fts_table(table),
|
||||||
"num_relationships_for_sorting": 0,
|
"num_relationships_for_sorting": 0,
|
||||||
|
"private": private,
|
||||||
}
|
}
|
||||||
|
|
||||||
if request.args.get("_sort") == "relationships" or not table_counts:
|
if request.args.get("_sort") == "relationships" or not table_counts:
|
||||||
|
@ -78,8 +93,8 @@ class IndexView(BaseView):
|
||||||
# Only add views if this is less than TRUNCATE_AT
|
# Only add views if this is less than TRUNCATE_AT
|
||||||
if len(tables_and_views_truncated) < TRUNCATE_AT:
|
if len(tables_and_views_truncated) < TRUNCATE_AT:
|
||||||
num_views_to_add = TRUNCATE_AT - len(tables_and_views_truncated)
|
num_views_to_add = TRUNCATE_AT - len(tables_and_views_truncated)
|
||||||
for view_name in views[:num_views_to_add]:
|
for view in views[:num_views_to_add]:
|
||||||
tables_and_views_truncated.append({"name": view_name})
|
tables_and_views_truncated.append(view)
|
||||||
|
|
||||||
databases.append(
|
databases.append(
|
||||||
{
|
{
|
||||||
|
@ -100,7 +115,7 @@ class IndexView(BaseView):
|
||||||
),
|
),
|
||||||
"hidden_tables_count": len(hidden_tables),
|
"hidden_tables_count": len(hidden_tables),
|
||||||
"views_count": len(views),
|
"views_count": len(views),
|
||||||
"private": private,
|
"private": database_private,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -74,6 +74,37 @@ def test_database_list_respects_view_database():
|
||||||
assert '<a href="/fixtures">fixtures</a> 🔒</h2>' in auth_response.text
|
assert '<a href="/fixtures">fixtures</a> 🔒</h2>' in auth_response.text
|
||||||
|
|
||||||
|
|
||||||
|
def test_database_list_respects_view_table():
|
||||||
|
with make_app_client(
|
||||||
|
metadata={
|
||||||
|
"databases": {
|
||||||
|
"data": {
|
||||||
|
"tables": {
|
||||||
|
"names": {"allow": {"id": "root"}},
|
||||||
|
"v": {"allow": {"id": "root"}},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
extra_databases={
|
||||||
|
"data.db": "create table names (name text); create view v as select * from names"
|
||||||
|
},
|
||||||
|
) as client:
|
||||||
|
html_fragments = [
|
||||||
|
">names</a> 🔒",
|
||||||
|
">v</a> 🔒",
|
||||||
|
]
|
||||||
|
anon_response_text = client.get("/").text
|
||||||
|
assert "0 rows in 0 tables" in anon_response_text
|
||||||
|
for html_fragment in html_fragments:
|
||||||
|
assert html_fragment not in anon_response_text
|
||||||
|
auth_response_text = client.get(
|
||||||
|
"/", cookies={"ds_actor": client.ds.sign({"id": "root"}, "actor")},
|
||||||
|
).text
|
||||||
|
for html_fragment in html_fragments:
|
||||||
|
assert html_fragment in auth_response_text
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"allow,expected_anon,expected_auth",
|
"allow,expected_anon,expected_auth",
|
||||||
[(None, 200, 200), ({}, 403, 403), ({"id": "root"}, 403, 200),],
|
[(None, 200, 200), ({}, 403, 403), ({"id": "root"}, 403, 200),],
|
||||||
|
|
Ładowanie…
Reference in New Issue