{"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1636036312", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1636036312, "node_id": "IC_kwDOBm6k_c5hg-7Y", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-14T15:37:14Z", "updated_at": "2023-07-14T15:37:14Z", "author_association": "OWNER", "body": "I think I made this decision because I was thinking about default deny: obviously if a user has been denied access to a database. It doesn't make sense that they could access tables within it.\r\n\r\nBut now that I am spending more time with authentication tokens, which default to denying everything, except for the things that you have explicitly listed, this policy, no longer makes as much sense.\r\n\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1636040164", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1636040164, "node_id": "IC_kwDOBm6k_c5hg_3k", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-14T15:40:21Z", "updated_at": "2023-07-14T15:40:21Z", "author_association": "OWNER", "body": "Relevant code: \r\nhttps://github.com/simonw/datasette/blob/0f7192b6154edb576c41b55bd3f2a3f53e5f436a/datasette/app.py#L822-L855", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1636042066", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1636042066, "node_id": "IC_kwDOBm6k_c5hhAVS", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-14T15:41:54Z", "updated_at": "2023-07-14T15:42:32Z", "author_association": "OWNER", "body": "I tried some code spelunking and came across https://github.com/simonw/datasette/commit/d6e03b04302a0852e7133dc030eab50177c37be7 which says:\r\n\r\n> - If you have table permission but not database permission you can now view the table page\r\n\r\nRefs:\r\n- #832 \r\n\r\nWhich suggests that my initial design decision wasn't what appears to be implemented today.\r\n\r\nNeeds more investigation.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1636053060", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1636053060, "node_id": "IC_kwDOBm6k_c5hhDBE", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-14T15:51:36Z", "updated_at": "2023-07-14T16:14:05Z", "author_association": "OWNER", "body": "This might only be an issue with the code that checks `_r` on actors.\r\n\r\nhttps://github.com/simonw/datasette/blob/0f7192b6154edb576c41b55bd3f2a3f53e5f436a/datasette/default_permissions.py#L185-L222\r\n\r\nAdded in https://github.com/simonw/datasette/commit/bcc781f4c50a8870e3389c4e60acb625c34b0317 - refs:\r\n\r\n- #1855 ", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1636093730", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1636093730, "node_id": "IC_kwDOBm6k_c5hhM8i", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-14T16:26:27Z", "updated_at": "2023-07-14T16:32:49Z", "author_association": "OWNER", "body": "Here's that crucial comment:\r\n\r\n> If _r is defined then we use those to further restrict the actor.\r\n>\r\n>Crucially, we only use this to say NO (return False) - we never use it to return YES (True) because that might over-ride other restrictions placed on this actor\r\n\r\nSo that's why I implemented it like this.\r\n\r\nThe goal here is to be able to issue a token which can't do anything _more_ than the actor it is associated with, but CAN be configured to do less.\r\n\r\nSo I think the solution here is for the `_r` checking code to perhaps implement its own view cascade logic - it notices if you have `view-table` and consequently fails to block `view-table` and `view-instance`.\r\n\r\nI'm not sure that's going to work though - would that mean that granting `view-table` grants `view-database` in a surprising and harmful way?\r\n\r\nMaybe that's OK: if you have `view-database` but permission checks fail for individual tables and queries you shouldn't be able to see a thing that you shouldn't. Need to verify that though.\r\n\r\nAlso, do `Permission` instances have enough information to implement this kind of cascade without hard-coding anything? \r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1638567228", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1638567228, "node_id": "IC_kwDOBm6k_c5hqo08", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-17T17:24:19Z", "updated_at": "2023-07-17T17:25:12Z", "author_association": "OWNER", "body": "Confirmed that this is an issue with regular Datasette signed tokens as well. I created one on https://latest.datasette.io/-/create-token with these details:\r\n```json\r\n{\r\n \"_r\": {\r\n \"r\": {\r\n \"fixtures\": {\r\n \"sortable\": [\r\n \"vt\"\r\n ]\r\n }\r\n }\r\n },\r\n \"a\": \"root\",\r\n \"d\": 3600,\r\n \"t\": 1689614483\r\n}\r\n```\r\nRun like this:\r\n```\r\ncurl -H 'Authorization: Bearer dstok_eyJhIjoicm9vdCIsInQiOjE2ODk2MTQ0ODMsImQiOjM2MDAsIl9yIjp7InIiOnsiZml4dHVyZXMiOnsic29ydGFibGUiOlsidnQiXX19fX0.n-VGxxawz1Q0WK7sqLfhXUgcvY0' \\\r\n https://latest.datasette.io/fixtures/sortable.json\r\n```\r\nReturned an HTML Forbidden page:\r\n```html\r\n\r\n\r\n\r\n Forbidden\r\n \r\n...\r\n```\r\nSame token againts `/-/actor.json` returns:\r\n```json\r\n{\r\n \"actor\": {\r\n \"id\": \"root\",\r\n \"token\": \"dstok\",\r\n \"_r\": {\r\n \"r\": {\r\n \"fixtures\": {\r\n \"sortable\": [\r\n \"vt\"\r\n ]\r\n }\r\n }\r\n },\r\n \"token_expires\": 1689618083\r\n }\r\n}\r\n```\r\nReminder - `\"_r\"` means restrict, `\"r\"` means resource.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1640064620", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1640064620, "node_id": "IC_kwDOBm6k_c5hwWZs", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-07-18T11:47:21Z", "updated_at": "2023-07-18T11:47:21Z", "author_association": "OWNER", "body": "I think I've figured out the problem here.\r\n\r\nThe question being asked is \"can this actor access this resource, which is within this database within this instance\".\r\n\r\nThe answer to this question needs to consider the full set of questions at once - yes they can access within this instance IF they have access to the specified table and that's the table being asked about.\r\n\r\nBut the questions are currently being asked independently, which means the plugin hook acting on `view-instance` can't see that the answer here should be yes because it's actually about a table that the actor has explicit permission to view.\r\n\r\nSo I think I may need to redesign the plugin hook to always see the full hierarchy of checks, not just a single check at a time.\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1690693830", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1690693830, "node_id": "IC_kwDOBm6k_c5kxfDG", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-23T21:51:52Z", "updated_at": "2023-08-23T21:52:58Z", "author_association": "OWNER", "body": "This is the hook in question: https://github.com/simonw/datasette/blob/bdf59eb7db42559e538a637bacfe86d39e5d17ca/datasette/hookspecs.py#L108-L110\r\n\r\n- `True` means they are allowed to access it. You only need a single`True` from a plugin to allow it.\r\n- `False` means they are not, and just one `False` from a plugin will deny it (even if another one returned `True` I think)\r\n- `None` means that the plugin has no opinion on this question.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1690703764", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1690703764, "node_id": "IC_kwDOBm6k_c5kxheU", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-23T22:02:14Z", "updated_at": "2023-08-23T22:02:14Z", "author_association": "OWNER", "body": "Built this new test:\r\n```python\r\n@pytest.mark.asyncio\r\nasync def test_view_table_token_can_access_table(perms_ds):\r\n actor = {\r\n \"id\": \"restricted-token\",\r\n \"token\": \"dstok\",\r\n # Restricted to just view-table on perms_ds_two/t1\r\n \"_r\": {\"r\": {\"perms_ds_two\": {\"t1\": [\"vt\"]}}},\r\n }\r\n cookies = {\"ds_actor\": perms_ds.client.actor_cookie(actor)}\r\n response = await perms_ds.client.get(\"/perms_ds_two/t1.json\", cookies=cookies)\r\n assert response.status_code == 200\r\n```\r\nThe test fails. Running it with `pytest --pdb` let me do this:\r\n```\r\n(Pdb) from pprint import pprint\r\n(Pdb) pprint(perms_ds._permission_checks)\r\ndeque([{'action': 'view-table',\r\n 'actor': {'_r': {'r': {'perms_ds_two': {'t1': ['vt']}}},\r\n 'id': 'restricted-token',\r\n 'token': 'dstok'},\r\n 'resource': ('perms_ds_two', 't1'),\r\n 'result': None,\r\n 'used_default': True,\r\n 'when': '2023-08-23T21:59:45.117155'},\r\n {'action': 'view-database',\r\n 'actor': {'_r': {'r': {'perms_ds_two': {'t1': ['vt']}}},\r\n 'id': 'restricted-token',\r\n 'token': 'dstok'},\r\n 'resource': 'perms_ds_two',\r\n 'result': False,\r\n 'used_default': False,\r\n 'when': '2023-08-23T21:59:45.117189'},\r\n {'action': 'view-instance',\r\n 'actor': {'_r': {'r': {'perms_ds_two': {'t1': ['vt']}}},\r\n 'id': 'restricted-token',\r\n 'token': 'dstok'},\r\n 'resource': None,\r\n 'result': False,\r\n 'used_default': False,\r\n 'when': '2023-08-23T21:59:45.126751'},\r\n {'action': 'debug-menu',\r\n 'actor': {'_r': {'r': {'perms_ds_two': {'t1': ['vt']}}},\r\n 'id': 'restricted-token',\r\n 'token': 'dstok'},\r\n 'resource': None,\r\n 'result': False,\r\n 'used_default': False,\r\n 'when': '2023-08-23T21:59:45.126777'}],\r\n maxlen=200)\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1690705243", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1690705243, "node_id": "IC_kwDOBm6k_c5kxh1b", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-23T22:03:54Z", "updated_at": "2023-08-23T22:03:54Z", "author_association": "OWNER", "body": "Idea: `datasette-permissions-debug` plugin which simply prints out a stacktrace for every permission check so you can see where in the code they are.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1691036559", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1691036559, "node_id": "IC_kwDOBm6k_c5kyyuP", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-24T05:40:53Z", "updated_at": "2023-08-24T05:40:53Z", "author_association": "OWNER", "body": "There might be an easier way to solve this. Here's some permission checks that run when hitting `/fixtures/facetable.json`:\r\n\r\n```\r\npermission_allowed: action=view-table, resource=('fixtures', 'facetable'), actor={'_r': {'a': ['vi'], 'd': {'fixtures': ['vd']}, 'r': {'fixtures': {'facetable': ['vt']}}}, 'a': 'user'}\r\n\r\n File \"/datasette/views/table.py\", line 727, in table_view_traced\r\n view_data = await table_view_data(\r\n\r\n File \"/datasette/views/table.py\", line 875, in table_view_data\r\n visible, private = await datasette.check_visibility(\r\n\r\n File \"/datasette/app.py\", line 890, in check_visibility\r\n await self.ensure_permissions(actor, permissions)\r\n\r\npermission_allowed: action=view-database, resource=fixtures, actor={'_r': {'a': ['vi'], 'd': {'fixtures': ['vd']}, 'r': {'fixtures': {'facetable': ['vt']}}}, 'a': 'user'}\r\n\r\n File \"/datasette/views/table.py\", line 727, in table_view_traced\r\n view_data = await table_view_data(\r\n\r\n File \"/datasette/views/table.py\", line 875, in table_view_data\r\n visible, private = await datasette.check_visibility(\r\n\r\n File \"/datasette/app.py\", line 890, in check_visibility\r\n await self.ensure_permissions(actor, permissions)\r\n\r\npermission_allowed: action=view-instance, resource=, actor={'_r': {'a': ['vi'], 'd': {'fixtures': ['vd']}, 'r': {'fixtures': {'facetable': ['vt']}}}, 'a': 'user'}\r\n\r\n File \"/datasette/views/table.py\", line 727, in table_view_traced\r\n view_data = await table_view_data(\r\n\r\n File \"/datasette/views/table.py\", line 875, in table_view_data\r\n visible, private = await datasette.check_visibility(\r\n\r\n File \"/datasette/app.py\", line 890, in check_visibility\r\n await self.ensure_permissions(actor, permissions)\r\n```\r\nThat's with a token that has the view instance, view database and view table permissions required.\r\n\r\nBut... what if the restrictions logic said that if you have view-table you automatically also get view-database and view-instance?\r\n\r\nWould that actually let people do anything they shouldn't be able to do? I don't think it would even let them see a list of tables that they weren't allowed to visit, so it might be OK.\r\n\r\nI'll try that and see how it works.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1691037971", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1691037971, "node_id": "IC_kwDOBm6k_c5kyzET", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-24T05:42:47Z", "updated_at": "2023-08-24T05:42:47Z", "author_association": "OWNER", "body": "I applied a fun trick to help test this out:\r\n```diff\r\ndiff --git a/datasette/cli.py b/datasette/cli.py\r\nindex 58f89c1c..830f47ef 100644\r\n--- a/datasette/cli.py\r\n+++ b/datasette/cli.py\r\n@@ -445,6 +445,10 @@ def uninstall(packages, yes):\r\n \"--token\",\r\n help=\"API token to send with --get requests\",\r\n )\r\n+@click.option(\r\n+ \"--actor\",\r\n+ help=\"Actor to use for --get requests\",\r\n+)\r\n @click.option(\"--version-note\", help=\"Additional note to show on /-/versions\")\r\n @click.option(\"--help-settings\", is_flag=True, help=\"Show available settings\")\r\n @click.option(\"--pdb\", is_flag=True, help=\"Launch debugger on any errors\")\r\n@@ -499,6 +503,7 @@ def serve(\r\n root,\r\n get,\r\n token,\r\n+ actor,\r\n version_note,\r\n help_settings,\r\n pdb,\r\n@@ -611,7 +616,10 @@ def serve(\r\n headers = {}\r\n if token:\r\n headers[\"Authorization\"] = \"Bearer {}\".format(token)\r\n- response = client.get(get, headers=headers)\r\n+ cookies = {}\r\n+ if actor:\r\n+ cookies[\"ds_actor\"] = client.actor_cookie(json.loads(actor))\r\n+ response = client.get(get, headers=headers, cookies=cookies)\r\n click.echo(response.text)\r\n exit_code = 0 if response.status == 200 else 1\r\n sys.exit(exit_code)\r\n```\r\nThis 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.\r\n\r\nWith that in place I can try this, with a token that has view-instance and view-database and view-table:\r\n```bash\r\ndatasette fixtures.db --get '/fixtures/facetable.json' --actor '{\r\n \"_r\": {\r\n \"a\": [\r\n \"vi\"\r\n ],\r\n \"d\": {\r\n \"fixtures\": [\r\n \"vd\"\r\n ]\r\n },\r\n \"r\": {\r\n \"fixtures\": {\r\n \"facetable\": [\r\n \"vt\"\r\n ]\r\n }\r\n }\r\n },\r\n \"a\": \"user\"\r\n}'\r\n```\r\nOr this, with a token that just has view-table but is missing the view-database and view-instance:\r\n```bash\r\ndatasette fixtures.db --get '/fixtures/facetable.json' --actor '{\r\n \"_r\": {\r\n \"r\": {\r\n \"fixtures\": {\r\n \"facetable\": [\r\n \"vt\"\r\n ]\r\n }\r\n }\r\n },\r\n \"a\": \"user\"\r\n}'\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1691043475", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1691043475, "node_id": "IC_kwDOBm6k_c5ky0aT", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-24T05:50:04Z", "updated_at": "2023-08-24T05:50:04Z", "author_association": "OWNER", "body": "On first test this seems to work!\r\n\r\n```diff\r\ndiff --git a/datasette/default_permissions.py b/datasette/default_permissions.py\r\nindex 63a66c3c..9303dac8 100644\r\n--- a/datasette/default_permissions.py\r\n+++ b/datasette/default_permissions.py\r\n@@ -187,6 +187,30 @@ def permission_allowed_actor_restrictions(datasette, actor, action, resource):\r\n return None\r\n _r = actor.get(\"_r\")\r\n \r\n+ # Special case for view-instance: it's allowed if there are any view-database\r\n+ # or view-table permissions defined\r\n+ if action == \"view-instance\":\r\n+ database_rules = _r.get(\"d\") or {}\r\n+ for rules in database_rules.values():\r\n+ if \"vd\" in rules or \"view-database\" in rules:\r\n+ return None\r\n+ # Now check resources\r\n+ resource_rules = _r.get(\"r\") or {}\r\n+ for _database, resources in resource_rules.items():\r\n+ for rules in resources.values():\r\n+ if \"vt\" in rules or \"view-table\" in rules:\r\n+ return None\r\n+\r\n+ # Special case for view-database: it's allowed if there are any view-table permissions\r\n+ # defined within that database\r\n+ if action == \"view-database\":\r\n+ database_name = resource\r\n+ resource_rules = _r.get(\"r\") or {}\r\n+ resources_in_database = resource_rules.get(database_name) or {}\r\n+ for rules in resources_in_database.values():\r\n+ if \"vt\" in rules or \"view-table\" in rules:\r\n+ return None\r\n+\r\n # Does this action have an abbreviation?\r\n to_check = {action}\r\n permission = datasette.permissions.get(action)\r\n```\r\nNeeds a LOT of testing to make sure what it's doing is sensible though.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1691044283", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1691044283, "node_id": "IC_kwDOBm6k_c5ky0m7", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-24T05:51:02Z", "updated_at": "2023-08-24T05:51:02Z", "author_association": "OWNER", "body": "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.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1691045051", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1691045051, "node_id": "IC_kwDOBm6k_c5ky0y7", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-24T05:51:59Z", "updated_at": "2023-08-24T05:51:59Z", "author_association": "OWNER", "body": "With that fix in place, this works:\r\n```bash\r\ndatasette fixtures.db --get '/fixtures/facetable.json' --actor '{\r\n \"_r\": {\r\n \"r\": {\r\n \"fixtures\": {\r\n \"facetable\": [\r\n \"vt\"\r\n ]\r\n }\r\n }\r\n },\r\n \"a\": \"user\"\r\n}'\r\n```\r\nBut this fails, because it's for a table not explicitly listed:\r\n```bash\r\ndatasette fixtures.db --get '/fixtures/searchable.json' --actor '{\r\n \"_r\": {\r\n \"r\": {\r\n \"fixtures\": {\r\n \"facetable\": [\r\n \"vt\"\r\n ]\r\n }\r\n }\r\n },\r\n \"a\": \"user\"\r\n}'\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1691758168", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1691758168, "node_id": "IC_kwDOBm6k_c5k1i5Y", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-24T14:09:45Z", "updated_at": "2023-08-24T14:09:45Z", "author_association": "OWNER", "body": "I'm going to implement this in a branch to make it easier to test out.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1691824713", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1691824713, "node_id": "IC_kwDOBm6k_c5k1zJJ", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-24T14:45:49Z", "updated_at": "2023-08-24T14:45:49Z", "author_association": "OWNER", "body": "I tested this out against a Datasette Cloud instance. I created a restricted token and tested it like this:\r\n```bash\r\ncurl -H \"Authorization: Bearer $TOKEN\" \\\r\n 'https://$INSTANCE/-/actor.json' | jq\r\n```\r\n```json\r\n{\r\n \"actor\": {\r\n \"id\": \"245\",\r\n \"token\": \"dsatok\",\r\n \"token_id\": 2,\r\n \"_r\": {\r\n \"r\": {\r\n \"data\": {\r\n \"all_stocks\": [\r\n \"vt\"\r\n ]\r\n }\r\n }\r\n }\r\n }\r\n}\r\n```\r\nIt can access the `all_stocks` demo table:\r\n```bash\r\ncurl -H \"Authorization: Bearer $TOKEN\" \\\r\n 'https://$INSTANCE/data/all_stocks.json?_size=1' | jq\r\n```\r\n```json\r\n{\r\n \"ok\": true,\r\n \"next\": \"1\",\r\n \"rows\": [\r\n {\r\n \"rowid\": 1,\r\n \"Date\": \"2013-01-02\",\r\n \"Open\": 79.12,\r\n \"High\": 79.29,\r\n \"Low\": 77.38,\r\n \"Close\": 78.43,\r\n \"Volume\": 140124866,\r\n \"Name\": \"AAPL\"\r\n }\r\n ],\r\n \"truncated\": false\r\n}\r\n```\r\nAccessing the database returns just information about that table, even though other tables exist:\r\n```bash\r\ncurl -H \"Authorization: Bearer $TOKEN\" \\\r\n 'https://$INSTANCE/data.json?_size=1'\r\n```\r\n```json\r\n{\r\n \"database\": \"data\",\r\n \"private\": true,\r\n \"path\": \"/data\",\r\n \"size\": 3796992,\r\n \"tables\": [\r\n {\r\n \"name\": \"all_stocks\",\r\n \"columns\": [\r\n \"Date\",\r\n \"Open\",\r\n \"High\",\r\n \"Low\",\r\n \"Close\",\r\n \"Volume\",\r\n \"Name\"\r\n ],\r\n \"primary_keys\": [],\r\n \"count\": 8813,\r\n \"hidden\": false,\r\n \"fts_table\": null,\r\n \"foreign_keys\": {\r\n \"incoming\": [],\r\n \"outgoing\": []\r\n },\r\n \"private\": true\r\n }\r\n ],\r\n \"hidden_count\": 0,\r\n \"views\": [],\r\n \"queries\": [],\r\n \"allow_execute_sql\": false,\r\n \"table_columns\": {}\r\n}\r\n```\r\nAnd hitting the top-level `/.json` thing does the same - it reveals that table but not any of the other tables or databases:\r\n```bash\r\ncurl -H \"Authorization: Bearer $TOKEN\" \\\r\n 'https://$INSTANCE/.json?_size=1'\r\n```\r\n```json\r\n{\r\n \"data\": {\r\n \"name\": \"data\",\r\n \"hash\": null,\r\n \"color\": \"8d777f\",\r\n \"path\": \"/data\",\r\n \"tables_and_views_truncated\": [\r\n {\r\n \"name\": \"all_stocks\",\r\n \"columns\": [\r\n \"Date\",\r\n \"Open\",\r\n \"High\",\r\n \"Low\",\r\n \"Close\",\r\n \"Volume\",\r\n \"Name\"\r\n ],\r\n \"primary_keys\": [],\r\n \"count\": null,\r\n \"hidden\": false,\r\n \"fts_table\": null,\r\n \"num_relationships_for_sorting\": 0,\r\n \"private\": false\r\n }\r\n ],\r\n \"tables_and_views_more\": false,\r\n \"tables_count\": 1,\r\n \"table_rows_sum\": 0,\r\n \"show_table_row_counts\": false,\r\n \"hidden_table_rows_sum\": 0,\r\n \"hidden_tables_count\": 0,\r\n \"views_count\": 0,\r\n \"private\": false\r\n }\r\n}\r\n```", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1691842259", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1691842259, "node_id": "IC_kwDOBm6k_c5k13bT", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-24T14:55:54Z", "updated_at": "2023-08-24T14:55:54Z", "author_association": "OWNER", "body": "So what's needed to finish this is:\r\n- Tests that demonstrate that nothing is revealed that shouldn't be by tokens restricted in this way\r\n- Similar tests for other permissions like `create-table` that check that they work (and don't also need `view-instance` etc).\r\n- Documentation", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1696361304", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1696361304, "node_id": "IC_kwDOBm6k_c5lHGtY", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-28T20:23:47Z", "updated_at": "2023-08-28T20:24:35Z", "author_association": "OWNER", "body": "Here's an existing relevant test:\r\n\r\nhttps://github.com/simonw/datasette/blob/2e2825869fc2655b5fcadc743f6f9dec7a49bc65/tests/test_permissions.py#L616-L666\r\n\r\nIt's not quite right for this new set of tests though, since they need to be exercising actual endpoints (`/.json` etc) in order to check that this works correctly.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/2102#issuecomment-1696378239", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/2102", "id": 1696378239, "node_id": "IC_kwDOBm6k_c5lHK1_", "user": {"value": 9599, "label": "simonw"}, "created_at": "2023-08-28T20:38:01Z", "updated_at": "2023-08-28T20:38:01Z", "author_association": "OWNER", "body": "I want to test \"for this set of restrictions, does a GET/POST to this path return 200 or 403\"?", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1805076818, "label": "API tokens with view-table but not view-database/view-instance cannot access the table"}, "performed_via_github_app": null}