From 98632f0a874b7b9dac6abf0abb9fdb7e2839a4d3 Mon Sep 17 00:00:00 2001 From: Simon Willison Date: Thu, 11 Jun 2020 09:02:03 -0700 Subject: [PATCH] --secret command for datasette publish Closes #787 --- datasette/cli.py | 28 +++++++++++++++--------- datasette/publish/cloudrun.py | 2 ++ datasette/publish/common.py | 7 ++++++ datasette/publish/heroku.py | 3 +++ datasette/utils/__init__.py | 7 +++++- docs/datasette-package-help.txt | 3 +++ docs/datasette-publish-cloudrun-help.txt | 3 +++ docs/datasette-publish-heroku-help.txt | 3 +++ docs/plugins.rst | 1 + tests/test_package.py | 8 ++++--- tests/test_publish_cloudrun.py | 3 +++ tests/test_utils.py | 4 ++++ 12 files changed, 58 insertions(+), 14 deletions(-) diff --git a/datasette/cli.py b/datasette/cli.py index 2e3c8e36..ff9a2d5c 100644 --- a/datasette/cli.py +++ b/datasette/cli.py @@ -165,6 +165,12 @@ def plugins(all, plugins_dir): ) @click.option("--spatialite", is_flag=True, help="Enable SpatialLite extension") @click.option("--version-note", help="Additional note to show on /-/versions") +@click.option( + "--secret", + help="Secret used for signing secure values, such as signed cookies", + envvar="DATASETTE_PUBLISH_SECRET", + default=lambda: os.urandom(32).hex(), +) @click.option( "-p", "--port", default=8001, help="Port to run the server on, defaults to 8001", ) @@ -187,6 +193,7 @@ def package( install, spatialite, version_note, + secret, port, **extra_metadata ): @@ -203,16 +210,17 @@ def package( with temporary_docker_directory( files, "datasette", - metadata, - extra_options, - branch, - template_dir, - plugins_dir, - static, - install, - spatialite, - version_note, - extra_metadata, + metadata=metadata, + extra_options=extra_options, + branch=branch, + template_dir=template_dir, + plugins_dir=plugins_dir, + static=static, + install=install, + spatialite=spatialite, + version_note=version_note, + secret=secret, + extra_metadata=extra_metadata, port=port, ): args = ["docker", "build"] diff --git a/datasette/publish/cloudrun.py b/datasette/publish/cloudrun.py index 8271209a..8f99dc2e 100644 --- a/datasette/publish/cloudrun.py +++ b/datasette/publish/cloudrun.py @@ -47,6 +47,7 @@ def publish_subcommand(publish): install, plugin_secret, version_note, + secret, title, license, license_url, @@ -120,6 +121,7 @@ def publish_subcommand(publish): install, spatialite, version_note, + secret, extra_metadata, environment_variables, ): diff --git a/datasette/publish/common.py b/datasette/publish/common.py index 2911029d..49a4798e 100644 --- a/datasette/publish/common.py +++ b/datasette/publish/common.py @@ -1,5 +1,6 @@ from ..utils import StaticMount import click +import os import shutil import sys @@ -52,6 +53,12 @@ def add_common_publish_arguments_and_options(subcommand): click.option( "--version-note", help="Additional note to show on /-/versions" ), + click.option( + "--secret", + help="Secret used for signing secure values, such as signed cookies", + envvar="DATASETTE_PUBLISH_SECRET", + default=lambda: os.urandom(32).hex(), + ), click.option("--title", help="Title for metadata"), click.option("--license", help="License label for metadata"), click.option("--license_url", help="License URL for metadata"), diff --git a/datasette/publish/heroku.py b/datasette/publish/heroku.py index 7adf9d92..6cda68da 100644 --- a/datasette/publish/heroku.py +++ b/datasette/publish/heroku.py @@ -35,6 +35,7 @@ def publish_subcommand(publish): install, plugin_secret, version_note, + secret, title, license, license_url, @@ -100,6 +101,7 @@ def publish_subcommand(publish): static, install, version_note, + secret, extra_metadata, ): app_name = None @@ -144,6 +146,7 @@ def temporary_heroku_directory( static, install, version_note, + secret, extra_metadata=None, ): extra_metadata = extra_metadata or {} diff --git a/datasette/utils/__init__.py b/datasette/utils/__init__.py index 51373c46..5090f67e 100644 --- a/datasette/utils/__init__.py +++ b/datasette/utils/__init__.py @@ -278,10 +278,13 @@ def make_dockerfile( install, spatialite, version_note, + secret, environment_variables=None, port=8001, ): cmd = ["datasette", "serve", "--host", "0.0.0.0"] + environment_variables = environment_variables or {} + environment_variables["DATASETTE_SECRET"] = secret for filename in files: cmd.extend(["-i", filename]) cmd.extend(["--cors", "--inspect-file", "inspect-data.json"]) @@ -324,7 +327,7 @@ CMD {cmd}""".format( environment_variables="\n".join( [ "ENV {} '{}'".format(key, value) - for key, value in (environment_variables or {}).items() + for key, value in environment_variables.items() ] ), files=" ".join(files), @@ -348,6 +351,7 @@ def temporary_docker_directory( install, spatialite, version_note, + secret, extra_metadata=None, environment_variables=None, port=8001, @@ -381,6 +385,7 @@ def temporary_docker_directory( install, spatialite, version_note, + secret, environment_variables, port=port, ) diff --git a/docs/datasette-package-help.txt b/docs/datasette-package-help.txt index 326b66cb..1b14f908 100644 --- a/docs/datasette-package-help.txt +++ b/docs/datasette-package-help.txt @@ -17,6 +17,9 @@ Options: --install TEXT Additional packages (e.g. plugins) to install --spatialite Enable SpatialLite extension --version-note TEXT Additional note to show on /-/versions + --secret TEXT Secret used for signing secure values, such as signed + cookies + -p, --port INTEGER Port to run the server on, defaults to 8001 --title TEXT Title for metadata --license TEXT License label for metadata diff --git a/docs/datasette-publish-cloudrun-help.txt b/docs/datasette-publish-cloudrun-help.txt index 98fc9c71..a625bd10 100644 --- a/docs/datasette-publish-cloudrun-help.txt +++ b/docs/datasette-publish-cloudrun-help.txt @@ -15,6 +15,9 @@ Options: datasette-auth-github client_id xxx --version-note TEXT Additional note to show on /-/versions + --secret TEXT Secret used for signing secure values, such as signed + cookies + --title TEXT Title for metadata --license TEXT License label for metadata --license_url TEXT License URL for metadata diff --git a/docs/datasette-publish-heroku-help.txt b/docs/datasette-publish-heroku-help.txt index ec157753..b2caa2cc 100644 --- a/docs/datasette-publish-heroku-help.txt +++ b/docs/datasette-publish-heroku-help.txt @@ -15,6 +15,9 @@ Options: datasette-auth-github client_id xxx --version-note TEXT Additional note to show on /-/versions + --secret TEXT Secret used for signing secure values, such as signed + cookies + --title TEXT Title for metadata --license TEXT License label for metadata --license_url TEXT License URL for metadata diff --git a/docs/plugins.rst b/docs/plugins.rst index a28092a3..989cf672 100644 --- a/docs/plugins.rst +++ b/docs/plugins.rst @@ -536,6 +536,7 @@ Let's say you want to build a plugin that adds a ``datasette publish my_hosting_ install, plugin_secret, version_note, + secret, title, license, license_url, diff --git a/tests/test_package.py b/tests/test_package.py index f0cbe88f..3248b3a4 100644 --- a/tests/test_package.py +++ b/tests/test_package.py @@ -15,7 +15,7 @@ FROM python:3.8 COPY . /app WORKDIR /app - +ENV DATASETTE_SECRET 'sekrit' RUN pip install -U datasette RUN datasette inspect test.db --inspect-file inspect-data.json ENV PORT {port} @@ -33,7 +33,7 @@ def test_package(mock_call, mock_which): mock_call.side_effect = capture with runner.isolated_filesystem(): open("test.db", "w").write("data") - result = runner.invoke(cli.cli, ["package", "test.db"]) + result = runner.invoke(cli.cli, ["package", "test.db", "--secret", "sekrit"]) assert 0 == result.exit_code mock_call.assert_has_calls([mock.call(["docker", "build", "."])]) assert EXPECTED_DOCKERFILE.format(port=8001) == capture.captured @@ -48,6 +48,8 @@ def test_package_with_port(mock_call, mock_which): runner = CliRunner() with runner.isolated_filesystem(): open("test.db", "w").write("data") - result = runner.invoke(cli.cli, ["package", "test.db", "-p", "8080"]) + result = runner.invoke( + cli.cli, ["package", "test.db", "-p", "8080", "--secret", "sekrit"] + ) assert 0 == result.exit_code assert EXPECTED_DOCKERFILE.format(port=8080) == capture.captured diff --git a/tests/test_publish_cloudrun.py b/tests/test_publish_cloudrun.py index 55c207c7..c3ed1f90 100644 --- a/tests/test_publish_cloudrun.py +++ b/tests/test_publish_cloudrun.py @@ -172,6 +172,8 @@ def test_publish_cloudrun_plugin_secrets(mock_call, mock_output, mock_which): "client_id", "x-client-id", "--show-files", + "--secret", + "x-secret", ], ) dockerfile = ( @@ -184,6 +186,7 @@ COPY . /app WORKDIR /app ENV DATASETTE_AUTH_GITHUB_CLIENT_ID 'x-client-id' +ENV DATASETTE_SECRET 'x-secret' RUN pip install -U datasette RUN datasette inspect test.db --inspect-file inspect-data.json ENV PORT 8001 diff --git a/tests/test_utils.py b/tests/test_utils.py index b490953f..d613e999 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -247,6 +247,7 @@ def test_temporary_docker_directory_uses_hard_link(): install=[], spatialite=False, version_note=None, + secret="secret", ) as temp_docker: hello = os.path.join(temp_docker, "hello") assert "world" == open(hello).read() @@ -274,6 +275,7 @@ def test_temporary_docker_directory_uses_copy_if_hard_link_fails(mock_link): install=[], spatialite=False, version_note=None, + secret=None, ) as temp_docker: hello = os.path.join(temp_docker, "hello") assert "world" == open(hello).read() @@ -297,11 +299,13 @@ def test_temporary_docker_directory_quotes_args(): install=[], spatialite=False, version_note="$PWD", + secret="secret", ) as temp_docker: df = os.path.join(temp_docker, "Dockerfile") df_contents = open(df).read() assert "'$PWD'" in df_contents assert "'--$HOME'" in df_contents + assert "ENV DATASETTE_SECRET 'secret'" in df_contents def test_compound_keys_after_sql():