github
html_url | issue_url | id | node_id | user | created_at | updated_at | author_association | body | reactions | issue | performed_via_github_app |
---|---|---|---|---|---|---|---|---|---|---|---|
https://github.com/simonw/datasette/issues/2102#issuecomment-1691842259 | https://api.github.com/repos/simonw/datasette/issues/2102 | 1691842259 | IC_kwDOBm6k_c5k13bT | 9599 | 2023-08-24T14:55:54Z | 2023-08-24T14:55:54Z | OWNER | So what's needed to finish this is: - Tests that demonstrate that nothing is revealed that shouldn't be by tokens restricted in this way - Similar tests for other permissions like `create-table` that check that they work (and don't also need `view-instance` etc). - Documentation | { "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
1805076818 | |
https://github.com/simonw/datasette/issues/2102#issuecomment-1691824713 | https://api.github.com/repos/simonw/datasette/issues/2102 | 1691824713 | IC_kwDOBm6k_c5k1zJJ | 9599 | 2023-08-24T14:45:49Z | 2023-08-24T14:45:49Z | OWNER | I tested this out against a Datasette Cloud instance. I created a restricted token and tested it like this: ```bash curl -H "Authorization: Bearer $TOKEN" \ 'https://$INSTANCE/-/actor.json' | jq ``` ```json { "actor": { "id": "245", "token": "dsatok", "token_id": 2, "_r": { "r": { "data": { "all_stocks": [ "vt" ] } } } } } ``` It can access the `all_stocks` demo table: ```bash curl -H "Authorization: Bearer $TOKEN" \ 'https://$INSTANCE/data/all_stocks.json?_size=1' | jq ``` ```json { "ok": true, "next": "1", "rows": [ { "rowid": 1, "Date": "2013-01-02", "Open": 79.12, "High": 79.29, "Low": 77.38, "Close": 78.43, "Volume": 140124866, "Name": "AAPL" } ], "truncated": false } ``` Accessing the database returns just information about that table, even though other tables exist: ```bash curl -H "Authorization: Bearer $TOKEN" \ 'https://$INSTANCE/data.json?_size=1' ``` ```json { "database": "data", "private": true, "path": "/data", "size": 3796992, "tables": [ { "name": "all_stocks", "columns": [ "Date", "Open", "High", "Low", "Close", "Volume", "Name" ], "primary_keys": [], "count": 8813, "hidden": false, "fts_table": null, "foreign_keys": { "incoming": [], "outgoing": [] }, "private": true } ], "hidden_count": 0, "views": [], "queries": [], "allow_execute_sql": false, "table_columns": {} } ``` And hitting the top-level `/.json` thing does the same - it reveals that table but not any of the other tables or databases: ```bash curl -H "Authorization: Bearer $TOKEN" \ 'https://$INSTANCE/.json?_size=1' ``` ```json { "data": { "name": "data", "hash": null, "color": "8d777f", "path": "/data", … | { "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
1805076818 | |
https://github.com/simonw/datasette/issues/2102#issuecomment-1691758168 | https://api.github.com/repos/simonw/datasette/issues/2102 | 1691758168 | IC_kwDOBm6k_c5k1i5Y | 9599 | 2023-08-24T14:09:45Z | 2023-08-24T14:09:45Z | OWNER | I'm going to implement this in a branch to make it easier to test out. | { "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
1805076818 | |
https://github.com/simonw/datasette/issues/2102#issuecomment-1691045051 | https://api.github.com/repos/simonw/datasette/issues/2102 | 1691045051 | IC_kwDOBm6k_c5ky0y7 | 9599 | 2023-08-24T05:51:59Z | 2023-08-24T05:51:59Z | OWNER | With that fix in place, this works: ```bash datasette fixtures.db --get '/fixtures/facetable.json' --actor '{ "_r": { "r": { "fixtures": { "facetable": [ "vt" ] } } }, "a": "user" }' ``` But this fails, because it's for a table not explicitly listed: ```bash datasette fixtures.db --get '/fixtures/searchable.json' --actor '{ "_r": { "r": { "fixtures": { "facetable": [ "vt" ] } } }, "a": "user" }' ``` | { "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
1805076818 | |
https://github.com/simonw/datasette/issues/2102#issuecomment-1691044283 | https://api.github.com/repos/simonw/datasette/issues/2102 | 1691044283 | IC_kwDOBm6k_c5ky0m7 | 9599 | 2023-08-24T05:51:02Z | 2023-08-24T05:51:02Z | OWNER | Also need to confirm that permissions like `insert-row`, `delete-row`, `create-table` etc don't also need special cases to ensure they get through the `view-instance` etc checks, if those exist for those actions. | { "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
1805076818 | |
https://github.com/simonw/datasette/issues/2102#issuecomment-1691043475 | https://api.github.com/repos/simonw/datasette/issues/2102 | 1691043475 | IC_kwDOBm6k_c5ky0aT | 9599 | 2023-08-24T05:50:04Z | 2023-08-24T05:50:04Z | OWNER | On first test this seems to work! ```diff diff --git a/datasette/default_permissions.py b/datasette/default_permissions.py index 63a66c3c..9303dac8 100644 --- a/datasette/default_permissions.py +++ b/datasette/default_permissions.py @@ -187,6 +187,30 @@ def permission_allowed_actor_restrictions(datasette, actor, action, resource): return None _r = actor.get("_r") + # Special case for view-instance: it's allowed if there are any view-database + # or view-table permissions defined + if action == "view-instance": + database_rules = _r.get("d") or {} + for rules in database_rules.values(): + if "vd" in rules or "view-database" in rules: + return None + # Now check resources + resource_rules = _r.get("r") or {} + for _database, resources in resource_rules.items(): + for rules in resources.values(): + if "vt" in rules or "view-table" in rules: + return None + + # Special case for view-database: it's allowed if there are any view-table permissions + # defined within that database + if action == "view-database": + database_name = resource + resource_rules = _r.get("r") or {} + resources_in_database = resource_rules.get(database_name) or {} + for rules in resources_in_database.values(): + if "vt" in rules or "view-table" in rules: + return None + # Does this action have an abbreviation? to_check = {action} permission = datasette.permissions.get(action) ``` Needs a LOT of testing to make sure what it's doing is sensible though. | { "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
1805076818 | |
https://github.com/simonw/datasette/issues/2102#issuecomment-1691037971 | https://api.github.com/repos/simonw/datasette/issues/2102 | 1691037971 | IC_kwDOBm6k_c5kyzET | 9599 | 2023-08-24T05:42:47Z | 2023-08-24T05:42:47Z | OWNER | I applied a fun trick to help test this out: ```diff diff --git a/datasette/cli.py b/datasette/cli.py index 58f89c1c..830f47ef 100644 --- a/datasette/cli.py +++ b/datasette/cli.py @@ -445,6 +445,10 @@ def uninstall(packages, yes): "--token", help="API token to send with --get requests", ) +@click.option( + "--actor", + help="Actor to use for --get requests", +) @click.option("--version-note", help="Additional note to show on /-/versions") @click.option("--help-settings", is_flag=True, help="Show available settings") @click.option("--pdb", is_flag=True, help="Launch debugger on any errors") @@ -499,6 +503,7 @@ def serve( root, get, token, + actor, version_note, help_settings, pdb, @@ -611,7 +616,10 @@ def serve( headers = {} if token: headers["Authorization"] = "Bearer {}".format(token) - response = client.get(get, headers=headers) + cookies = {} + if actor: + cookies["ds_actor"] = client.actor_cookie(json.loads(actor)) + response = client.get(get, headers=headers, cookies=cookies) click.echo(response.text) exit_code = 0 if response.status == 200 else 1 sys.exit(exit_code) ``` This adds a `--actor` option to `datasette ... --get /path` which makes it easy to test an API endpoint using a fake actor with a set of `_r` restrictions. With that in place I can try this, with a token that has view-instance and view-database and view-table: ```bash datasette fixtures.db --get '/fixtures/facetable.json' --actor '{ "_r": { "a": [ "vi" ], "d": { "fixtures": [ "vd" ] }, "r": { "fixtures": { "facetable": [ "vt" ] } } }, "a": "user" }' ``` Or this, with a token that just has view-table but is missing the view-database and view-instan… | { "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
1805076818 | |
https://github.com/simonw/datasette/issues/2102#issuecomment-1691036559 | https://api.github.com/repos/simonw/datasette/issues/2102 | 1691036559 | IC_kwDOBm6k_c5kyyuP | 9599 | 2023-08-24T05:40:53Z | 2023-08-24T05:40:53Z | OWNER | There might be an easier way to solve this. Here's some permission checks that run when hitting `/fixtures/facetable.json`: ``` permission_allowed: action=view-table, resource=('fixtures', 'facetable'), actor={'_r': {'a': ['vi'], 'd': {'fixtures': ['vd']}, 'r': {'fixtures': {'facetable': ['vt']}}}, 'a': 'user'} File "/datasette/views/table.py", line 727, in table_view_traced view_data = await table_view_data( File "/datasette/views/table.py", line 875, in table_view_data visible, private = await datasette.check_visibility( File "/datasette/app.py", line 890, in check_visibility await self.ensure_permissions(actor, permissions) permission_allowed: action=view-database, resource=fixtures, actor={'_r': {'a': ['vi'], 'd': {'fixtures': ['vd']}, 'r': {'fixtures': {'facetable': ['vt']}}}, 'a': 'user'} File "/datasette/views/table.py", line 727, in table_view_traced view_data = await table_view_data( File "/datasette/views/table.py", line 875, in table_view_data visible, private = await datasette.check_visibility( File "/datasette/app.py", line 890, in check_visibility await self.ensure_permissions(actor, permissions) permission_allowed: action=view-instance, resource=<None>, actor={'_r': {'a': ['vi'], 'd': {'fixtures': ['vd']}, 'r': {'fixtures': {'facetable': ['vt']}}}, 'a': 'user'} File "/datasette/views/table.py", line 727, in table_view_traced view_data = await table_view_data( File "/datasette/views/table.py", line 875, in table_view_data visible, private = await datasette.check_visibility( File "/datasette/app.py", line 890, in check_visibility await self.ensure_permissions(actor, permissions) ``` That's with a token that has the view instance, view database and view table permissions required. But... what if the restrictions logic said that if you have view-table you automatically also get view-database and view-instance? Would that actually let people do anything they shouldn't be able to do? I don't think … | { "total_count": 0, "+1": 0, "-1": 0, "laugh": 0, "hooray": 0, "confused": 0, "heart": 0, "rocket": 0, "eyes": 0 } |
1805076818 |