diff --git a/datasette/app.py b/datasette/app.py index ea9bb6d2..c052be58 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -116,6 +116,11 @@ SETTINGS = ( True, "Allow users to specify columns to facet using ?_facet= parameter", ), + Setting( + "default_allow_sql", + True, + "Allow anyone to run arbitrary SQL queries", + ), Setting( "allow_download", True, diff --git a/datasette/default_permissions.py b/datasette/default_permissions.py index b58d8d1b..a0681e83 100644 --- a/datasette/default_permissions.py +++ b/datasette/default_permissions.py @@ -36,12 +36,16 @@ def permission_allowed(datasette, actor, action, resource): return None return actor_matches_allow(actor, allow) elif action == "execute-sql": + # Only use default_allow_sql setting if it is set to False: + default_allow_sql = ( + None if datasette.setting("default_allow_sql") else False + ) # Use allow_sql block from database block, or from top-level database_allow_sql = datasette.metadata("allow_sql", database=resource) if database_allow_sql is None: database_allow_sql = datasette.metadata("allow_sql") if database_allow_sql is None: - return None + return default_allow_sql return actor_matches_allow(actor, database_allow_sql) return inner diff --git a/docs/authentication.rst b/docs/authentication.rst index 685dab15..87852555 100644 --- a/docs/authentication.rst +++ b/docs/authentication.rst @@ -307,7 +307,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 an ``"allow_sql"`` block 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: @@ -515,7 +521,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 a6885fc8..ed20ea8a 100644 --- a/docs/cli-reference.rst +++ b/docs/cli-reference.rst @@ -224,6 +224,8 @@ These can be passed to ``datasette serve`` using ``datasette serve --setting nam (default=50) allow_facet Allow users to specify columns to facet using ?_facet= parameter (default=True) + default_allow_sql Allow anyone to run arbitrary SQL queries + (default=True) allow_download Allow users to download the original SQLite database files (default=True) suggest_facets Calculate and display suggested facets diff --git a/docs/settings.rst b/docs/settings.rst index a6d50543..8a83cc2f 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/tests/test_api.py b/tests/test_api.py index 4027a7a5..db624823 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -805,6 +805,7 @@ def test_settings_json(app_client): assert { "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 f0d28037..9ca50cbe 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -215,6 +215,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)