home / github

Menu
  • Search all tables
  • GraphQL API

issue_comments

Table actions
  • GraphQL API for issue_comments

43 rows where "updated_at" is on date 2022-12-13 sorted by updated_at descending

✖
✖

✎ View and edit SQL

This data as json, CSV (advanced)

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

issue 13

  • UI to create reduced scope tokens from the `/-/create-token` page 12
  • `datasette create-token` ability to create tokens with a reduced set of permissions 11
  • register_permissions() plugin hook 5
  • "permissions" propery in metadata for configuring arbitrary permissions 3
  • `datasette --get` mechanism for sending tokens 2
  • Bad ?_sort returns a 500 error, should be a 400 2
  • datasette --root running in Docker doesn't reliably show the magic URL 2
  • Finalize design of JSON for Datasette 1.0 1
  • /db/-/create API should require insert-rows permission to use row: or rows: option 1
  • "permissions" blocks in metadata.json/yaml 1
  • register_permissions(datasette) plugin hook 1
  • `/-/permissions` should list available permissions 1
  • 500 error on permission debug page when testing actors with _r 1

user 3

  • simonw 41
  • davidhaley 1
  • codecov[bot] 1

author_association 2

  • OWNER 41
  • NONE 2
id html_url issue_url node_id user created_at updated_at ▲ author_association body reactions issue performed_via_github_app
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  

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 472.616ms · About: github-to-sqlite
  • Sort ascending
  • Sort descending
  • Facet by this
  • Hide this column
  • Show all columns
  • Show not-blank rows