{"html_url": "https://github.com/simonw/datasette/issues/1829#issuecomment-1288321630", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1829", "id": 1288321630, "node_id": "IC_kwDOBm6k_c5Myjpe", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-10-24T02:22:49Z", "updated_at": "2022-10-24T02:23:46Z", "author_association": "OWNER", "body": "Visit https://latest.datasette.io/login-as-root and then:\r\n\r\nhttps://latest.datasette.io/\r\n\r\n\"image\"\r\n\r\nhttps://latest.datasette.io/_internal/columns\r\n\r\n\"image\"\r\n\r\nhttps://latest.datasette.io/_internal/columns/_internal,columns,cid\r\n\r\n\"image\"\r\n\r\nhttps://latest.datasette.io/_internal/from_hook\r\n\r\n\"image\"\r\n\r\nThat's all as it should be.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1396948693, "label": "Table/database that is private due to inherited permissions does not show padlock"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1829#issuecomment-1288320411", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1829", "id": 1288320411, "node_id": "IC_kwDOBm6k_c5MyjWb", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-10-24T02:21:19Z", "updated_at": "2022-10-24T02:21:19Z", "author_association": "OWNER", "body": "Updated docs for `check_visibility()`: https://docs.datasette.io/en/latest/internals.html#await-check-visibility-actor-action-none-resource-none-permissions-none", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1396948693, "label": "Table/database that is private due to inherited permissions does not show padlock"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1829#issuecomment-1288308945", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1829", "id": 1288308945, "node_id": "IC_kwDOBm6k_c5MygjR", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-10-24T02:07:50Z", "updated_at": "2022-10-24T02:07:50Z", "author_association": "OWNER", "body": "Useful test: if you sign in as root to https://latest.datasette.io/_internal/columns/_internal,columns,database_name you can see there's no padlock icon on that page or on https://latest.datasette.io/_internal/columns - fixing this bug should fix that.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1396948693, "label": "Table/database that is private due to inherited permissions does not show padlock"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1829#issuecomment-1278302478", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1829", "id": 1278302478, "node_id": "IC_kwDOBm6k_c5MMVkO", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-10-14T00:06:19Z", "updated_at": "2022-10-14T00:06:19Z", "author_association": "OWNER", "body": "I'll finish this in a PR:\r\n- https://github.com/simonw/datasette/pull/1842", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1396948693, "label": "Table/database that is private due to inherited permissions does not show padlock"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1829#issuecomment-1278300241", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1829", "id": 1278300241, "node_id": "IC_kwDOBm6k_c5MMVBR", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-10-14T00:03:52Z", "updated_at": "2022-10-14T00:04:28Z", "author_association": "OWNER", "body": "Here's what I've got so far:\r\n```diff\r\ndiff --git a/datasette/app.py b/datasette/app.py\r\nindex 5fa4955c..df9eae49 100644\r\n--- a/datasette/app.py\r\n+++ b/datasette/app.py\r\n@@ -1,5 +1,5 @@\r\n import asyncio\r\n-from typing import Sequence, Union, Tuple\r\n+from typing import Sequence, Union, Tuple, Optional\r\n import asgi_csrf\r\n import collections\r\n import datetime\r\n@@ -707,7 +707,7 @@ class Datasette:\r\n \r\n Raises datasette.Forbidden() if any of the checks fail\r\n \"\"\"\r\n- assert actor is None or isinstance(actor, dict)\r\n+ assert actor is None or isinstance(actor, dict), \"actor must be None or a dict\"\r\n for permission in permissions:\r\n if isinstance(permission, str):\r\n action = permission\r\n@@ -732,23 +732,34 @@ class Datasette:\r\n else:\r\n raise Forbidden(action)\r\n \r\n- async def check_visibility(self, actor, action, resource):\r\n+ async def check_visibility(\r\n+ self,\r\n+ actor: dict,\r\n+ action: Optional[str] = None,\r\n+ resource: Optional[str] = None,\r\n+ permissions: Optional[\r\n+ Sequence[Union[Tuple[str, Union[str, Tuple[str, str]]], str]]\r\n+ ] = None,\r\n+ ):\r\n \"\"\"Returns (visible, private) - visible = can you see it, private = can others see it too\"\"\"\r\n- visible = await self.permission_allowed(\r\n- actor,\r\n- action,\r\n- resource=resource,\r\n- default=True,\r\n- )\r\n- if not visible:\r\n+ if permissions:\r\n+ assert (\r\n+ not action and not resource\r\n+ ), \"Can't use action= or resource= with permissions=\"\r\n+ else:\r\n+ permissions = [(action, resource)]\r\n+ try:\r\n+ await self.ensure_permissions(actor, permissions)\r\n+ except Forbidden:\r\n return False, False\r\n- private = not await self.permission_allowed(\r\n- None,\r\n- action,\r\n- resource=resource,\r\n- default=True,\r\n- )\r\n- return visible, private\r\n+ # User can see it, but can the anonymous user see it?\r\n+ try:\r\n+ await self.ensure_permissions(None, permissions)\r\n+ except Forbidden:\r\n+ # It's visible but private\r\n+ return True, True\r\n+ # It's visible to everyone\r\n+ return True, False\r\n \r\n async def execute(\r\n self,\r\ndiff --git a/datasette/views/table.py b/datasette/views/table.py\r\nindex 60c092f9..f73b0957 100644\r\n--- a/datasette/views/table.py\r\n+++ b/datasette/views/table.py\r\n@@ -28,7 +28,7 @@ from datasette.utils import (\r\n urlsafe_components,\r\n value_as_boolean,\r\n )\r\n-from datasette.utils.asgi import BadRequest, NotFound\r\n+from datasette.utils.asgi import BadRequest, Forbidden, NotFound\r\n from datasette.filters import Filters\r\n from .base import DataView, DatasetteError, ureg\r\n from .database import QueryView\r\n@@ -213,18 +213,16 @@ class TableView(DataView):\r\n raise NotFound(f\"Table not found: {table_name}\")\r\n \r\n # Ensure user has permission to view this table\r\n- await self.ds.ensure_permissions(\r\n+ visible, private = await self.ds.check_visibility(\r\n request.actor,\r\n- [\r\n+ permissions=[\r\n (\"view-table\", (database_name, table_name)),\r\n (\"view-database\", database_name),\r\n \"view-instance\",\r\n ],\r\n )\r\n-\r\n- private = not await self.ds.permission_allowed(\r\n- None, \"view-table\", (database_name, table_name), default=True\r\n- )\r\n+ if not visible:\r\n+ raise Forbidden(\"You do not have permission to view this table\")\r\n \r\n # Handle ?_filter_column and redirect, if present\r\n redirect_params = filters_should_redirect(request.args)\r\n```\r\nStill needs tests and a documentation update.\r\n\r\nAlso this fix is currently only applied on the table page - needs to be applied on database, row and query pages too.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1396948693, "label": "Table/database that is private due to inherited permissions does not show padlock"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1829#issuecomment-1278237331", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1829", "id": 1278237331, "node_id": "IC_kwDOBm6k_c5MMFqT", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-10-13T22:17:45Z", "updated_at": "2022-10-13T22:19:22Z", "author_association": "OWNER", "body": "I think `check_visibility` should be changed to optionally accept `permissions=` which is the same list of tuples that can be passed to `ensure_permissions`.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1396948693, "label": "Table/database that is private due to inherited permissions does not show padlock"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1829#issuecomment-1267709546", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1829", "id": 1267709546, "node_id": "IC_kwDOBm6k_c5Lj7Zq", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-10-04T23:19:24Z", "updated_at": "2022-10-04T23:21:07Z", "author_association": "OWNER", "body": "There's also a `check_visibility()` helper which I'm not using in these particular cases but which may be relevant. It's called like this:\r\n\r\nhttps://github.com/simonw/datasette/blob/4218c9cd742b79b1e3cb80878e42b7e39d16ded2/datasette/views/database.py#L65-L77\r\n\r\nAnd is defined here: https://github.com/simonw/datasette/blob/4218c9cd742b79b1e3cb80878e42b7e39d16ded2/datasette/app.py#L694-L710\r\n\r\nIt's actually documented as a public method here: https://docs.datasette.io/en/stable/internals.html#await-check-visibility-actor-action-resource-none\r\n\r\n> This convenience method can be used to answer the question \"should this item be considered private, in that it is visible to me but it is not visible to anonymous users?\"\r\n>\r\n> It returns a tuple of two booleans, `(visible, private)`. `visible` indicates if the actor can see this resource. `private` will be `True` if an anonymous user would not be able to view the resource.\r\n\r\nNote that this documented method cannot actually do the right thing - because it's not being given the multiple permissions that need to be checked in order to completely answer the question.\r\n\r\nSo I probably need to redesign that method a bit.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 1396948693, "label": "Table/database that is private due to inherited permissions does not show padlock"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/1829#issuecomment-1267708232", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/1829", "id": 1267708232, "node_id": "IC_kwDOBm6k_c5Lj7FI", "user": {"value": 9599, "label": "simonw"}, "created_at": "2022-10-04T23:17:36Z", "updated_at": "2022-10-04T23:17:36Z", "author_association": "OWNER", "body": "Here's the relevant code from the table page:\r\n\r\nhttps://github.com/simonw/datasette/blob/4218c9cd742b79b1e3cb80878e42b7e39d16ded2/datasette/views/table.py#L215-L227\r\n\r\nNote how `ensure_permissions()` there takes the table, database and instance into account... but the `private` assignment (used to decide if the padlock should display or not) only considers the `view-table` check.\r\n\r\nHere's the same code for the database page:\r\n\r\nhttps://github.com/simonw/datasette/blob/4218c9cd742b79b1e3cb80878e42b7e39d16ded2/datasette/views/database.py#L139-L141\r\n\r\nAnd for canned query pages:\r\n\r\nhttps://github.com/simonw/datasette/blob/4218c9cd742b79b1e3cb80878e42b7e39d16ded2/datasette/views/database.py#L228-L240\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": 1396948693, "label": "Table/database that is private due to inherited permissions does not show padlock"}, "performed_via_github_app": null}