home / github

Menu
  • Search all tables
  • GraphQL API

issue_comments

Table actions
  • GraphQL API for issue_comments

9,269 rows sorted by updated_at descending

✎ View and edit SQL

This data as json, CSV (advanced)

Suggested facets: created_at (date), updated_at (date)

user >30

  • simonw 7,950
  • codecov[bot] 180
  • fgregg 70
  • eyeseast 64
  • russss 39
  • psychemedia 34
  • abdusco 26
  • bgrins 24
  • mroswell 22
  • aborruso 19
  • chrismp 18
  • chapmanjacobd 17
  • jacobian 14
  • carlmjohnson 14
  • brandonrobertz 14
  • RhetTbull 14
  • tballison 13
  • wragge 12
  • tsibley 11
  • rixx 11
  • stonebig 11
  • dependabot[bot] 11
  • frafra 10
  • terrycojones 10
  • rayvoelker 10
  • maxhawkins 9
  • clausjuhl 9
  • bobwhitelock 9
  • 20after4 8
  • dracos 8
  • …

issue >30

  • Show column metadata plus links for foreign keys on arbitrary query results 51
  • Redesign default .json format 48
  • Rethink how .ext formats (v.s. ?_format=) works before 1.0 48
  • Upgrade to CodeMirror 6, add SQL autocomplete 48
  • JavaScript plugin hooks mechanism similar to pluggy 47
  • Updated Dockerfile with SpatiaLite version 5.0 45
  • Complete refactor of TableView and table.html template 45
  • Port Datasette to ASGI 42
  • Authentication (and permissions) as a core concept 40
  • Deploy a live instance of demos/apache-proxy 34
  • await datasette.client.get(path) mechanism for executing internal requests 33
  • Maintain an in-memory SQLite table of connected databases and their tables 32
  • Research: demonstrate if parallel SQL queries are worthwhile 32
  • Ability to sort (and paginate) by column 31
  • Default API token authentication mechanism 30
  • link_or_copy_directory() error - Invalid cross-device link 28
  • Export to CSV 27
  • base_url configuration setting 27
  • Documentation with recommendations on running Datasette in production without using Docker 27
  • Optimize all those calls to index_list and foreign_key_list 27
  • Support cross-database joins 26
  • Ability for a canned query to write to the database 26
  • table.transform() method for advanced alter table 26
  • New pattern for views that return either JSON or HTML, available for plugins 26
  • Proof of concept for Datasette on AWS Lambda with EFS 25
  • WIP: Add Gmail takeout mbox import 25
  • Redesign register_output_renderer callback 24
  • Make it easier to insert geometries, with documentation and maybe code 24
  • API explorer tool 24
  • Stream all results for arbitrary SQL and canned queries 23
  • …

reactions 17 ✖

  • {"total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} 8,963
  • {"total_count": 1, "+1": 1, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} 184
  • {"total_count": 1, "+1": 0, "-1": 0, "laugh": 0, "hooray": 1, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} 34
  • {"total_count": 1, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 1, "rocket": 0, "eyes": 0} 33
  • {"total_count": 2, "+1": 2, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} 13
  • {"total_count": 1, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 1, "eyes": 0} 12
  • {"total_count": 1, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 1} 7
  • {"total_count": 1, "+1": 0, "-1": 0, "laugh": 1, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} 6
  • {"total_count": 3, "+1": 3, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} 4
  • {"total_count": 2, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 2, "rocket": 0, "eyes": 0} 3
  • {"total_count": 2, "+1": 1, "-1": 0, "laugh": 0, "hooray": 1, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} 3
  • {"total_count": 2, "+1": 0, "-1": 0, "laugh": 0, "hooray": 2, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} 2
  • {"total_count": 15, "+1": 7, "-1": 0, "laugh": 1, "hooray": 1, "confused": 0, "heart": 5, "rocket": 1, "eyes": 0} 1
  • {"total_count": 2, "+1": 1, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 1, "eyes": 0} 1
  • {"total_count": 2, "+1": 1, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 1, "rocket": 0, "eyes": 0} 1
  • {"total_count": 3, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 3, "rocket": 0, "eyes": 0} 1
  • {"total_count": 3, "+1": 0, "-1": 0, "laugh": 0, "hooray": 3, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0} 1

author_association 4

  • OWNER 7,453
  • NONE 818
  • CONTRIBUTOR 501
  • MEMBER 497
id html_url issue_url node_id user created_at updated_at ▲ author_association body reactions issue performed_via_github_app
1352644281 https://github.com/simonw/datasette/issues/1958#issuecomment-1352644281 https://api.github.com/repos/simonw/datasette/issues/1958 IC_kwDOBm6k_c5Qn7a5 simonw 9599 2022-12-15T07:08:14Z 2022-12-15T07:08:14Z OWNER

Thanks for the details write-up! This looks like a bug in Datasette itself when run with Docker. Moving this issue there.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
datasette --root running in Docker doesn't reliably show the magic URL 1497909798  
1352643333 https://github.com/simonw/datasette/issues/1955#issuecomment-1352643333 https://api.github.com/repos/simonw/datasette/issues/1955 IC_kwDOBm6k_c5Qn7MF simonw 9599 2022-12-15T07:07:29Z 2022-12-15T07:07:29Z OWNER

Datasette 0.63 is the release that broke this, thanks to this issue:

  • https://github.com/simonw/datasette/issues/1809
{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Running Datasette using direct Uvicorn/Gunicorn command is not possible 1496652622  
1352643049 https://github.com/simonw/datasette/issues/1955#issuecomment-1352643049 https://api.github.com/repos/simonw/datasette/issues/1955 IC_kwDOBm6k_c5Qn7Hp simonw 9599 2022-12-15T07:07:10Z 2022-12-15T07:07:10Z OWNER

This is definitely a regression: Datasette is meant to work in those environments, and I didn't think to test them when I added the invoke_startup() hook.

Coincidentally I actually built a plugin for running Datasette with Gunicorn just a couple of months ago:

https://datasette.io/plugins/datasette-gunicorn

And I just tested and it has the same bug you describe here! Filed:

  • https://github.com/simonw/datasette-gunicorn/issues/5
{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Running Datasette using direct Uvicorn/Gunicorn command is not possible 1496652622  
1352459146 https://github.com/simonw/datasette/issues/1953#issuecomment-1352459146 https://api.github.com/repos/simonw/datasette/issues/1953 IC_kwDOBm6k_c5QnOOK simonw 9599 2022-12-15T02:02:15Z 2022-12-15T02:02:15Z OWNER

``` The third Datasette 1.0 alpha release adds upsert support to the JSON API, plus the ability to specify finely grained permissions when creating an API token.

  • New /db/table/-/upsert API, documented here. upsert is an update-or-replace: existing rows will have specified keys updated, but if no row matches the incoming primary key a brand new row will be inserted instead. (#1878)
  • New register_permissions(datasette) plugin hook. Plugins can now register named permissions, which will then be listed in various interfaces that show available permissions. (#1940)
  • The /db/-/create API for creating a table now accepts "ignore": true and "replace": true options when called with the "rows" property that creates a new table based on an example set of rows. This means the API can be called multiple times with different rows, setting rules for what should happen if a primary key collides with an existing row. (#1927)
  • Arbitrary permissions can now be configured at the instance, database and resource (table, SQL view or canned query) level in Datasette's Metadata JSON and YAML files. The new "permissions" key can be used to specify which actors should have which permissions. See Other permissions in metadata for details. (#1636)
  • The /-/create-token page can now be used to create API tokens which are restricted to just a subset of actions, including against specific databases or resources. See API Tokens for details. (#1947)
  • Likewise, the datasette create-token CLI command can now create tokens with a subset of permissions. (#1855)
  • New datasette.create_token() API method <datasette_create_token>` for programmatically creating signed API tokens. (#1951)
  • /db/-/create API now requires actor to have insert-row permission in order to use the "row" or "rows" properties. (#1937) ```
{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Release notes for Datasette 1.0a2 1495821607  
1352411327 https://github.com/simonw/datasette/issues/1949#issuecomment-1352411327 https://api.github.com/repos/simonw/datasette/issues/1949 IC_kwDOBm6k_c5QnCi_ simonw 9599 2022-12-15T00:46:27Z 2022-12-15T00:46:27Z OWNER

I got this far: ```diff diff --git a/datasette/handle_exception.py b/datasette/handle_exception.py index 8b7e83e3..31d41e00 100644 --- a/datasette/handle_exception.py +++ b/datasette/handle_exception.py @@ -54,7 +54,17 @@ def handle_exception(datasette, request, exception): headers = {} if datasette.cors: add_cors_headers(headers) - if request.path.split("?")[0].endswith(".json"): + # Return JSON error under certain conditions + should_return_json = ( + # URL ends in .json + request.path.split("?")[0].endswith(".json") + or + # Hints from incoming request headers + request.headers.get("content-type") == "application/json" + or "application/json" in request.headers.get("accept", "") + ) + breakpoint() + if should_return_json: return Response.json(info, status=status, headers=headers) else: template = datasette.jinja_env.select_template(templates) diff --git a/tests/test_api_write.py b/tests/test_api_write.py index f27d143f..982543a6 100644 --- a/tests/test_api_write.py +++ b/tests/test_api_write.py @@ -1140,6 +1140,38 @@ async def test_create_table_permissions( assert data["errors"] == expected_errors

+@pytest.mark.asyncio +@pytest.mark.parametrize( + "headers,expect_json", + ( + ({}, False), + ({"Accept": "text/html"}, True), + ({"Accept": "application/json"}, True), + ({"Content-Type": "application/json"}, True), + ({"Accept": "application/json, text/plain, /"}, True), + ({"Content-Type": "application/json"}, True), + ({"accept": "application/json, text/plain, /"}, True), + ({"content-type": "application/json"}, True), + ), +) +async def test_permission_errors_html_and_json(ds_write, headers, expect_json): + request_headers = {"Authorization": "Bearer bad_token"} + request_headers.update(headers) + response = await ds_write.client.post( + "/data/-/create", + json={}, + headers=request_headers, + ) + assert response.status_code == 403 + if expect_json: + data = response.json() + assert data["ok"] is False + assert data["errors"] == ["Permission denied"] + else: + assert response.headers["Content-Type"] == "text/html; charset=utf-8" + assert "Permission denied" in response.text + + @pytest.mark.asyncio @pytest.mark.parametrize( "input,expected_rows_after", ``` Then decided I would punt this until the next milestone.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`.json` errors should be returned as JSON 1493471221  
1352410078 https://github.com/simonw/datasette/issues/1953#issuecomment-1352410078 https://api.github.com/repos/simonw/datasette/issues/1953 IC_kwDOBm6k_c5QnCPe simonw 9599 2022-12-15T00:44:56Z 2022-12-15T00:44:56Z OWNER

Highlights:

  • /db/table/-/upsert
  • ignore and replace for /db/-/create
  • register_permissions() plugin hook
  • datasette create-token can create restricted tokens
  • /-/create-token can too
  • datasette --get --token option
  • datasette.create_token() API method

Plus some smaller things.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Release notes for Datasette 1.0a2 1495821607  
1352378370 https://github.com/simonw/datasette/issues/1949#issuecomment-1352378370 https://api.github.com/repos/simonw/datasette/issues/1949 IC_kwDOBm6k_c5Qm6gC simonw 9599 2022-12-15T00:02:08Z 2022-12-15T00:04:54Z OWNER

I fixed this issue to help research this further: - https://github.com/simonw/datasette-ripgrep/issues/26

Now this search works:

https://ripgrep.datasette.io/-/ripgrep?pattern=return+_error&literal=on&glob=datasette%2F**

I wish I had this feature! - https://github.com/simonw/datasette-ripgrep/issues/24

Looks like I have both _error() and _errors() functions in there!

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`.json` errors should be returned as JSON 1493471221  
1352371019 https://github.com/simonw/datasette/issues/1957#issuecomment-1352371019 https://api.github.com/repos/simonw/datasette/issues/1957 IC_kwDOBm6k_c5Qm4tL simonw 9599 2022-12-14T23:50:50Z 2022-12-14T23:50:50Z OWNER

One option: if any rows were truncated, show a button you can click to run the query again with truncation disabled - maybe with ?_truncate=off in the URL or similar.

But... still want to truncate if the user runs a query that would return multiple MBs of HTML (assuming the other Datasette query limits don't stop that from happening).

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Reconsider row value truncation on query page 1497577017  
1352357322 https://github.com/simonw/datasette/issues/1295#issuecomment-1352357322 https://api.github.com/repos/simonw/datasette/issues/1295 IC_kwDOBm6k_c5Qm1XK simonw 9599 2022-12-14T23:28:49Z 2022-12-14T23:28:49Z OWNER

Related: - #1875

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Errors should have links to further information 855296937  
1352356356 https://github.com/simonw/datasette/issues/1949#issuecomment-1352356356 https://api.github.com/repos/simonw/datasette/issues/1949 IC_kwDOBm6k_c5Qm1IE simonw 9599 2022-12-14T23:27:25Z 2022-12-14T23:28:16Z OWNER

Also weird: errors returned by that mechanism look like this: json { "ok": false, "errors": ["list of error messages"] } While errors returned by the rest of Datasette look like this: https://latest.datasette.io/fixtures/no_table.json

json { "ok": false, "error": "Table not found: no_table", "status": 404, "title": null } Related: - #1875

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`.json` errors should be returned as JSON 1493471221  
1352354927 https://github.com/simonw/datasette/issues/1949#issuecomment-1352354927 https://api.github.com/repos/simonw/datasette/issues/1949 IC_kwDOBm6k_c5Qm0xv simonw 9599 2022-12-14T23:25:06Z 2022-12-14T23:25:14Z OWNER

Looks like the code I've written for permission checking on TableCreateView and friends doesn't use the regular raise Forbidden or raise DatasetteError mechanisms - it does its own thing here:

https://github.com/simonw/datasette/blob/9ad76d279e2c3874ca5070626a25458ce129f126/datasette/views/database.py#L580-L584

Which uses this:

https://github.com/simonw/datasette/blob/9ad76d279e2c3874ca5070626a25458ce129f126/datasette/views/base.py#L547-L548

Having two different patterns to return errors is bad, I should fix that.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`.json` errors should be returned as JSON 1493471221  
1352340518 https://github.com/simonw/datasette/issues/1949#issuecomment-1352340518 https://api.github.com/repos/simonw/datasette/issues/1949 IC_kwDOBm6k_c5QmxQm simonw 9599 2022-12-14T23:07:01Z 2022-12-14T23:07:01Z OWNER

Easiest fix would be to look for accept: application/json and/or content-type: application/json headers.

Not bullet-proof, so people might occasionally make JSON requests and get back an HTML error - but the documentation can tell people that they need to send those headers if they want to reliably get back JSON error messages.

I'm happy with this as a solution.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`.json` errors should be returned as JSON 1493471221  
1352338620 https://github.com/simonw/datasette/issues/1949#issuecomment-1352338620 https://api.github.com/repos/simonw/datasette/issues/1949 IC_kwDOBm6k_c5Qmwy8 simonw 9599 2022-12-14T23:05:17Z 2022-12-14T23:05:17Z OWNER

Sniffing for a { is a little bit tricky though, as the post body is lazily loaded on request here:

https://github.com/simonw/datasette/blob/9ad76d279e2c3874ca5070626a25458ce129f126/datasette/utils/asgi.py#L127-L135

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`.json` errors should be returned as JSON 1493471221  
1352335503 https://github.com/simonw/datasette/issues/1949#issuecomment-1352335503 https://api.github.com/repos/simonw/datasette/issues/1949 IC_kwDOBm6k_c5QmwCP simonw 9599 2022-12-14T23:03:28Z 2022-12-14T23:03:28Z OWNER

This raises a more complicated issue

At some point I'm likely to want to add an HTML interface for creating tables and inserting and updating rows.

The obvious URLs for that are the same as for the JSON API: /db/table/-/insert and suchlike.

Those endpoints are currently POST only - and can return JSON all the time.

If they start accepting form POSTs too they'll need to be able to accept form-encoded data and return HTML instead. That's OK - they can detect incoming JSON thanks to the content-type header an the fact that the request body starts with { - but the should_return_json fix described above could intefere with how errors are returned if I'm not careful.

I think it can still work though: I'll only set should_return_json = True if the endpoint gets a POST with a body starting {, or a content-type JSON header.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`.json` errors should be returned as JSON 1493471221  
1352331314 https://github.com/simonw/datasette/issues/1949#issuecomment-1352331314 https://api.github.com/repos/simonw/datasette/issues/1949 IC_kwDOBm6k_c5QmvAy simonw 9599 2022-12-14T22:59:36Z 2022-12-14T22:59:36Z OWNER

I'm going to prototype that up to see what it looks like.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`.json` errors should be returned as JSON 1493471221  
1352330825 https://github.com/simonw/datasette/issues/1949#issuecomment-1352330825 https://api.github.com/repos/simonw/datasette/issues/1949 IC_kwDOBm6k_c5Qmu5J simonw 9599 2022-12-14T22:58:51Z 2022-12-14T22:59:27Z OWNER

I need a way for those JSON endpoints to communicate back to the handle_exception handler that they are returning JSON, so it knows to behave differently.

Since it gets the request object, one way could be to have view code set request.should_return_json = True so that the handler knows to do something different.

It's a bit of a cludge though!

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`.json` errors should be returned as JSON 1493471221  
1352329027 https://github.com/simonw/datasette/issues/1949#issuecomment-1352329027 https://api.github.com/repos/simonw/datasette/issues/1949 IC_kwDOBm6k_c5QmudD simonw 9599 2022-12-14T22:56:24Z 2022-12-14T22:57:19Z OWNER

Most .json errors DO return as JSON, thanks to this:

https://github.com/simonw/datasette/blob/c094dde3ff2bae030f261e6440d4fb082eb860a9/datasette/handle_exception.py#L19-L24

https://github.com/simonw/datasette/blob/c094dde3ff2bae030f261e6440d4fb082eb860a9/datasette/handle_exception.py#L57-L58

But that code triggers when the URL ends with .json - and none of the JSON write API endpoints (things like /db/-/create and /db/table/-/insert) follow that convention.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`.json` errors should be returned as JSON 1493471221  
1352075845 https://github.com/simonw/datasette/issues/1956#issuecomment-1352075845 https://api.github.com/repos/simonw/datasette/issues/1956 IC_kwDOBm6k_c5QlwpF simonw 9599 2022-12-14T19:57:17Z 2022-12-14T19:58:22Z OWNER

I'm going to test this using calls to ds.permission_allowed() with an actor with _r block.

I can add extra tests to https://github.com/simonw/datasette/blob/1a3dcf494376e32f7cff110c86a88e5b0a3f3924/tests/test_permissions.py#L605-L616

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Handle abbreviations properly in permission_allowed_actor_restrictions 1497288666  
1352070655 https://github.com/simonw/datasette/issues/1956#issuecomment-1352070655 https://api.github.com/repos/simonw/datasette/issues/1956 IC_kwDOBm6k_c5QlvX_ simonw 9599 2022-12-14T19:54:36Z 2022-12-14T19:54:36Z OWNER

Also this code should work with non-abbreviations too.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Handle abbreviations properly in permission_allowed_actor_restrictions 1497288666  
1352644276 https://github.com/simonw/datasette/issues/1958#issuecomment-1352644276 https://api.github.com/repos/simonw/datasette/issues/1958 IC_kwDOBm6k_c5Qn7a0 davidhaley 11729897 2022-12-14T14:53:53Z 2022-12-14T14:53:53Z NONE

I don't have much experience with Python; however, I wonder if this print statement needs flush=True?

https://github.com/simonw/datasette/blob/fdf7c27b5438f02153c3a7f8ad1b320e4b29e4f4/datasette/cli.py#L621

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
datasette --root running in Docker doesn't reliably show the magic URL 1497909798  
1352644274 https://github.com/simonw/datasette/issues/1958#issuecomment-1352644274 https://api.github.com/repos/simonw/datasette/issues/1958 IC_kwDOBm6k_c5Qn7ay davidhaley 11729897 2022-12-14T14:19:24Z 2022-12-14T14:19:24Z NONE

Hmm, it appears after I kill the process with Ctrl+c:

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
datasette --root running in Docker doesn't reliably show the magic URL 1497909798  
1350415644 https://github.com/simonw/datasette/issues/1952#issuecomment-1350415644 https://api.github.com/repos/simonw/datasette/issues/1952 IC_kwDOBm6k_c5QfbUc simonw 9599 2022-12-14T05:22:59Z 2022-12-14T05:22:59Z OWNER

Non-memory named databases shouldn't show write actions, since those won't persist.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Improvements to /-/create-token restrictions interface 1495716243  
1350414961 https://github.com/simonw/datasette/issues/1947#issuecomment-1350414961 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QfbJx simonw 9599 2022-12-14T05:22:00Z 2022-12-14T05:22:00Z OWNER

I think the next big step for this feature is for me to actually use it to build a few things.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1350414402 https://github.com/simonw/datasette/issues/1947#issuecomment-1350414402 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QfbBC simonw 9599 2022-12-14T05:21:07Z 2022-12-14T05:21:07Z OWNER

It would be neat not to show write permissions against immutable databases too - and not hard from a performance perspective since it doesn't involve hundreds more permission checks.

That will need permissions to grow a flag for if they need a mutable database though, which is a bigger job.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1350413555 https://github.com/simonw/datasette/issues/1947#issuecomment-1350413555 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5Qfazz simonw 9599 2022-12-14T05:19:52Z 2022-12-14T05:19:52Z OWNER

Maybe I should have kept _memory listed for instances that are running with --crossdb enabled?

Yeah I think I should.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1350409537 https://github.com/simonw/datasette/issues/1947#issuecomment-1350409537 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QfZ1B simonw 9599 2022-12-14T05:14:16Z 2022-12-14T05:14:16Z OWNER

New interface now live at https://latest.datasette.io/-/create-token

It shouldn't be showing _memory though.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1350402667 https://github.com/simonw/datasette/issues/1947#issuecomment-1350402667 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QfYJr simonw 9599 2022-12-14T05:05:10Z 2022-12-14T05:05:10Z OWNER

Tests can go here:

https://github.com/simonw/datasette/blob/d98a8effb10ce8fe04a03eae42baa8a9cb0ca3f7/tests/test_auth.py#L143-L160

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1350401651 https://github.com/simonw/datasette/issues/1947#issuecomment-1350401651 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QfX5z simonw 9599 2022-12-14T05:03:59Z 2022-12-14T05:03:59Z OWNER

I shipped a working interface. Could still do with some extra tests.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1350293098 https://github.com/simonw/datasette/issues/1951#issuecomment-1350293098 https://api.github.com/repos/simonw/datasette/issues/1951 IC_kwDOBm6k_c5Qe9Zq simonw 9599 2022-12-14T02:43:44Z 2022-12-14T02:43:44Z OWNER

Documentation for the new method: https://docs.datasette.io/en/latest/internals.html#create-token-actor-id-expires-after-none-restrict-all-none-restrict-database-none-restrict-resource-none

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette.create_token(...)` method for creating signed API tokens 1495431932  
1350231654 https://github.com/simonw/datasette/issues/1951#issuecomment-1350231654 https://api.github.com/repos/simonw/datasette/issues/1951 IC_kwDOBm6k_c5QeuZm simonw 9599 2022-12-14T01:48:50Z 2022-12-14T01:48:57Z OWNER

I like that the word restrict reflects the _r in the actor/token.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette.create_token(...)` method for creating signed API tokens 1495431932  
1350222701 https://github.com/simonw/datasette/issues/1951#issuecomment-1350222701 https://api.github.com/repos/simonw/datasette/issues/1951 IC_kwDOBm6k_c5QesNt simonw 9599 2022-12-14T01:35:05Z 2022-12-14T01:35:22Z OWNER

Maybe this:

```python datasette.create_token("root", expires_after=3600, restrict_all=("view-query", "view-table"))

token = datasette.create_token("root", expires_after=3600, restrict_database={ "fixtures": ("view-query",) })

token = datasette.create_token("root", expires_after=3600, restrict_resource={ "fixtures": { "facetable": ("insert-row", "update-row") } }) ```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette.create_token(...)` method for creating signed API tokens 1495431932  
1350220579 https://github.com/simonw/datasette/issues/1951#issuecomment-1350220579 https://api.github.com/repos/simonw/datasette/issues/1951 IC_kwDOBm6k_c5Qersj simonw 9599 2022-12-14T01:31:38Z 2022-12-14T01:31:38Z OWNER

The problem with all=(..) is it feels misleading - it's actually restricting the permissions made available to the token.

Likewise, databases= being a dict of restricted permissions isn't completely obvious.

And the nested tables= dictionary feels a bit odd too.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette.create_token(...)` method for creating signed API tokens 1495431932  
1350218177 https://github.com/simonw/datasette/issues/1951#issuecomment-1350218177 https://api.github.com/repos/simonw/datasette/issues/1951 IC_kwDOBm6k_c5QerHB simonw 9599 2022-12-14T01:27:45Z 2022-12-14T01:30:41Z OWNER

Some sketches: ```python

Token for root user

token = datasette.create_token("root")

Expiring in an hour

token = datasette.create_token("root", expires_after=3600) More complicated is when you want to restrict to specific permissions:python

Limited to view-query and view-table

token = datasette.create_token("root", expires_after=3600, all=("view-query", "view-table"))

I'm not sure about that all= name

Limits within a specific database:

token = datasette.create_token("root", expires_after=3600, databases={ "fixtures": ("view-query",) })

And specific tables:

token = datasette.create_token("root", expires_after=3600, tables={ "fixtures": { "facetable": ("insert-row", "update-row") } }) ```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette.create_token(...)` method for creating signed API tokens 1495431932  
1350217380 https://github.com/simonw/datasette/issues/1951#issuecomment-1350217380 https://api.github.com/repos/simonw/datasette/issues/1951 IC_kwDOBm6k_c5Qeq6k simonw 9599 2022-12-14T01:26:22Z 2022-12-14T01:26:22Z OWNER

It's going to look very similar to the CLI tool, at least in terms of capabilities: ``` Usage: datasette create-token [OPTIONS] ID

Create a signed API token for the specified actor ID

Example:

  datasette create-token root --secret mysecret

To allow only "view-database-download" for all databases:

  datasette create-token root --secret mysecret \
      --all view-database-download

To allow "create-table" against a specific database:

  datasette create-token root --secret mysecret \
      --database mydb create-table

To allow "insert-row" against a specific table:

  datasette create-token root --secret myscret \
      --resource mydb mytable insert-row

Restricted actions can be specified multiple times using multiple --all, --database, and --resource options.

Add --debug to see a decoded version of the token. ```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette.create_token(...)` method for creating signed API tokens 1495431932  
1350215936 https://github.com/simonw/datasette/issues/1947#issuecomment-1350215936 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QeqkA simonw 9599 2022-12-14T01:23:42Z 2022-12-14T01:23:42Z OWNER

With tilde-encoding for database and table names the HTML looks like this:

html <input type="checkbox" name="table:weird:foo~3Abar:view-table">

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1350148192 https://github.com/simonw/datasette/issues/1947#issuecomment-1350148192 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QeaBg simonw 9599 2022-12-14T00:19:06Z 2022-12-14T00:19:06Z OWNER

Another option: I could set a time limit - say 200ms - on how long I'm willing to spend calculating permissions before displaying this form

First calculate view permissions for tables and databases (and maybe views and canned queries too).

Then see if I can check every permission that I'm going to show as a checkbox on this page. If I get that done within the time limit use that to show the options.

If I run out of time show all options and maybe include a note saying that some of them may not actually be available.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1350125018 https://github.com/simonw/datasette/issues/1947#issuecomment-1350125018 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QeUXa simonw 9599 2022-12-14T00:08:09Z 2022-12-14T00:08:09Z OWNER

Also: don't show hidden tables.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1350124381 https://github.com/simonw/datasette/issues/1947#issuecomment-1350124381 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QeUNd simonw 9599 2022-12-14T00:07:51Z 2022-12-14T00:07:51Z OWNER

Another thing to consider in the future: once Datasette can support thousands of tables (see #417) the list on this page will turn into multiple MBs of HTML, which may cause all kinds of problems - not to mention the overhead of all of those table visibility permission checks.

Hopefully by then I'll have a good fix for the permission listings problem: - #1152

And I can apply the same mechanism here.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1352644270 https://github.com/simonw/datasette/issues/1958#issuecomment-1352644270 https://api.github.com/repos/simonw/datasette/issues/1958 IC_kwDOBm6k_c5Qn7au davidhaley 11729897 2022-12-14T00:05:16Z 2022-12-14T00:05:16Z NONE

FYI @simonw, I don't see that message.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
datasette --root running in Docker doesn't reliably show the magic URL 1497909798  
1350037572 https://github.com/simonw/datasette/issues/1947#issuecomment-1350037572 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5Qd_BE simonw 9599 2022-12-13T23:27:32Z 2022-12-13T23:27:32Z OWNER

I'm going to ignore the permissions issue for the moment - I'll allow people to select any permissions they like in any of the databases or tables that are visible to them (don't want to leak the existence of databases/tables to users who shouldn't be able to see them).

I think the value of getting this working outweights any potential confusion from not using finely grained permission checks to decide if the user should be able to apply a permission or not.

The tokens themselves won't be able to perform insert-row or similar if the user doesn't have the ability to do that, even if they selected that checkbox.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1350019528 https://github.com/simonw/datasette/issues/1947#issuecomment-1350019528 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5Qd6nI simonw 9599 2022-12-13T23:19:16Z 2022-12-13T23:19:16Z OWNER

Here's the checkbox prototype: ```diff diff --git a/datasette/templates/create_token.html b/datasette/templates/create_token.html index a94881ed..1795ebaf 100644 --- a/datasette/templates/create_token.html +++ b/datasette/templates/create_token.html @@ -2,11 +2,20 @@

{% block title %}Create an API token{% endblock %}

+{% block extra_head %} +<style type="text/css"> +#restrict-permissions label { + display: inline; + width: 90%; +} +</style> +{% endblock %} + {% block content %}

Create an API token

-

This token will allow API access with the same abilities as your current user.

+

This token will allow API access with the same abilities as your current user, {{ request.actor.id }}

{% if errors %} {% for error in errors %} @@ -27,8 +36,39 @@ - + +

+ Restrict actions that can be performed using this token +

All databases and tables

+
    + {% for permission in all_permissions %} +
  • <label> {{ permission }}</label>
  • + {% endfor %} +
+ + {% for database in databases %} +

All tables in database: {{ database }}

+
    + {% for permission in database_permissions %} +
  • <label> {{ permission }}</label>
  • + {% endfor %} +
+ {% endfor %} +

Specific tables

+ {% for dbt in database_with_tables %} + {% for table in dbt.tables %} +

{{ dbt.database }}: {{ table }}

+
    + {% for permission in table_permissions %} +
  • <label> {{ permission }}</label>
  • + {% endfor %} +
+ {% endfor %} + {% endfor %} +
+ </form> +

{% if token %}

diff --git a/datasette/views/special.py b/datasette/views/special.py index 30345d14..48357f87 100644 --- a/datasette/views/special.py +++ b/datasette/views/special.py @@ -231,12 +231,37 @@ class CreateTokenView(BaseView): return await self.render( ["create_token.html"], request, - {"actor": request.actor}, + { + "actor": request.actor, + "all_permissions": self.ds.permissions.keys(), + "database_permissions": [ + key + for key, value in self.ds.permissions.items() + if value.takes_database + ], + "table_permissions": [ + key + for key, value in self.ds.permissions.items() + if value.takes_resource + ], + "databases": [k for k in self.ds.databases.keys() if k != "_internal"], + "database_with_tables": [ + { + "database": db.name, + "tables": await db.table_names(), + } + for db in self.ds.databases.values() + if db.name != "_internal" + ], + }, ) async def post(self, request): self.check_permission(request) post = await request.post_vars() + from pprint import pprint + + pprint(post) errors = [] duration = None if post.get("expire_type"): ```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1350013016 https://github.com/simonw/datasette/issues/1947#issuecomment-1350013016 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5Qd5BY simonw 9599 2022-12-13T23:16:24Z 2022-12-13T23:17:17Z OWNER

Slightly tricky thing here is that it should only show permissions that the user themselves has - on databases and tables that they have permission to access.

I have a nasty feeling this may require looping through everything and running every permission check, which could get very expensive if there are plugins involved that do their own storage check to resolve a permission.

It's that classic permission system problem: how to efficiently iterate through everything the user has permission to do in one go?

Might be that I have to punt on that, and show the user a list of permissions to select that they might not actually have ability for.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1350008636 https://github.com/simonw/datasette/issues/1947#issuecomment-1350008636 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5Qd388 simonw 9599 2022-12-13T23:14:33Z 2022-12-13T23:14:33Z OWNER

Checkbox interface looks like this. It's not beautiful but it's good enough for the moment:

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1350002434 https://github.com/simonw/datasette/issues/1947#issuecomment-1350002434 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5Qd2cC simonw 9599 2022-12-13T23:11:50Z 2022-12-13T23:11:59Z OWNER

I think checkboxes will work well.

Here's the data I get back from them (as post_vars()):

{'all:debug-menu': 'on', 'all:insert-row': 'on', 'expire_duration': '', 'expire_type': '', 'table:fixtures:delete-row': 'on', 'table:fixtures:drop-table': 'on', 'table:fixtures:view-query': 'on'}

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1349975255 https://github.com/simonw/datasette/issues/1947#issuecomment-1349975255 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QdvzX simonw 9599 2022-12-13T23:00:11Z 2022-12-13T23:00:11Z OWNER

My <select multiple> prototype: ```diff diff --git a/datasette/templates/create_token.html b/datasette/templates/create_token.html index a94881ed..5bd641cc 100644 --- a/datasette/templates/create_token.html +++ b/datasette/templates/create_token.html @@ -6,7 +6,7 @@

Create an API token

-

This token will allow API access with the same abilities as your current user.

+

This token will allow API access with the same abilities as your current user, {{ request.actor.id }}

{% if errors %} {% for error in errors %} @@ -28,6 +28,36 @@ + +

+ Restrict actions that can be performed using this token +

Restrict actions that can be performed using this token:

+

<label="all_permissions">All databases and tables:</label>

+

<select multiple id="all_permissions" size="{{ all_permissions|length * 4 }}"> + <optgroup label="All databases and tables"> + {% for permission in all_permissions %} + <option value="all:{{ permission }}">{{ permission }}</option> + {% endfor %} + </optgroup> + {% for database in databases %} + <optgroup label="All tables in database: {{ database }}"> + {% for permission in database_permissions %} + <option value="db:{{ database }}:{{ permission }}">{{ permission }}</option> + {% endfor %} + </optgroup> + {% endfor %} + {% for dbt in database_with_tables %} + {% for table in dbt.tables %} + <optgroup label="Table {{ dbt.database }}.{{ table }}"> + {% for permission in table_permissions %} + <option value="table:{{ dbt.database }}:{{ permission }}">{{ permission }}</option> + {% endfor %} + </optgroup> + {% endfor %} + {% endfor %} + </select>

+
+ </form>

{% if token %} diff --git a/datasette/views/special.py b/datasette/views/special.py index 30345d14..9d0fcd31 100644 --- a/datasette/views/special.py +++ b/datasette/views/special.py @@ -231,7 +231,17 @@ class CreateTokenView(BaseView): return await self.render( ["create_token.html"], request, - {"actor": request.actor}, + { + "actor": request.actor, + "all_permissions": self.ds.permissions.keys(), + "database_permissions": [key for key, value in self.ds.permissions.items() if value.takes_database], + "table_permissions": [key for key, value in self.ds.permissions.items() if value.takes_resource], + "databases": self.ds.databases.keys(), + "database_with_tables": [{ + "database": db.name, + "tables": await db.table_names(), + } for db in self.ds.databases.values()], + }, )

 async def post(self, request):

```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1349974287 https://github.com/simonw/datasette/issues/1947#issuecomment-1349974287 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QdvkP simonw 9599 2022-12-13T22:59:44Z 2022-12-13T22:59:44Z OWNER

Got an option group thing working:

But... it strikes me that any time you're considering a <select multiple> like this a nested list of checkboxes would actually be better - easier for people to use.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1349972480 https://github.com/simonw/datasette/issues/1947#issuecomment-1349972480 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QdvIA simonw 9599 2022-12-13T22:58:51Z 2022-12-13T22:58:51Z OWNER

I'm experimenting with a <select multiple> for this.

The usability for keyboards is still pretty awful, but it's a niche enough feature that maybe that's OK for the moment?

javascript var select = document.querySelector('select'); var selected = Array.from(temp0.options).filter(o => o.selected).map(o => o.value)

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1347768549 https://github.com/simonw/datasette/issues/1947#issuecomment-1347768549 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QVVDl simonw 9599 2022-12-13T05:25:56Z 2022-12-13T22:29:12Z OWNER
  • [x] I should add a --database example to that help text.
{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1347768328 https://github.com/simonw/datasette/issues/1947#issuecomment-1347768328 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QVVAI simonw 9599 2022-12-13T05:25:31Z 2022-12-13T22:25:46Z OWNER

https://latest.datasette.io/-/create-token currently looks like this:

As a reminder, the CLI options that this needs to provide an alternative to are:

https://github.com/simonw/datasette/blob/d4b98d3924dec625a99236e65b1b169ff957381f/docs/cli-reference.rst#L619-L638

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1349864950 https://github.com/simonw/datasette/issues/1950#issuecomment-1349864950 https://api.github.com/repos/simonw/datasette/issues/1950 IC_kwDOBm6k_c5QdU32 simonw 9599 2022-12-13T22:11:15Z 2022-12-13T22:11:15Z OWNER

Most places I use that exception at the moment set their own non-500 status error: ``` datasette % rg DatasetteError datasette/handle_exception.py 7:from .views.base import DatasetteError 33: elif isinstance(exception, DatasetteError):

datasette/filters.py 2:from datasette.views.base import DatasetteError 22: raise DatasetteError("_where= is not allowed", status=403) 141: raise DatasetteError(

datasette/views/table.py 34:from .base import BaseView, DataView, DatasetteError, ureg, _error 178: raise DatasetteError( 192: raise DatasetteError( 390: raise DatasetteError("Cannot use _sort and _sort_desc at the same time") 394: raise DatasetteError(f"Cannot sort table by {sort}") 400: raise DatasetteError(f"Cannot sort table by {sort_desc}")

datasette/views/base.py 39:class DatasetteError(Exception): 219: raise DatasetteError(str(e), title="Invalid SQL", status=400) 222: raise DatasetteError(str(e)) 224: except DatasetteError: 382: raise DatasetteError( 402: raise DatasetteError(str(e), title="Invalid SQL", status=400) 405: raise DatasetteError(str(e)) 407: except DatasetteError:

datasette/views/table2.py 28:from .base import DataView, DatasetteError, ureg 296: raise DatasetteError( 310: raise DatasetteError( 472: raise DatasetteError("Cannot use _sort and _sort_desc at the same time") 476: raise DatasetteError(f"Cannot sort table by {sort}") 482: raise DatasetteError(f"Cannot sort table by {sort_desc}")

datasette/views/database.py 31:from .base import BaseView, DatasetteError, DataView, _error 188: raise DatasetteError("Invalid database", status=404) 190: raise DatasetteError("Cannot download in-memory databases", status=404) 194: raise DatasetteError("Cannot download database", status=404) ```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Bad ?_sort returns a 500 error, should be a 400 1495241162  
1349855620 https://github.com/simonw/datasette/issues/1950#issuecomment-1349855620 https://api.github.com/repos/simonw/datasette/issues/1950 IC_kwDOBm6k_c5QdSmE simonw 9599 2022-12-13T22:08:50Z 2022-12-13T22:08:50Z OWNER

https://github.com/simonw/datasette/blob/d4b98d3924dec625a99236e65b1b169ff957381f/datasette/views/table.py#L392-L400

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Bad ?_sort returns a 500 error, should be a 400 1495241162  
1352644267 https://github.com/simonw/datasette/issues/1958#issuecomment-1352644267 https://api.github.com/repos/simonw/datasette/issues/1958 IC_kwDOBm6k_c5Qn7ar simonw 9599 2022-12-13T18:33:32Z 2022-12-13T18:33:32Z OWNER

When you run --root you need to follow the special link that gets output to the console:

% datasette --root http://127.0.0.1:8001/-/auth-token?token=036d8055cc8000e9667f21c1dd08722a9358c066463873ad9566d23d88765c52 INFO: Started server process [53934] INFO: Waiting for application startup. INFO: Application startup complete. That /-/auth-token?... link is the one that sets the cookie and lets you in.

{
    "total_count": 1,
    "+1": 1,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
datasette --root running in Docker doesn't reliably show the magic URL 1497909798  
1352644262 https://github.com/simonw/datasette/issues/1958#issuecomment-1352644262 https://api.github.com/repos/simonw/datasette/issues/1958 IC_kwDOBm6k_c5Qn7am davidhaley 11729897 2022-12-13T16:49:31Z 2022-12-13T16:49:52Z NONE

I may have found the reason. I don't think the --root option is taking effect.

Visited: http://127.0.0.1:8001/-/permissions

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
datasette --root running in Docker doesn't reliably show the magic URL 1497909798  
1347801679 https://github.com/simonw/datasette/issues/1914#issuecomment-1347801679 https://api.github.com/repos/simonw/datasette/issues/1914 IC_kwDOBm6k_c5QVdJP simonw 9599 2022-12-13T06:15:54Z 2022-12-13T06:15:54Z OWNER

Should make sure that every API that returns an object as the top level (that's almost all of them) includes "ok": true to indicate no errors.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Finalize design of JSON for Datasette 1.0 1468689139  
1347775760 https://github.com/simonw/datasette/issues/1947#issuecomment-1347775760 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QVW0Q simonw 9599 2022-12-13T05:38:47Z 2022-12-13T05:38:47Z OWNER

I'm going to hide the options for reducing the scope of the token inside a details/summary element.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1347770871 https://github.com/simonw/datasette/issues/1937#issuecomment-1347770871 https://api.github.com/repos/simonw/datasette/issues/1937 IC_kwDOBm6k_c5QVVn3 simonw 9599 2022-12-13T05:30:43Z 2022-12-13T05:30:43Z OWNER

Also you should need update-row permission to use the "replace": true option - I should add that rule to /-/insert add well.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
/db/-/create API should require insert-rows permission to use row: or rows: option 1483320357  
1347767048 https://github.com/simonw/datasette/pull/1938#issuecomment-1347767048 https://api.github.com/repos/simonw/datasette/issues/1938 IC_kwDOBm6k_c5QVUsI simonw 9599 2022-12-13T05:23:18Z 2022-12-13T05:23:18Z OWNER

I landed this already: - #1636

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
"permissions" blocks in metadata.json/yaml 1485488236  
1347766530 https://github.com/simonw/datasette/issues/1948#issuecomment-1347766530 https://api.github.com/repos/simonw/datasette/issues/1948 IC_kwDOBm6k_c5QVUkC simonw 9599 2022-12-13T05:22:19Z 2022-12-13T05:22:19Z OWNER

I tested:

{"id": "root", "_r": {"a": "view-table"}}

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
500 error on permission debug page when testing actors with _r 1493404423  
1347761892 https://github.com/simonw/datasette/issues/1855#issuecomment-1347761892 https://api.github.com/repos/simonw/datasette/issues/1855 IC_kwDOBm6k_c5QVTbk simonw 9599 2022-12-13T05:14:25Z 2022-12-13T05:14:25Z OWNER

New documentation: https://docs.datasette.io/en/latest/authentication.html#restricting-the-actions-that-a-token-can-perform

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette create-token` ability to create tokens with a reduced set of permissions 1423336089  
1347760109 https://github.com/simonw/datasette/issues/1947#issuecomment-1347760109 https://api.github.com/repos/simonw/datasette/issues/1947 IC_kwDOBm6k_c5QVS_t simonw 9599 2022-12-13T05:12:00Z 2022-12-13T05:12:00Z OWNER

For the UI: I think I'm going to dump a whole bunch of form elements on the page (so you can set up to 3 of each category of limit without any JavaScript), then add JavaScript that hides all but one of the options and gives you a "add another" widget that adds multiple more.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
UI to create reduced scope tokens from the `/-/create-token` page 1493390939  
1347759522 https://github.com/simonw/datasette/issues/1855#issuecomment-1347759522 https://api.github.com/repos/simonw/datasette/issues/1855 IC_kwDOBm6k_c5QVS2i simonw 9599 2022-12-13T05:11:43Z 2022-12-13T05:11:43Z OWNER

Decided to do the /-/create-token UI in a separate ticket: - #1947

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette create-token` ability to create tokens with a reduced set of permissions 1423336089  
1347733217 https://github.com/simonw/datasette/issues/1946#issuecomment-1347733217 https://api.github.com/repos/simonw/datasette/issues/1946 IC_kwDOBm6k_c5QVMbh simonw 9599 2022-12-13T04:28:45Z 2022-12-13T04:28:45Z OWNER

Demo of the new feature: % datasette create-token --secret s root dstok_eyJhIjoicm9vdCIsInRva2VuIjoiZHN0b2siLCJ0IjoxNjcwOTA1NjgwfQ.pqSWOwCSNp678hEWl9l5o7m1GaM % datasette --get /-/actor.json {"actor": null} % DATASETTE_SECRET=s datasette --get /-/actor.json --token dstok_eyJhIjoicm9vdCIsInRva2VuIjoiZHN0b2siLCJ0IjoxNjcwOTA1NjgwfQ.pqSWOwCSNp678hEWl9l5o7m1GaM {"actor": {"id": "root", "token": "dstok"}}

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette --get` mechanism for sending tokens 1493339206  
1347732039 https://github.com/simonw/datasette/issues/1946#issuecomment-1347732039 https://api.github.com/repos/simonw/datasette/issues/1946 IC_kwDOBm6k_c5QVMJH simonw 9599 2022-12-13T04:26:20Z 2022-12-13T04:26:20Z OWNER

Two options:

  • --header "Authorization: Bearer XXX" which can be used to send any headers
  • --token XXX to specify the token, which is then sent using that header

I like the second option more, simply because there are currently no other headers that affect how Datasette works. --token feels obvious and easy to use.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette --get` mechanism for sending tokens 1493339206  
1347731288 https://github.com/simonw/datasette/issues/1855#issuecomment-1347731288 https://api.github.com/repos/simonw/datasette/issues/1855 IC_kwDOBm6k_c5QVL9Y simonw 9599 2022-12-13T04:24:50Z 2022-12-13T04:24:50Z OWNER

For the tests for datasette create-token it would be useful if datasette --get had a mechanism for sending an Authorization: Bearer X header.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette create-token` ability to create tokens with a reduced set of permissions 1423336089  
1347726302 https://github.com/simonw/datasette/issues/1855#issuecomment-1347726302 https://api.github.com/repos/simonw/datasette/issues/1855 IC_kwDOBm6k_c5QVKve simonw 9599 2022-12-13T04:16:26Z 2022-12-13T04:16:26Z OWNER

I'm going to move this code into datasette/cli.py - it's a bit unexpected having it live in default_permissions.py like this (I couldn't find the code when I went looking for it earlier).

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette create-token` ability to create tokens with a reduced set of permissions 1423336089  
1347707683 https://github.com/simonw/datasette/issues/1855#issuecomment-1347707683 https://api.github.com/repos/simonw/datasette/issues/1855 IC_kwDOBm6k_c5QVGMj simonw 9599 2022-12-13T03:55:35Z 2022-12-13T04:15:27Z OWNER

Help looks like this:

``` Usage: datasette create-token [OPTIONS] ID

Create a signed API token for the specified actor ID

Example:

  datasette create-token root --secret mysecret

To only allow create-table:

  datasette create-token root --secret mysecret \
      --all create-table

Or to only allow insert-row against a specific table:

  datasette create-token root --secret myscret \
      --resource mydb mytable insert-row

Restricted actions can be specified multiple times using multiple --all, --database, and --resource options.

Add --debug to see a decoded version of the token.

Options: --secret TEXT Secret used for signing the API tokens [required] -e, --expires-after INTEGER Token should expire after this many seconds -a, --all ACTION Restrict token to this action -d, --database DB ACTION Restrict token to this action on this database -r, --resource DB RESOURCE ACTION Restrict token to this action on this database resource (a table, SQL view or named query) --debug Show decoded token --plugins-dir DIRECTORY Path to directory containing custom plugins --help Show this message and exit. ```

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette create-token` ability to create tokens with a reduced set of permissions 1423336089  
1347695728 https://github.com/simonw/datasette/issues/1855#issuecomment-1347695728 https://api.github.com/repos/simonw/datasette/issues/1855 IC_kwDOBm6k_c5QVDRw simonw 9599 2022-12-13T03:30:09Z 2022-12-13T03:30:09Z OWNER

I just noticed this in the existing code:

https://github.com/simonw/datasette/blob/c5d30b58a1cd1c66bbddcf3561db005543ecaf25/datasette/default_permissions.py#L195-L203

Hard-coding those action names should not be necessary any more, especially now we have datasette.permissions for looking up metadata about the permissions.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette create-token` ability to create tokens with a reduced set of permissions 1423336089  
1347694871 https://github.com/simonw/datasette/issues/1855#issuecomment-1347694871 https://api.github.com/repos/simonw/datasette/issues/1855 IC_kwDOBm6k_c5QVDEX simonw 9599 2022-12-13T03:28:15Z 2022-12-13T03:28:15Z OWNER

Initial prototype of the create-token command changes:

diff diff --git a/datasette/default_permissions.py b/datasette/default_permissions.py index 406dae40..bbe1247e 100644 --- a/datasette/default_permissions.py +++ b/datasette/default_permissions.py @@ -278,17 +278,55 @@ def register_commands(cli): help="Token should expire after this many seconds", type=int, ) + @click.option( + "alls", + "-a", + "--all", + type=str, + multiple=True, + help="Restrict token to this permission", + ) + @click.option( + "databases", + "-d", + "--database", + type=(str, str), + multiple=True, + help="Restrict token to this permission on this database", + ) + @click.option( + "resources", + "-r", + "--resource", + type=(str, str, str), + multiple=True, + help="Restrict token to this permission on this database resource (a table, SQL view or named query)", + ) @click.option( "--debug", help="Show decoded token", is_flag=True, ) - def create_token(id, secret, expires_after, debug): + def create_token(id, secret, expires_after, alls, databases, resources, debug): "Create a signed API token for the specified actor ID" ds = Datasette(secret=secret) bits = {"a": id, "token": "dstok", "t": int(time.time())} if expires_after: bits["d"] = expires_after + if alls or databases or resources: + bits["_r"] = {} + if alls: + bits["_r"]["a"] = list(alls) + if databases: + bits["_r"]["d"] = {} + for database, action in databases: + bits["_r"]["d"].setdefault(database, []).append(action) + if resources: + bits["_r"]["r"] = {} + for database, table, action in resources: + bits["_r"]["r"].setdefault(database, {}).setdefault( + table, [] + ).append(action) token = ds.sign(bits, namespace="token") click.echo("dstok_{}".format(token)) if debug: Still needs tests, plus I'd like it to use abbreviations if available to keep the token length shorter.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette create-token` ability to create tokens with a reduced set of permissions 1423336089  
1347693620 https://github.com/simonw/datasette/issues/1855#issuecomment-1347693620 https://api.github.com/repos/simonw/datasette/issues/1855 IC_kwDOBm6k_c5QVCw0 simonw 9599 2022-12-13T03:25:41Z 2022-12-13T03:25:41Z OWNER

I'm going to rename "t" in the magic format to "r" for resource.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette create-token` ability to create tokens with a reduced set of permissions 1423336089  
1347675456 https://github.com/simonw/datasette/issues/1855#issuecomment-1347675456 https://api.github.com/repos/simonw/datasette/issues/1855 IC_kwDOBm6k_c5QU-VA simonw 9599 2022-12-13T02:57:46Z 2022-12-13T02:57:46Z OWNER

I was going to have the CLI command throw an error if you attempt to use a permission that isn't registered with Datasette, but then I remembered that one of the uses for the CLI tool is to create signed tokens that will work against other Datasette instances (via the --secret option) that might have different plugins installed that register different permission names.

So I might have it output warnings instead.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette create-token` ability to create tokens with a reduced set of permissions 1423336089  
1313148519 https://github.com/simonw/datasette/issues/1855#issuecomment-1313148519 https://api.github.com/repos/simonw/datasette/issues/1855 IC_kwDOBm6k_c5ORQ5n simonw 9599 2022-11-14T06:13:43Z 2022-12-13T02:46:51Z OWNER

The datasette create-token command will need to be able to do this too.

Right now that command looks like this: ``` % datasette create-token --help Usage: datasette create-token [OPTIONS] ID

Create a signed API token for the specified actor ID

Options: --secret TEXT Secret used for signing the API tokens [required] -e, --expires-after INTEGER Token should expire after this many seconds --debug Show decoded token --help Show this message and exit. % datasette create-token root --secret sec --debug -e 445 dstok_eyJhIjoicm9vdCIsInRva2VuIjoiZHN0b2siLCJ0IjoxNjY4NDA2MjEzLCJkIjo0NDV9.Hd6qRli6xRKkOIRQgZkPO5iN1wM

Decoded:

{ "a": "root", "token": "dstok", "t": 1668406213, "d": 445 } `` (The--debug` bit adds the decoded token.)

Syntax for adding "insert row" for everything, "update row" for all in the "data" database and "delete row" just for the docs / titles table: datasette create-token root --secret sec \ --all insert-row \ --database data update-row \ --table docs titles delete-row The ir / ur / dr options would work too. To add multiple permissions use these options multiple times: datasette create-token root --secret sec \ --all insert-row \ --all delete-row Short versions: -a and -d and -t.

UPDATE: I have decided to use the term resource in the user-facing elements of this feature instead of table, since that can refer to a SQL view and a canned query as well.

So --resource and -r, not -t.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette create-token` ability to create tokens with a reduced set of permissions 1423336089  
1347669087 https://github.com/simonw/datasette/issues/1855#issuecomment-1347669087 https://api.github.com/repos/simonw/datasette/issues/1855 IC_kwDOBm6k_c5QU8xf simonw 9599 2022-12-13T02:45:15Z 2022-12-13T02:45:15Z OWNER

The hardest piece here is the UI. I'm going to implement the CLI command first.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`datasette create-token` ability to create tokens with a reduced set of permissions 1423336089  
1347655074 https://github.com/simonw/datasette/issues/1636#issuecomment-1347655074 https://api.github.com/repos/simonw/datasette/issues/1636 IC_kwDOBm6k_c5QU5Wi simonw 9599 2022-12-13T02:21:04Z 2022-12-13T02:21:23Z OWNER

The thing I'm stuck on at the moment is how to implement it such that an allow block for create-table at the root of the metadata will be checked correctly.

Maybe the algorithm when _resolve_metadata_permissions_blocks(datasette, actor, action, resource) is called should do this:

  1. If a root permission block matching that action exists, test with that
  2. Next, if resource has been passed, check at the database level
  3. If the resource included a table/query, check at that level too

So everything is keyed off the incoming action name.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
"permissions" propery in metadata for configuring arbitrary permissions 1138008042  
1347648326 https://github.com/simonw/datasette/issues/1636#issuecomment-1347648326 https://api.github.com/repos/simonw/datasette/issues/1636 IC_kwDOBm6k_c5QU3tG simonw 9599 2022-12-13T02:10:02Z 2022-12-13T02:10:02Z OWNER

The implementation for this will go here: https://github.com/simonw/datasette/blob/8bf06a76b51bc9ace7cf72cf0cca8f1da7704ea7/datasette/default_permissions.py#L81-L83

Here's the start of the tests (currently marked as xfail):

https://github.com/simonw/datasette/blob/8bf06a76b51bc9ace7cf72cf0cca8f1da7704ea7/tests/test_permissions.py#L652-L689

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
"permissions" propery in metadata for configuring arbitrary permissions 1138008042  
1347647298 https://github.com/simonw/datasette/issues/1636#issuecomment-1347647298 https://api.github.com/repos/simonw/datasette/issues/1636 IC_kwDOBm6k_c5QU3dC simonw 9599 2022-12-13T02:08:46Z 2022-12-13T02:08:46Z OWNER

A bunch of the work for this just landed - in particular the new scheme is now documented (even though it doesn't work yet):

https://docs.datasette.io/en/latest/authentication.html#other-permissions-in-metadata

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
"permissions" propery in metadata for configuring arbitrary permissions 1138008042  
1347646516 https://github.com/simonw/datasette/issues/1939#issuecomment-1347646516 https://api.github.com/repos/simonw/datasette/issues/1939 IC_kwDOBm6k_c5QU3Q0 simonw 9599 2022-12-13T02:07:50Z 2022-12-13T02:07:50Z OWNER

Documentation for the new hook: https://docs.datasette.io/en/latest/plugin_hooks.html#register-permissions-datasette

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions(datasette) plugin hook 1485757511  
1347645615 https://github.com/simonw/datasette/issues/1943#issuecomment-1347645615 https://api.github.com/repos/simonw/datasette/issues/1943 IC_kwDOBm6k_c5QU3Cv simonw 9599 2022-12-13T02:06:47Z 2022-12-13T02:06:47Z OWNER

This URL is already used for the https://latest.datasette.io/-/permissions tool - but it could include a block on that page that tells you what permissions are available.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
`/-/permissions` should list available permissions 1490576818  
1347640542 https://github.com/simonw/datasette/pull/1940#issuecomment-1347640542 https://api.github.com/repos/simonw/datasette/issues/1940 IC_kwDOBm6k_c5QU1ze simonw 9599 2022-12-13T02:02:10Z 2022-12-13T02:02:10Z OWNER

This PR ended up bundling part of the implementation of: - #1636

I'm going to be bad an NOT untangle that from this before I merge it.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions() plugin hook 1486011362  
1347632350 https://github.com/simonw/datasette/pull/1940#issuecomment-1347632350 https://api.github.com/repos/simonw/datasette/issues/1940 IC_kwDOBm6k_c5QUzze codecov[bot] 22429695 2022-12-13T01:48:40Z 2022-12-13T02:00:52Z NONE

Codecov Report

Base: 92.00% // Head: 92.03% // Increases project coverage by +0.02% :tada:

Coverage data is based on head (a1317ab) compared to base (e539c1c). Patch coverage: 100.00% of modified lines in pull request are covered.

:exclamation: Current head a1317ab differs from pull request most recent head 94e5c75. Consider uploading reports for the commit 94e5c75 to get more accurate results

Additional details and impacted files ```diff @@ Coverage Diff @@ ## main #1940 +/- ## ========================================== + Coverage 92.00% 92.03% +0.02% ========================================== Files 38 38 Lines 5378 5396 +18 ========================================== + Hits 4948 4966 +18 Misses 430 430 ``` | [Impacted Files](https://codecov.io/gh/simonw/datasette/pull/1940?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison) | Coverage Δ | | |---|---|---| | [datasette/permissions.py](https://codecov.io/gh/simonw/datasette/pull/1940/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL3Blcm1pc3Npb25zLnB5) | `100.00% <ø> (ø)` | | | [datasette/views/database.py](https://codecov.io/gh/simonw/datasette/pull/1940/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL3ZpZXdzL2RhdGFiYXNlLnB5) | `96.26% <ø> (ø)` | | | [datasette/views/index.py](https://codecov.io/gh/simonw/datasette/pull/1940/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL3ZpZXdzL2luZGV4LnB5) | `96.49% <ø> (ø)` | | | [datasette/views/special.py](https://codecov.io/gh/simonw/datasette/pull/1940/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL3ZpZXdzL3NwZWNpYWwucHk=) | `79.20% <ø> (-0.21%)` | :arrow_down: | | [datasette/views/table.py](https://codecov.io/gh/simonw/datasette/pull/1940/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL3ZpZXdzL3RhYmxlLnB5) | `92.57% <ø> (ø)` | | | [datasette/\_\_init\_\_.py](https://codecov.io/gh/simonw/datasette/pull/1940/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL19faW5pdF9fLnB5) | `100.00% <100.00%> (ø)` | | | [datasette/app.py](https://codecov.io/gh/simonw/datasette/pull/1940/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL2FwcC5weQ==) | `94.47% <100.00%> (+0.04%)` | :arrow_up: | | [datasette/default\_permissions.py](https://codecov.io/gh/simonw/datasette/pull/1940/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL2RlZmF1bHRfcGVybWlzc2lvbnMucHk=) | `95.20% <100.00%> (+0.39%)` | :arrow_up: | | [datasette/hookspecs.py](https://codecov.io/gh/simonw/datasette/pull/1940/diff?src=pr&el=tree&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison#diff-ZGF0YXNldHRlL2hvb2tzcGVjcy5weQ==) | `100.00% <100.00%> (ø)` | | Help us with your feedback. Take ten seconds to tell us [how you rate us](https://about.codecov.io/nps?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison). Have a feature suggestion? [Share it here.](https://app.codecov.io/gh/feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=Simon+Willison)

:umbrella: View full report at Codecov.
:loudspeaker: Do you have feedback about the report comment? Let us know in this issue.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions() plugin hook 1486011362  
1347634128 https://github.com/simonw/datasette/pull/1940#issuecomment-1347634128 https://api.github.com/repos/simonw/datasette/issues/1940 IC_kwDOBm6k_c5QU0PQ simonw 9599 2022-12-13T01:51:56Z 2022-12-13T01:51:56Z OWNER

Actually one last thing: I said that the error would only occur if the permissions differed in some way.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions() plugin hook 1486011362  
1347620733 https://github.com/simonw/datasette/pull/1940#issuecomment-1347620733 https://api.github.com/repos/simonw/datasette/issues/1940 IC_kwDOBm6k_c5QUw99 simonw 9599 2022-12-13T01:33:06Z 2022-12-13T01:33:06Z OWNER

It's this change which triggers the failures: ```diff diff --git a/datasette/app.py b/datasette/app.py index 760063d5..defa9688 100644 --- a/datasette/app.py +++ b/datasette/app.py @@ -707,9 +707,12 @@ class Datasette: ) return crumbs

  • async def permission_allowed(self, actor, action, resource=None, default=False):
  • async def permission_allowed(self, actor, action, resource=None, default=None): """Check permissions using the permissions_allowed plugin hook""" result = None
  • Use default from registered permission, if available

  • if default is None and action in self.permissions:
  • default = self.permissions[action].default for check in pm.hook.permission_allowed( datasette=self, actor=actor, ```
{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions() plugin hook 1486011362  
1347616055 https://github.com/simonw/datasette/pull/1940#issuecomment-1347616055 https://api.github.com/repos/simonw/datasette/issues/1940 IC_kwDOBm6k_c5QUv03 simonw 9599 2022-12-13T01:27:03Z 2022-12-13T01:27:03Z OWNER

I'm going to revert that last commit, see if I can get the tests running again and then apply the changes a line at a time to figure out which ones broke things.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions() plugin hook 1486011362  
1345701316 https://github.com/simonw/datasette/pull/1940#issuecomment-1345701316 https://api.github.com/repos/simonw/datasette/issues/1940 IC_kwDOBm6k_c5QNcXE simonw 9599 2022-12-12T00:10:59Z 2022-12-12T00:10:59Z OWNER

Here's my first test failure: ``` tests/test_permissions.py .......F

traceback >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

allow = {}, expected_anon = 403, expected_auth = 403, path = '/fixtures/compound_three_primary_keys' padlock_client = <datasette.utils.testing.TestClient object at 0x107c09d20>

@pytest.mark.parametrize(
    "allow,expected_anon,expected_auth",
    [
        (None, 200, 200),
        ({}, 403, 403),
        ({"id": "root"}, 403, 200),
    ],
)
@pytest.mark.parametrize(
    "path",
    (
        "/",
        "/fixtures",
        "/fixtures/compound_three_primary_keys",
        "/fixtures/compound_three_primary_keys/a,a,a",
        "/fixtures/two",  # Query
    ),
)
def test_view_padlock(allow, expected_anon, expected_auth, path, padlock_client):
    padlock_client.ds._metadata_local["allow"] = allow
    fragment = "🔒</h1>"
    anon_response = padlock_client.get(path)
  assert expected_anon == anon_response.status

E assert 403 == 200 E + where 200 = <datasette.utils.testing.TestResponse object at 0x107aea680>.status

/Users/simon/Dropbox/Development/datasette/tests/test_permissions.py:61: AssertionError

entering PDB >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

PDB post_mortem (IO-capturing turned off) >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> /Users/simon/Dropbox/Development/datasette/tests/test_permissions.py(61)test_view_padlock() -> assert expected_anon == anon_response.status (Pdb) anon_response <datasette.utils.testing.TestResponse object at 0x107aea680> (Pdb) anon_response.status 200 (Pdb) path '/fixtures/compound_three_primary_keys' (Pdb) padlock_client.ds.metadata *** AttributeError: 'Datasette' object has no attribute 'metadata' (Pdb) padlock_client.ds._metadata_local {'databases': {'fixtures': {'queries': {'two': {'sql': 'select 1 + 1', 'name': 'two'}, 'from_async_hook': {'sql': 'select 2', 'name': 'from_async_hook'}, 'from_hook': {'sql': "select 1, 'null' as actor_id", 'name': 'from_hook'}}, 'source': None, 'source_url': None, 'license': None, 'license_url': None, 'about': None, 'about_url': None}}, 'allow': {}} (Pdb) allow {} `` It looks like I've broken theallowlogic that notices that if there's an"allow": {}` on the root then anonymous users should not be allowed to view any pages.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions() plugin hook 1486011362  
1345691103 https://github.com/simonw/datasette/issues/1939#issuecomment-1345691103 https://api.github.com/repos/simonw/datasette/issues/1939 IC_kwDOBm6k_c5QNZ3f simonw 9599 2022-12-11T23:37:49Z 2022-12-11T23:37:49Z OWNER

Idea: a /-/permissions introspection endpoint for listing registered permissions

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions(datasette) plugin hook 1485757511  
1344965367 https://github.com/simonw/sqlite-utils/issues/517#issuecomment-1344965367 https://api.github.com/repos/simonw/sqlite-utils/issues/517 IC_kwDOCGYnMM5QKor3 simonw 9599 2022-12-10T01:26:31Z 2022-12-10T01:26:31Z OWNER

At some point I should drop it from all of these other projects too: https://cs.github.com/?scopeName=All+repos&scope=&q=user%3Asimonw+%223.6%22+path%3A.github%2Fworkflows%2F*

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Drop support for Python 3.6 1487757143  
1344959032 https://github.com/simonw/datasette/issues/1942#issuecomment-1344959032 https://api.github.com/repos/simonw/datasette/issues/1942 IC_kwDOBm6k_c5QKnI4 simonw 9599 2022-12-10T01:11:26Z 2022-12-10T01:11:26Z OWNER

One way this could work: if plugins request it, a block like this is added to the page:

```html

<script type="application/json+datasette" id="datasette-json"> { "...": "..." } </script>

Then a function could be provided which extracts and parses that data:javascript var data = await datasette.jsonData(); `` Why anawait? Because then I could have it work the exact same way if the data is NOT available on the page - it could trigger afetch()` call for the same stuff. So loading it on the page becomes an optional performance optimization.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Option for plugins to request that JSON be served on the page 1487738738  
1344669160 https://github.com/simonw/datasette/issues/1706#issuecomment-1344669160 https://api.github.com/repos/simonw/datasette/issues/1706 IC_kwDOBm6k_c5QJgXo rdmurphy 419145 2022-12-09T19:11:40Z 2022-12-09T19:11:40Z NONE

Ah, yes! Was just trying to do this and had the same issue. +1 to this!

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
[feature] immutable mode for a directory, not just individual sqlite file 1198822563  
1343872168 https://github.com/simonw/datasette/issues/1939#issuecomment-1343872168 https://api.github.com/repos/simonw/datasette/issues/1939 IC_kwDOBm6k_c5QGdyo simonw 9599 2022-12-09T05:29:53Z 2022-12-09T05:29:53Z OWNER

I'm going to address those ideas for changes to the permission_allowed() in a separate issue.

What would it take for the register_permissions() hook to be something I'm comfortable landing?

I think it's mainly that the list of permissions it provides should Do More Stuff:

  • Participate in unit tests, in particular this one:

https://github.com/simonw/datasette/blob/e539c1c024bc62d88df91d9107cbe37e7f0fe55f/tests/conftest.py#L79-L102

  • That new default option should be respected - maybe if you omit default= from a call to permission_allowed() it could fall back on the default from there?
  • Log a warning if you attempt to check a permission that wasn't registered

Then I can use the permissions - in particular their metadata - to help implement his:

  • 1636

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions(datasette) plugin hook 1485757511  
1343869900 https://github.com/simonw/datasette/issues/1941#issuecomment-1343869900 https://api.github.com/repos/simonw/datasette/issues/1941 IC_kwDOBm6k_c5QGdPM simonw 9599 2022-12-09T05:25:19Z 2022-12-09T05:25:19Z OWNER

I don't plan to implement this for Datasette 1.0, but it should be something that can be added later on in a minor version bump.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
Mechanism for supporting key rotation for DATASETTE_SECRET 1486036269  
1343858998 https://github.com/simonw/datasette/issues/1939#issuecomment-1343858998 https://api.github.com/repos/simonw/datasette/issues/1939 IC_kwDOBm6k_c5QGak2 simonw 9599 2022-12-09T05:12:17Z 2022-12-09T05:12:17Z OWNER

Draft docs for the new plugin hook: https://datasette--1940.org.readthedocs.build/en/1940/plugin_hooks.html#register-permissions-datasette

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions(datasette) plugin hook 1485757511  
1343856781 https://github.com/simonw/datasette/issues/1939#issuecomment-1343856781 https://api.github.com/repos/simonw/datasette/issues/1939 IC_kwDOBm6k_c5QGaCN simonw 9599 2022-12-09T05:10:00Z 2022-12-09T05:10:00Z OWNER

Made a draft PR so ReadTheDocs would deploy my new documentation somewhere.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions(datasette) plugin hook 1485757511  
1343844555 https://github.com/simonw/datasette/issues/1939#issuecomment-1343844555 https://api.github.com/repos/simonw/datasette/issues/1939 IC_kwDOBm6k_c5QGXDL simonw 9599 2022-12-09T04:48:28Z 2022-12-09T04:48:28Z OWNER

I'm going to try a spike in a branch with datasette.action_allowed(...) and a register_permissions() plugin hook, to see what they look like.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions(datasette) plugin hook 1485757511  
1343844112 https://github.com/simonw/datasette/issues/1939#issuecomment-1343844112 https://api.github.com/repos/simonw/datasette/issues/1939 IC_kwDOBm6k_c5QGW8Q simonw 9599 2022-12-09T04:47:28Z 2022-12-09T04:47:28Z OWNER

I think action_allowed is my favourite, even though there's a little bit of concept overlap with table_actions and database_actions.

I never really liked those plugin hook names much to be honest, especially since they are inconsistent with menu_links:

https://github.com/simonw/datasette/blob/d67f812b7327c7075732688f3df728807503dc58/datasette/hookspecs.py#L123-L135

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions(datasette) plugin hook 1485757511  
1343843352 https://github.com/simonw/datasette/issues/1939#issuecomment-1343843352 https://api.github.com/repos/simonw/datasette/issues/1939 IC_kwDOBm6k_c5QGWwY simonw 9599 2022-12-09T04:45:50Z 2022-12-09T04:45:50Z OWNER

Another option:

python if await datasette.actor_can(actor, "insert-data"...)

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions(datasette) plugin hook 1485757511  
1343842362 https://github.com/simonw/datasette/issues/1939#issuecomment-1343842362 https://api.github.com/repos/simonw/datasette/issues/1939 IC_kwDOBm6k_c5QGWg6 simonw 9599 2022-12-09T04:43:38Z 2022-12-09T04:43:38Z OWNER

Asked ChatGPT for some alternative names, I didn't like any of them:

is_permission_granted
has_permission
check_permission
is_action_allowed
check_access_permission
permission_check
validate_permission
check_actor_permission
verify_permission
check_authorization
{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions(datasette) plugin hook 1485757511  
1343753386 https://github.com/simonw/datasette/issues/1939#issuecomment-1343753386 https://api.github.com/repos/simonw/datasette/issues/1939 IC_kwDOBm6k_c5QGAyq simonw 9599 2022-12-09T02:20:20Z 2022-12-09T02:21:01Z OWNER

It's also referenced in this plugin hook: python @hookspec def permission_allowed(datasette, actor, action, resource): """Check if actor is allowed to perform this action - return True, False or None""" But more importantly, in these ones: ```python @hookspec def table_actions(datasette, actor, database, table, request): """Links for the table actions menu"""

@hookspec def database_actions(datasette, actor, database, request): """Links for the database actions menu""" ``` So the word "action" is already used within Datasette to refer to those things - which are almost but not quite the same as actions-as-permissions: many of the things that show up in those menus relate to permissions the user has, but not necessarily all of them.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions(datasette) plugin hook 1485757511  
1343751860 https://github.com/simonw/datasette/issues/1939#issuecomment-1343751860 https://api.github.com/repos/simonw/datasette/issues/1939 IC_kwDOBm6k_c5QGAa0 simonw 9599 2022-12-09T02:18:11Z 2022-12-09T02:19:23Z OWNER

Should I rename "permission" to "action" elsewhere too? Maybe have a register_actions(...) plugin hook instead of adding register_permissions(...)?

What else could the word "action" mean?

Currently it's used in the codebase to refer to GitHub Actions, and for code like this: python if await self.permission_allowed( actor=actor, action="view-instance", default=True ): Which is already revealing the confusion between "permission" and "action".

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions(datasette) plugin hook 1485757511  
1343751261 https://github.com/simonw/datasette/issues/1939#issuecomment-1343751261 https://api.github.com/repos/simonw/datasette/issues/1939 IC_kwDOBm6k_c5QGARd simonw 9599 2022-12-09T02:17:14Z 2022-12-09T02:17:14Z OWNER

One option: python async def action_allowed(self, actor, action, database=None, resource=None): action_allowed fixes the permission v.s. action thing a bit, and is a new name that doesn't clash with the existing method. I dropped default because that's now a property of the permission itself. table is now called resource and database is a separate parameter.

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions(datasette) plugin hook 1485757511  
1343749617 https://github.com/simonw/datasette/issues/1939#issuecomment-1343749617 https://api.github.com/repos/simonw/datasette/issues/1939 IC_kwDOBm6k_c5QF_3x simonw 9599 2022-12-09T02:15:54Z 2022-12-09T02:15:54Z OWNER

What if I came up with a new method name for this, which could co-exist with the old one while that old one was deprecated?

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions(datasette) plugin hook 1485757511  
1343744338 https://github.com/simonw/datasette/issues/1939#issuecomment-1343744338 https://api.github.com/repos/simonw/datasette/issues/1939 IC_kwDOBm6k_c5QF-lS simonw 9599 2022-12-09T02:08:42Z 2022-12-09T02:08:42Z OWNER

Extracted a TIL: https://til.simonwillison.net/github/github-code-search-api-uses

{
    "total_count": 0,
    "+1": 0,
    "-1": 0,
    "laugh": 0,
    "hooray": 0,
    "confused": 0,
    "heart": 0,
    "rocket": 0,
    "eyes": 0
}
register_permissions(datasette) plugin hook 1485757511  

Next page

Advanced export

JSON shape: default, array, newline-delimited, object

CSV options:

CREATE TABLE [issue_comments] (
   [html_url] TEXT,
   [issue_url] TEXT,
   [id] INTEGER PRIMARY KEY,
   [node_id] TEXT,
   [user] INTEGER REFERENCES [users]([id]),
   [created_at] TEXT,
   [updated_at] TEXT,
   [author_association] TEXT,
   [body] TEXT,
   [reactions] TEXT,
   [issue] INTEGER REFERENCES [issues]([id])
, [performed_via_github_app] TEXT);
CREATE INDEX [idx_issue_comments_issue]
                ON [issue_comments] ([issue]);
CREATE INDEX [idx_issue_comments_user]
                ON [issue_comments] ([user]);
Powered by Datasette · Queries took 349.466ms · About: github-to-sqlite