{"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636395263", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636395263, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjM5NTI2Mw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-30T22:54:09Z", "updated_at": "2020-05-30T22:54:09Z", "author_association": "OWNER", "body": "Idea: add a `/-/actor.json` special page which JSON dumps out the current `request.scope[\"actor\"]` - so you can easily test how your request has been authenticated.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636393204", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636393204, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjM5MzIwNA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-30T22:29:44Z", "updated_at": "2020-05-30T22:30:15Z", "author_association": "OWNER", "body": "Robust testing of permissions is really important. I should think about utilities I may be able to add to Datasette's unit testing tools that make it as easy as possible to confirm which permission checks were carried out on a specific HTTP request.\r\n\r\nThat way I can set a good example that any Datasette plugin which makes permission checks can follow.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636392850", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636392850, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjM5Mjg1MA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-30T22:25:19Z", "updated_at": "2020-05-30T22:25:19Z", "author_association": "OWNER", "body": "The branch is now usable! Next step: write some experimental plugins that exercise some real authentication use-cases with it.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636391331", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636391331, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjM5MTMzMQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-30T22:08:21Z", "updated_at": "2020-05-30T22:08:21Z", "author_association": "OWNER", "body": "I'm going to add an awaitable utility method to the Datasette class for checking permissions:\r\n\r\n await datasette.permission_allowed(actor, action, resource_type, resource_identifier)\r\n\r\nThe second two arguments will be optional.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636388288", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636388288, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjM4ODI4OA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-30T21:34:50Z", "updated_at": "2020-05-30T21:34:50Z", "author_association": "OWNER", "body": "Debugging permissions is going to be important. Optional tooling that supports the following would be useful:\r\n\r\n- Log every check to `permission_allowed` to the console - optionally with tracebacks showing where in the code the check was made\r\n- Log every check to the https://latest.datasette.io/?_trace=1 output\r\n- A tool that shows you exactly what permissions the current authenticated user/entity has\r\n- A tool showing all available permissions\r\n\r\nThat last one is tricky if permissions are just strings that might be passed to `permission_allowed` - so maybe there needs to be a plugin hook that lets plugins register their permissions, such that they can be introspected later on? A `register_permission_actions()` hook that returns a list of permission action strings (or objects of some sort) perhaps.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636379067", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636379067, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjM3OTA2Nw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-30T20:12:47Z", "updated_at": "2020-05-30T20:40:42Z", "author_association": "OWNER", "body": "I could bake some permission checks into default Datasette, which are all treated as allow by default but can then be locked down by plugins. Maybe the following:\r\n\r\n permission_allowed(request.actor, \"execute-sql\", \"database\", \"name-of-database\")\r\n\r\nChecks that current user can execute arbitrary SQL queries against a specific database (or use the `?_where=` feature). Equivalent to current [allow_sql](https://datasette.readthedocs.io/en/0.43/config.html#allow-sql) setting.\r\n\r\n permission_allowed(request.actor, \"download-database\", \"database\", \"name-of-database\")\r\n\r\nCan the user download the database file? Like [allow_download](https://datasette.readthedocs.io/en/0.43/config.html#allow-download).\r\n\r\nMaybe one for [allow_csv_stream](https://datasette.readthedocs.io/en/0.43/config.html#allow-csv-stream) too.\r\n\r\nHaving a permission check (defaulting to True) on every single \"view\" would be useful:\r\n\r\n- view_index\r\n- view_database\r\n- view_table\r\n- view_row\r\n- view_query\r\n- view_special (for `/-/versions` and so on)\r\n", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636381732", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636381732, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjM4MTczMg==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-30T20:32:11Z", "updated_at": "2020-05-30T20:39:11Z", "author_association": "OWNER", "body": "I started sketching this out in the [authentication](https://github.com/simonw/datasette/tree/authentication) branch. Here's the documentation so far: https://github.com/simonw/datasette/blob/8871c20/docs/plugins.rst#actor_from_requestdatasette-request", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636376209", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636376209, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjM3NjIwOQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-30T19:53:28Z", "updated_at": "2020-05-30T20:09:10Z", "author_association": "OWNER", "body": "I think there are two hooks here:\r\n\r\n`actor_from_request(datasette, request)` - returns `None` or a dictionary.\r\n\r\n- `datasette` is a Datasette instance - useful for things like reading plugin configuration or executing queries\r\n- `request` is a [Request object](https://datasette.readthedocs.io/en/latest/internals.html#request-object) - which means ASGI scope can be accessed as `request.scope`\r\n\r\nA non-None value means the request is authenticated in some way. The shape of that dictionary is entirely undefined.\r\n\r\nThe second hook is for checking permissions. It can look something like this:\r\n\r\n`permission_allowed(actor, action, resource_type, resource_identifier)`\r\n\r\n- `actor` = the dictionary that was returned by `actor_from_scope`\r\n- `action` = a string representing the action to be performed, e.g. `edit-schema`\r\n- `resource_type` = a string representing the type of resource being acted on, e.g. `table`\r\n- `resource_identifier` = a string (or maybe tuple?) representing the specific resource, e.g. the table name\r\n\r\nI don't know if Datasette should provide default implementations of these hooks. It may be that leaving them completely up to plugins is the way to go.\r\n\r\nI think I need to prototype this quickly to start feeling for how well it might work.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636376893", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636376893, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjM3Njg5Mw==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-30T19:57:54Z", "updated_at": "2020-05-30T20:09:05Z", "author_association": "OWNER", "body": "`auth_from_scope(datasette, scope)` needs to be able to return an awaitable which is then awaited - so it can execute database queries.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636376974", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636376974, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjM3Njk3NA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-30T19:58:40Z", "updated_at": "2020-05-30T20:08:59Z", "author_association": "OWNER", "body": "Maybe call that `actor_from_request(datasette, request)` instead.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636378228", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636378228, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjM3ODIyOA==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-30T20:07:25Z", "updated_at": "2020-05-30T20:07:25Z", "author_association": "OWNER", "body": "I like \"actor\" better than \"entity\" to mean \"the user or API key that is authenticated for this request\".\r\n\r\nI'm going to use \"resource\" instead of \"subject\" - updating the design comment again.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636378121", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636378121, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjM3ODEyMQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-30T20:06:47Z", "updated_at": "2020-05-30T20:06:47Z", "author_association": "OWNER", "body": "In AWS IAM world the following terminology is used: https://aws.amazon.com/iam/features/manage-permissions/\r\n\r\n> Permissions are granted to IAM **entities** (users, groups, and roles) [...]\r\n> \r\n> To assign permissions to a user, group, role, or resource, you create a policy that lets you specify:\r\n> \r\n> * **Actions** \u2013 Which AWS service actions you allow. For example, you might allow a user to call the Amazon S3 ListBucket action. Any actions that you don't explicitly allow are denied.\r\n> * **Resources** \u2013 Which AWS resources you allow the action on. For example, what Amazon S3 buckets will you allow the user to perform the ListBucket action on? Users cannot access any resources that you do not explicitly grant permissions to.\r\n> * **Effect** \u2013 Whether to allow or deny access. Because access is denied by default, you typically write policies where the effect is to allow.\r\n> * **Conditions** \u2013 Which conditions must be present for the policy to take effect. For example, you might allow access only to the specific S3 buckets if the user is connecting from a specific IP range or has used multi-factor authentication at login.", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636377755", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636377755, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjM3Nzc1NQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-30T20:04:23Z", "updated_at": "2020-05-30T20:04:23Z", "author_association": "OWNER", "body": "My usage of the term `subject` here to mean \"the thing I am checking I have permission to interact with, e.g. a database table\" may be misleading. https://stackoverflow.com/questions/4989063/what-is-the-meaning-and-difference-between-subject-user-and-principal for example shows that JAAS (Java Authentication and Authorization Service) defines subject as \"The purpose of the Subject is to represent the authenticated user\".", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null} {"html_url": "https://github.com/simonw/datasette/issues/699#issuecomment-636377235", "issue_url": "https://api.github.com/repos/simonw/datasette/issues/699", "id": 636377235, "node_id": "MDEyOklzc3VlQ29tbWVudDYzNjM3NzIzNQ==", "user": {"value": 9599, "label": "simonw"}, "created_at": "2020-05-30T20:00:42Z", "updated_at": "2020-05-30T20:01:35Z", "author_association": "OWNER", "body": "I'm changing `auth` to `actor` and updating the above [design comment](https://github.com/simonw/datasette/issues/699#issuecomment-636376209).", "reactions": "{\"total_count\": 0, \"+1\": 0, \"-1\": 0, \"laugh\": 0, \"hooray\": 0, \"confused\": 0, \"heart\": 0, \"rocket\": 0, \"eyes\": 0}", "issue": {"value": 582526961, "label": "Authentication (and permissions) as a core concept"}, "performed_via_github_app": null}