diff --git a/datasette_enrichments/__init__.py b/datasette_enrichments/__init__.py index cb0bbad..5c21862 100644 --- a/datasette_enrichments/__init__.py +++ b/datasette_enrichments/__init__.py @@ -2,8 +2,10 @@ from datasette import hookimpl from datasette.database import Database import json +import secrets from datasette.plugins import pm from .views import enrichment_picker, enrichment_view +from .utils import get_with_auth from . import hookspecs from datasette.utils import await_me_maybe @@ -76,7 +78,8 @@ async def enqueue( qs += "&" qs += "_size=0&_extra=count" table_path = datasette.urls.table(db.name, table) - response = await datasette.client.get(table_path + ".json" + "?" + qs) + + response = await get_with_auth(datasette, table_path + ".json" + "?" + qs) row_count = response.json()["count"] await db.execute_write(CREATE_JOB_TABLE_SQL) @@ -130,7 +133,7 @@ async def run_enrichment(): if next_cursor: qs += "&_next={}".format(next_cursor) qs += "&_size={}".format(self.batch_size) - response = await datasette.client.get(table_path + "?" + qs) + response = await get_with_auth(datasette, table_path + "?" + qs) rows = response.json()["rows"] if not rows: break @@ -207,6 +210,19 @@ async def inner(): @hookimpl def permission_allowed(actor, action): + # Special actor used for internal datasette.client.get() calls + if actor == {"_datasette_enrichments": True}: + return True # Root user can always use enrichments if action == "enrichments" and actor and actor.get("id") == "root": return True + + +@hookimpl(tryfirst=True) +def actor_from_request(datasette, request): + secret_token = request.headers.get("x-datasette-enrichments") or "" + expected_token = getattr(datasette, "_secret_enrichments_token", None) + if expected_token and secrets.compare_digest( + secret_token, datasette._secret_enrichments_token + ): + return {"_datasette_enrichments": True} diff --git a/datasette_enrichments/utils.py b/datasette_enrichments/utils.py new file mode 100644 index 0000000..278524d --- /dev/null +++ b/datasette_enrichments/utils.py @@ -0,0 +1,10 @@ +import secrets + + +async def get_with_auth(datasette, *args, **kwargs): + if not hasattr(datasette, "_secret_enrichments_token"): + datasette._secret_enrichments_token = secrets.token_hex(16) + headers = kwargs.pop("headers", None) or {} + headers["x-datasette-enrichments"] = datasette._secret_enrichments_token + kwargs["headers"] = headers + return await datasette.client.get(*args, **kwargs) diff --git a/datasette_enrichments/views.py b/datasette_enrichments/views.py index 050f7fd..7c88e9f 100644 --- a/datasette_enrichments/views.py +++ b/datasette_enrichments/views.py @@ -1,5 +1,6 @@ from datasette import Response, NotFound, Forbidden from datasette.utils import path_with_added_args, MultiParams +from .utils import get_with_auth import urllib.parse @@ -36,8 +37,9 @@ async def enrichment_view(datasette, request): # re-encode query_string = urllib.parse.urlencode(bits) stuff = ( - await datasette.client.get( - datasette.urls.table(database, table, "json") + "?" + query_string + await get_with_auth( + datasette, + datasette.urls.table(database, table, "json") + "?" + query_string, ) ).json() @@ -85,8 +87,9 @@ async def enrichment_picker(datasette, request): # re-encode query_string = urllib.parse.urlencode(bits) stuff = ( - await datasette.client.get( - datasette.urls.table(database, table, "json") + "?" + query_string + await get_with_auth( + datasette, + datasette.urls.table(database, table, "json") + "?" + query_string, ) ).json() diff --git a/tests/test_enrichments.py b/tests/test_enrichments.py index 182aba6..3211e3b 100644 --- a/tests/test_enrichments.py +++ b/tests/test_enrichments.py @@ -12,7 +12,16 @@ async def test_uppercase_plugin(tmpdir, is_root): db.execute("create table t (id integer primary key, s text)") db.execute("insert into t (s) values ('hello')") db.execute("insert into t (s) values ('goodbye')") - datasette = Datasette([data]) + datasette = Datasette( + [data], + metadata={ + "databases": { + # Lock down permissions to test + # https://github.com/datasette/datasette-enrichments/issues/13 + "data": {"allow": {"id": "root"}} + } + }, + ) if not is_root: response1 = await datasette.client.get("/-/enrich/data/t")