diff --git a/datasette/views/database.py b/datasette/views/database.py index 2872bebc..4eb52f84 100644 --- a/datasette/views/database.py +++ b/datasette/views/database.py @@ -613,6 +613,13 @@ class TableCreateView(BaseView): ignore = data.get("ignore") replace = data.get("replace") + if replace: + # Must have update-row permission + if not await self.ds.permission_allowed( + request.actor, "update-row", resource=database_name + ): + return _error(["Permission denied - need update-row"], 403) + table_name = data.get("table") if not table_name: return _error(["Table is required"]) @@ -630,6 +637,13 @@ class TableCreateView(BaseView): if rows and row: return _error(["Cannot specify both rows and row"]) + if rows or row: + # Must have insert-row permission + if not await self.ds.permission_allowed( + request.actor, "insert-row", resource=database_name + ): + return _error(["Permission denied - need insert-row"], 403) + if columns: if rows or row: return _error(["Cannot specify columns with rows or row"]) diff --git a/docs/json_api.rst b/docs/json_api.rst index 526bd30e..caceb3e3 100644 --- a/docs/json_api.rst +++ b/docs/json_api.rst @@ -830,7 +830,8 @@ If the table is successfully created this will return a ``201`` status code and Creating a table from example data ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -Instead of specifying ``columns`` directly you can instead pass a single example ``row`` or a list of ``rows``. Datasette will create a table with a schema that matches those rows and insert them for you: +Instead of specifying ``columns`` directly you can instead pass a single example ``row`` or a list of ``rows``. +Datasette will create a table with a schema that matches those rows and insert them for you: :: @@ -855,6 +856,8 @@ Instead of specifying ``columns`` directly you can instead pass a single example "pk": "id" } +Doing this requires both the :ref:`permissions_create_table` and :ref:`permissions_insert_row` permissions. + The ``201`` response here will be similar to the ``columns`` form, but will also include the number of rows that were inserted as ``row_count``: .. code-block:: json @@ -884,6 +887,8 @@ If you pass a row to the create endpoint with a primary key that already exists You can avoid this error by passing the same ``"ignore": true`` or ``"replace": true`` options to the create endpoint as you can to the :ref:`insert endpoint `. +To use the ``"replace": true`` option you will also need the :ref:`permissions_update_row` permission. + .. _TableDropView: Dropping tables diff --git a/tests/test_api_write.py b/tests/test_api_write.py index ab3d0e0d..f27d143f 100644 --- a/tests/test_api_write.py +++ b/tests/test_api_write.py @@ -1096,6 +1096,50 @@ async def test_create_table(ds_write, input, expected_status, expected_response) assert data == expected_response +@pytest.mark.asyncio +@pytest.mark.parametrize( + "permissions,body,expected_status,expected_errors", + ( + (["create-table"], {"table": "t", "columns": [{"name": "c"}]}, 201, None), + # Need insert-row too if you use "rows": + ( + ["create-table"], + {"table": "t", "rows": [{"name": "c"}]}, + 403, + ["Permission denied - need insert-row"], + ), + # This should work: + ( + ["create-table", "insert-row"], + {"table": "t", "rows": [{"name": "c"}]}, + 201, + None, + ), + # If you use replace: true you need update-row too: + ( + ["create-table", "insert-row"], + {"table": "t", "rows": [{"id": 1}], "pk": "id", "replace": True}, + 403, + ["Permission denied - need update-row"], + ), + ), +) +async def test_create_table_permissions( + ds_write, permissions, body, expected_status, expected_errors +): + token = ds_write.create_token("root", restrict_all=["view-instance"] + permissions) + response = await ds_write.client.post( + "/data/-/create", + json=body, + headers=_headers(token), + ) + assert response.status_code == expected_status + if expected_errors: + data = response.json() + assert data["ok"] is False + assert data["errors"] == expected_errors + + @pytest.mark.asyncio @pytest.mark.parametrize( "input,expected_rows_after",