diff --git a/datasette/views/special.py b/datasette/views/special.py index 73b40b17..72e80316 100644 --- a/datasette/views/special.py +++ b/datasette/views/special.py @@ -797,6 +797,8 @@ class CreateTokenView(BaseView): raise Forbidden( "Token authentication cannot be used to create additional tokens" ) + if "_r" in request.actor: + raise Forbidden("Restricted actors cannot create API tokens") async def shared(self, request): self.check_permission(request) diff --git a/tests/test_auth.py b/tests/test_auth.py index 6024e3bb..68a2e6fd 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -1,4 +1,5 @@ import time +from unittest.mock import AsyncMock import pytest from bs4 import BeautifulSoup as Soup @@ -237,6 +238,35 @@ def test_auth_create_token( assert response3.json["actor"]["id"] == "test" +@pytest.mark.asyncio +@pytest.mark.parametrize("method", ["GET", "POST"]) +@pytest.mark.parametrize( + "restrictions", + [ + {}, + {"a": ["vi"]}, + {"d": {"db": ["vd"]}}, + {"r": {"db": {"t1": ["vt"]}}}, + ], + ids=["empty", "instance", "database", "table"], +) +async def test_auth_create_token_not_allowed_for_restricted_actors( + bare_ds, monkeypatch, method, restrictions +): + create_token = AsyncMock() + monkeypatch.setattr(bare_ds, "create_token", create_token) + + response = await bare_ds.client.request( + method, + "/-/create-token", + actor={"id": "test", "_r": restrictions}, + ) + + assert response.status_code == 403 + assert "Restricted actors cannot create API tokens" in response.text + create_token.assert_not_called() + + @pytest.mark.asyncio async def test_auth_create_token_not_allowed_for_tokens(ds_client): ds_tok = ds_client.ds.sign(