mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Added permission check to every view, closes #808
This commit is contained in:
parent
bd4de0647d
commit
86dec9e8ff
13 changed files with 220 additions and 2 deletions
|
|
@ -1,5 +1,15 @@
|
|||
import os
|
||||
import pathlib
|
||||
import pytest
|
||||
import re
|
||||
|
||||
UNDOCUMENTED_PERMISSIONS = {
|
||||
"this_is_allowed",
|
||||
"this_is_denied",
|
||||
"this_is_allowed_async",
|
||||
"this_is_denied_async",
|
||||
"no_match",
|
||||
}
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
|
|
@ -39,3 +49,31 @@ def restore_working_directory(tmpdir, request):
|
|||
os.chdir(previous_cwd)
|
||||
|
||||
request.addfinalizer(return_to_previous)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session", autouse=True)
|
||||
def check_permission_actions_are_documented():
|
||||
from datasette.plugins import pm
|
||||
|
||||
content = (
|
||||
(pathlib.Path(__file__).parent.parent / "docs" / "authentication.rst")
|
||||
.open()
|
||||
.read()
|
||||
)
|
||||
permissions_re = re.compile(r"\.\. _permissions_([^\s:]+):")
|
||||
documented_permission_actions = set(permissions_re.findall(content)).union(
|
||||
UNDOCUMENTED_PERMISSIONS
|
||||
)
|
||||
|
||||
def before(hook_name, hook_impls, kwargs):
|
||||
if hook_name == "permission_allowed":
|
||||
action = kwargs.get("action").replace("-", "_")
|
||||
assert (
|
||||
action in documented_permission_actions
|
||||
), "Undocumented permission action: {}, resource_type: {}, resource_identifier: {}".format(
|
||||
action, kwargs["resource_type"], kwargs["resource_identifier"]
|
||||
)
|
||||
|
||||
pm.add_hookcall_monitoring(
|
||||
before=before, after=lambda outcome, hook_name, hook_impls, kwargs: None
|
||||
)
|
||||
|
|
|
|||
|
|
@ -840,3 +840,19 @@ if __name__ == "__main__":
|
|||
sys.argv[0]
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
def assert_permission_checked(
|
||||
datasette, action, resource_type=None, resource_identifier=None
|
||||
):
|
||||
assert [
|
||||
pc
|
||||
for pc in datasette._permission_checks
|
||||
if pc["action"] == action
|
||||
and pc["resource_type"] == resource_type
|
||||
and pc["resource_identifier"] == resource_identifier
|
||||
], """Missing expected permission check: action={}, resource_type={}, resource_identifier={}
|
||||
Permission checks seen: {}
|
||||
""".format(
|
||||
action, resource_type, resource_identifier, datasette._permission_checks
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1721,7 +1721,7 @@ def test_trace(app_client):
|
|||
assert isinstance(trace["traceback"], list)
|
||||
assert isinstance(trace["database"], str)
|
||||
assert isinstance(trace["sql"], str)
|
||||
assert isinstance(trace["params"], (list, dict))
|
||||
assert isinstance(trace["params"], (list, dict, None.__class__))
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ def test_actor_cookie(app_client):
|
|||
|
||||
|
||||
def test_permissions_debug(app_client):
|
||||
app_client.ds._permission_checks.clear()
|
||||
assert 403 == app_client.get("/-/permissions").status
|
||||
# With the cookie it should work
|
||||
cookie = app_client.ds.sign({"id": "root"}, "actor")
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ from .fixtures import ( # noqa
|
|||
app_client_shorter_time_limit,
|
||||
app_client_two_attached_databases,
|
||||
app_client_with_hash,
|
||||
assert_permission_checked,
|
||||
make_app_client,
|
||||
METADATA,
|
||||
)
|
||||
|
|
@ -17,6 +18,7 @@ import urllib.parse
|
|||
|
||||
def test_homepage(app_client_two_attached_databases):
|
||||
response = app_client_two_attached_databases.get("/")
|
||||
assert_permission_checked(app_client_two_attached_databases.ds, "view-index")
|
||||
assert response.status == 200
|
||||
assert "text/html; charset=utf-8" == response.headers["content-type"]
|
||||
soup = Soup(response.body, "html.parser")
|
||||
|
|
@ -75,6 +77,12 @@ def test_static_mounts():
|
|||
def test_memory_database_page():
|
||||
for client in make_app_client(memory=True):
|
||||
response = client.get("/:memory:")
|
||||
assert_permission_checked(
|
||||
client.ds,
|
||||
"view-database",
|
||||
resource_type="database",
|
||||
resource_identifier=":memory:",
|
||||
)
|
||||
assert response.status == 200
|
||||
|
||||
|
||||
|
|
@ -87,6 +95,12 @@ def test_database_page_redirects_with_url_hash(app_client_with_hash):
|
|||
|
||||
def test_database_page(app_client):
|
||||
response = app_client.get("/fixtures")
|
||||
assert_permission_checked(
|
||||
app_client.ds,
|
||||
"view-database",
|
||||
resource_type="database",
|
||||
resource_identifier="fixtures",
|
||||
)
|
||||
soup = Soup(response.body, "html.parser")
|
||||
queries_ul = soup.find("h2", text="Queries").find_next_sibling("ul")
|
||||
assert queries_ul is not None
|
||||
|
|
@ -197,6 +211,12 @@ def test_row_page_does_not_truncate():
|
|||
for client in make_app_client(config={"truncate_cells_html": 5}):
|
||||
response = client.get("/fixtures/facetable/1")
|
||||
assert response.status == 200
|
||||
assert_permission_checked(
|
||||
client.ds,
|
||||
"view-row",
|
||||
resource_type="row",
|
||||
resource_identifier=("fixtures", "facetable", "1"),
|
||||
)
|
||||
table = Soup(response.body, "html.parser").find("table")
|
||||
assert table["class"] == ["rows-and-columns"]
|
||||
assert ["Mission"] == [
|
||||
|
|
@ -506,6 +526,12 @@ def test_templates_considered(app_client, path, expected_considered):
|
|||
|
||||
def test_table_html_simple_primary_key(app_client):
|
||||
response = app_client.get("/fixtures/simple_primary_key?_size=3")
|
||||
assert_permission_checked(
|
||||
app_client.ds,
|
||||
"view-table",
|
||||
resource_type="table",
|
||||
resource_identifier=("fixtures", "simple_primary_key"),
|
||||
)
|
||||
assert response.status == 200
|
||||
table = Soup(response.body, "html.parser").find("table")
|
||||
assert table["class"] == ["rows-and-columns"]
|
||||
|
|
@ -896,6 +922,12 @@ def test_database_download_allowed_for_immutable():
|
|||
assert len(soup.findAll("a", {"href": re.compile(r"\.db$")}))
|
||||
# Check we can actually download it
|
||||
assert 200 == client.get("/fixtures.db").status
|
||||
assert_permission_checked(
|
||||
client.ds,
|
||||
"view-database-download",
|
||||
resource_type="database",
|
||||
resource_identifier="fixtures",
|
||||
)
|
||||
|
||||
|
||||
def test_database_download_disallowed_for_mutable(app_client):
|
||||
|
|
@ -991,6 +1023,12 @@ def test_404_content_type(app_client):
|
|||
|
||||
def test_canned_query_with_custom_metadata(app_client):
|
||||
response = app_client.get("/fixtures/neighborhood_search?text=town")
|
||||
assert_permission_checked(
|
||||
app_client.ds,
|
||||
"view-query",
|
||||
resource_type="query",
|
||||
resource_identifier=("fixtures", "neighborhood_search"),
|
||||
)
|
||||
assert response.status == 200
|
||||
soup = Soup(response.body, "html.parser")
|
||||
assert "Search neighborhoods" == soup.find("h1").text
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue