mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
check_visibility can now take multiple permissions into account
Closes #1829
This commit is contained in:
parent
6887c12ea3
commit
78dad236df
10 changed files with 196 additions and 82 deletions
|
|
@ -1,6 +1,7 @@
|
|||
"""
|
||||
Tests for the datasette.app.Datasette class
|
||||
"""
|
||||
from datasette import Forbidden
|
||||
from datasette.app import Datasette, Database
|
||||
from itsdangerous import BadSignature
|
||||
from .fixtures import app_client
|
||||
|
|
@ -75,3 +76,52 @@ async def test_num_sql_threads_zero():
|
|||
assert response.json() == {"num_threads": 0, "threads": []}
|
||||
response2 = await ds.client.get("/test_num_sql_threads_zero/t.json?_shape=array")
|
||||
assert response2.json() == [{"id": 1}]
|
||||
|
||||
|
||||
ROOT = {"id": "root"}
|
||||
ALLOW_ROOT = {"allow": {"id": "root"}}
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
@pytest.mark.parametrize(
|
||||
"actor,metadata,permissions,should_allow,expected_private",
|
||||
(
|
||||
(None, ALLOW_ROOT, ["view-instance"], False, False),
|
||||
(ROOT, ALLOW_ROOT, ["view-instance"], True, True),
|
||||
(
|
||||
None,
|
||||
{"databases": {"_memory": ALLOW_ROOT}},
|
||||
[("view-database", "_memory")],
|
||||
False,
|
||||
False,
|
||||
),
|
||||
(
|
||||
ROOT,
|
||||
{"databases": {"_memory": ALLOW_ROOT}},
|
||||
[("view-database", "_memory")],
|
||||
True,
|
||||
True,
|
||||
),
|
||||
# Check private is false for non-protected instance check
|
||||
(
|
||||
ROOT,
|
||||
{"allow": True},
|
||||
["view-instance"],
|
||||
True,
|
||||
False,
|
||||
),
|
||||
),
|
||||
)
|
||||
async def test_datasette_ensure_permissions_check_visibility(
|
||||
actor, metadata, permissions, should_allow, expected_private
|
||||
):
|
||||
ds = Datasette([], memory=True, metadata=metadata)
|
||||
if not should_allow:
|
||||
with pytest.raises(Forbidden):
|
||||
await ds.ensure_permissions(actor, permissions)
|
||||
else:
|
||||
await ds.ensure_permissions(actor, permissions)
|
||||
# And try check_visibility too:
|
||||
visible, private = await ds.check_visibility(actor, permissions=permissions)
|
||||
assert visible == should_allow
|
||||
assert private == expected_private
|
||||
|
|
|
|||
|
|
@ -5,6 +5,20 @@ import pytest
|
|||
import urllib
|
||||
|
||||
|
||||
@pytest.fixture(scope="module")
|
||||
def padlock_client():
|
||||
with make_app_client(
|
||||
metadata={
|
||||
"databases": {
|
||||
"fixtures": {
|
||||
"queries": {"two": {"sql": "select 1 + 1"}},
|
||||
}
|
||||
}
|
||||
}
|
||||
) as client:
|
||||
yield client
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"allow,expected_anon,expected_auth",
|
||||
[
|
||||
|
|
@ -13,27 +27,33 @@ import urllib
|
|||
({"id": "root"}, 403, 200),
|
||||
],
|
||||
)
|
||||
def test_view_instance(allow, expected_anon, expected_auth):
|
||||
with make_app_client(metadata={"allow": allow}) as client:
|
||||
for path in (
|
||||
"/",
|
||||
"/fixtures",
|
||||
"/fixtures/compound_three_primary_keys",
|
||||
"/fixtures/compound_three_primary_keys/a,a,a",
|
||||
):
|
||||
anon_response = client.get(path)
|
||||
assert expected_anon == anon_response.status
|
||||
if allow and path == "/" and anon_response.status == 200:
|
||||
# Should be no padlock
|
||||
assert "<h1>Datasette 🔒</h1>" not in anon_response.text
|
||||
auth_response = client.get(
|
||||
path,
|
||||
cookies={"ds_actor": client.actor_cookie({"id": "root"})},
|
||||
)
|
||||
assert expected_auth == auth_response.status
|
||||
# Check for the padlock
|
||||
if allow and path == "/" and expected_anon == 403 and expected_auth == 200:
|
||||
assert "<h1>Datasette 🔒</h1>" in auth_response.text
|
||||
@pytest.mark.parametrize(
|
||||
"path",
|
||||
(
|
||||
"/",
|
||||
"/fixtures",
|
||||
"/fixtures/compound_three_primary_keys",
|
||||
"/fixtures/compound_three_primary_keys/a,a,a",
|
||||
"/fixtures/two", # Query
|
||||
),
|
||||
)
|
||||
def test_view_padlock(allow, expected_anon, expected_auth, path, padlock_client):
|
||||
padlock_client.ds._metadata_local["allow"] = allow
|
||||
fragment = "🔒</h1>"
|
||||
anon_response = padlock_client.get(path)
|
||||
assert expected_anon == anon_response.status
|
||||
if allow and anon_response.status == 200:
|
||||
# Should be no padlock
|
||||
assert fragment not in anon_response.text
|
||||
auth_response = padlock_client.get(
|
||||
path,
|
||||
cookies={"ds_actor": padlock_client.actor_cookie({"id": "root"})},
|
||||
)
|
||||
assert expected_auth == auth_response.status
|
||||
# Check for the padlock
|
||||
if allow and expected_anon == 403 and expected_auth == 200:
|
||||
assert fragment in auth_response.text
|
||||
del padlock_client.ds._metadata_local["allow"]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
|
|
@ -467,6 +487,10 @@ def test_permissions_cascade(cascade_app_client, path, permissions, expected_sta
|
|||
path,
|
||||
cookies={"ds_actor": cascade_app_client.actor_cookie(actor)},
|
||||
)
|
||||
assert expected_status == response.status
|
||||
assert (
|
||||
response.status == expected_status
|
||||
), "path: {}, permissions: {}, expected_status: {}, status: {}".format(
|
||||
path, permissions, expected_status, response.status
|
||||
)
|
||||
finally:
|
||||
cascade_app_client.ds._metadata_local = previous_metadata
|
||||
cascade_app_client.ds._local_metadata = previous_metadata
|
||||
|
|
|
|||
|
|
@ -823,8 +823,14 @@ def test_hook_forbidden(restore_working_directory):
|
|||
assert 403 == response.status
|
||||
response2 = client.get("/data2")
|
||||
assert 302 == response2.status
|
||||
assert "/login?message=view-database" == response2.headers["Location"]
|
||||
assert "view-database" == client.ds._last_forbidden_message
|
||||
assert (
|
||||
response2.headers["Location"]
|
||||
== "/login?message=You do not have permission to view this database"
|
||||
)
|
||||
assert (
|
||||
client.ds._last_forbidden_message
|
||||
== "You do not have permission to view this database"
|
||||
)
|
||||
|
||||
|
||||
def test_hook_handle_exception(app_client):
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue