From c41278b46f7936b4b1a8a14bf285bed82c81c609 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Wed, 4 Jan 2023 16:51:11 -0800 Subject: [PATCH] default_allow_sql setting, closes #1409 Refs #1410 --- datasette/app.py | 5 +++++ datasette/default_permissions.py | 8 +++++++- docs/authentication.rst | 10 ++++++++-- docs/cli-reference.rst | 2 ++ docs/settings.rst | 15 +++++++++++++++ docs/sql_queries.rst | 2 +- tests/test_api.py | 1 + tests/test_cli.py | 22 ++++++++++++++++++++++ 8 files changed, 61 insertions(+), 4 deletions(-) diff --git a/datasette/app.py b/datasette/app.py index 2e90571a..c13b2d54 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -141,6 +141,11 @@ SETTINGS = ( True, "Allow users to create and use signed API tokens", ), + Setting( + "default_allow_sql", + True, + "Allow anyone to run arbitrary SQL queries", + ), Setting( "max_signed_tokens_ttl", 0, diff --git a/datasette/default_permissions.py b/datasette/default_permissions.py index f9f52e1a..63a66c3c 100644 --- a/datasette/default_permissions.py +++ b/datasette/default_permissions.py @@ -69,9 +69,15 @@ def permission_allowed_default(datasette, actor, action, resource): return result # Check custom permissions: blocks - return await _resolve_metadata_permissions_blocks( + result = await _resolve_metadata_permissions_blocks( datasette, actor, action, resource ) + if result is not None: + return result + + # --setting default_allow_sql + if action == "execute-sql" and not datasette.setting("default_allow_sql"): + return False return inner diff --git a/docs/authentication.rst b/docs/authentication.rst index f5ee1a83..e0104401 100644 --- a/docs/authentication.rst +++ b/docs/authentication.rst @@ -313,7 +313,13 @@ To limit access to the ``add_name`` canned query in your ``dogs.db`` database to Controlling the ability to execute arbitrary SQL ------------------------------------------------ -The ``"allow_sql"`` block can be used to control who is allowed to execute arbitrary SQL queries, both using the form on the database page e.g. https://latest.datasette.io/fixtures or by appending a ``?_where=`` parameter to the table page as seen on https://latest.datasette.io/fixtures/facetable?_where=city_id=1. +Datasette defaults to allowing any site visitor to execute their own custom SQL queries, for example using the form on `the database page `__ or by appending a ``?_where=`` parameter to the table page `like this `__. + +Access to this ability is controlled by the :ref:`permissions_execute_sql` permission. + +The easiest way to disable arbitrary SQL queries is using the :ref:`default_allow_sql setting ` when you first start Datasette running. + +You can alternatively use the ``"allow_sql"`` to control who is allowed to execute arbitrary SQL queries. To enable just the :ref:`root user` to execute SQL for all databases in your instance, use the following: @@ -770,7 +776,7 @@ Actor is allowed to run arbitrary SQL queries against a specific database, e.g. ``resource`` - string The name of the database -Default *allow*. +Default *allow*. See also :ref:`the default_allow_sql setting `. .. _permissions_permissions_debug: diff --git a/docs/cli-reference.rst b/docs/cli-reference.rst index 5cac71ce..ff0202f8 100644 --- a/docs/cli-reference.rst +++ b/docs/cli-reference.rst @@ -234,6 +234,8 @@ These can be passed to ``datasette serve`` using ``datasette serve --setting nam database files (default=True) allow_signed_tokens Allow users to create and use signed API tokens (default=True) + default_allow_sql Allow anyone to run arbitrary SQL queries + (default=True) max_signed_tokens_ttl Maximum allowed expiry time for signed API tokens (default=0) suggest_facets Calculate and display suggested facets diff --git a/docs/settings.rst b/docs/settings.rst index b86b18bd..38227a6d 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -59,6 +59,21 @@ Settings The following options can be set using ``--setting name value``, or by storing them in the ``settings.json`` file for use with :ref:`config_dir`. +.. _setting_default_allow_sql: + +default_allow_sql +~~~~~~~~~~~~~~~~~ + +Should users be able to execute arbitrary SQL queries by default? + +Setting this to ``off`` causes permission checks for :ref:`permissions_execute_sql` to fail by default. + +:: + + datasette mydatabase.db --setting default_allow_sql off + +There are two ways to achieve this: the other is to add ``"allow_sql": false`` to your ``metadata.json`` file, as described in :ref:`authentication_permissions_execute_sql`. This setting offers a more convenient way to do this. + .. _setting_default_page_size: default_page_size diff --git a/docs/sql_queries.rst b/docs/sql_queries.rst index 010e3205..5f53a6d7 100644 --- a/docs/sql_queries.rst +++ b/docs/sql_queries.rst @@ -7,7 +7,7 @@ Datasette treats SQLite database files as read-only and immutable. This means it The easiest way to execute custom SQL against Datasette is through the web UI. The database index page includes a SQL editor that lets you run any SELECT query you like. You can also construct queries using the filter interface on the tables page, then click "View and edit SQL" to open that query in the custom SQL editor. -Note that this interface is only available if the :ref:`permissions_execute_sql` permission is allowed. +Note that this interface is only available if the :ref:`permissions_execute_sql` permission is allowed. See :ref:`authentication_permissions_execute_sql`. Any Datasette SQL query is reflected in the URL of the page, allowing you to bookmark them, share them with others and navigate through previous queries using your browser back button. diff --git a/tests/test_api.py b/tests/test_api.py index b2cf7ef0..5a751487 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -821,6 +821,7 @@ async def test_settings_json(ds_client): assert response.json() == { "default_page_size": 50, "default_facet_size": 30, + "default_allow_sql": True, "facet_suggest_time_limit_ms": 50, "facet_time_limit_ms": 200, "max_returned_rows": 100, diff --git a/tests/test_cli.py b/tests/test_cli.py index d3e015fa..d06996e7 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -216,6 +216,28 @@ def test_setting_type_validation(): assert '"default_page_size" should be an integer' in result.stderr +@pytest.mark.parametrize("default_allow_sql", (True, False)) +def test_setting_default_allow_sql(default_allow_sql): + runner = CliRunner() + result = runner.invoke( + cli, + [ + "--setting", + "default_allow_sql", + "on" if default_allow_sql else "off", + "--get", + "/_memory.json?sql=select+21&_shape=objects", + ], + ) + if default_allow_sql: + assert result.exit_code == 0, result.output + assert json.loads(result.output)["rows"][0] == {"21": 21} + else: + assert result.exit_code == 1, result.output + # This isn't JSON at the moment, maybe it should be though + assert "Forbidden" in result.output + + def test_config_deprecated(): # The --config option should show a deprecation message runner = CliRunner(mix_stderr=False)