html_url,issue_url,id,node_id,user,user_label,created_at,updated_at,author_association,body,reactions,issue,issue_label,performed_via_github_app
https://github.com/simonw/datasette/issues/1851#issuecomment-1294224185,https://api.github.com/repos/simonw/datasette/issues/1851,1294224185,IC_kwDOBm6k_c5NJEs5,9599,simonw,2022-10-27T23:18:24Z,2022-11-03T23:26:05Z,OWNER,"So new API design is:
```
POST /db/table/-/insert
Authorization: Bearer xxx
Content-Type: application/json
{
""row"": {
""id"": 1,
""name"": ""New record""
}
}
```
Returns:
```
201 Created
{
""row"": [{
""id"": 1,
""name"": ""New record""
}]
}
```","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",1421544654,API to insert a single record into an existing table,
https://github.com/simonw/datasette/issues/1855#issuecomment-1301594495,https://api.github.com/repos/simonw/datasette/issues/1855,1301594495,IC_kwDOBm6k_c5NlMF_,9599,simonw,2022-11-03T03:11:17Z,2022-11-03T03:11:17Z,OWNER,"Maybe the way to do this is through a new standard mechanism on the actor: a set of additional restrictions, e.g.:
```
{
""id"": ""root"",
""_r"": {
""a"": [""ir"", ""ur"", ""dr""],
""d"": {
""fixtures"": [""ir"", ""ur"", ""dr""]
},
""t"": {
""fixtures"": {
""searchable"": [""ir""]
}
}
}
```
`""a""` is ""all permissions"" - these apply to everything.
`""d""` permissions only apply to the specified database
`""t""` permissions only apply to the specified table
The way this works is there's a default [permission_allowed(datasette, actor, action, resource)](https://docs.datasette.io/en/stable/plugin_hooks.html#id25) hook which only consults these, and crucially just says NO if those rules do not match.
In this way it would apply as an extra layer of permission rules over the defaults (which for this `root` instance would all return yes).","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",1423336089,`datasette create-token` ability to create tokens with a reduced set of permissions,
https://github.com/simonw/datasette/issues/1881#issuecomment-1301638918,https://api.github.com/repos/simonw/datasette/issues/1881,1301638918,IC_kwDOBm6k_c5NlW8G,9599,simonw,2022-11-03T04:56:06Z,2022-11-03T04:56:06Z,OWNER,"I've also introduced a new concept of a permission abbreviation, which like the permission name needs to be globally unique.
That's a problem for plugins - they might just be able to guarantee that their permission long-form name is unique among other plugins (through sensible naming conventions) but the thing where they declare a initial-letters-only abbreviation is far more risky.
I think abbreviations are optional - they are provided for core permissions but plugins are advised not to use them.
Also Datasette could check that the installed plugins do not provide conflicting permissions on startup and refuse to start if they do.","{""total_count"": 0, ""+1"": 0, ""-1"": 0, ""laugh"": 0, ""hooray"": 0, ""confused"": 0, ""heart"": 0, ""rocket"": 0, ""eyes"": 0}",1434094365,Tool for simulating permission checks against actors,
https://github.com/simonw/datasette/issues/1881#issuecomment-1301635340,https://api.github.com/repos/simonw/datasette/issues/1881,1301635340,IC_kwDOBm6k_c5NlWEM,9599,simonw,2022-11-03T04:46:41Z,2022-11-03T04:46:41Z,OWNER,"Built this prototype:
![prototype](https://user-images.githubusercontent.com/9599/199649219-f146e43b-bfb5-45e6-9777-956f21a79887.gif)
In building it I realized I needed to know which permissions took a table, a database, both or neither. So I had to bake that into the code.
Here's the prototype so far (which includes a prototype of the logic for the `_r` field on actor, see #1855):
```diff
diff --git a/datasette/default_permissions.py b/datasette/default_permissions.py
index 32b0c758..f68aa38f 100644
--- a/datasette/default_permissions.py
+++ b/datasette/default_permissions.py
@@ -6,8 +6,8 @@ import json
import time
-@hookimpl(tryfirst=True)
-def permission_allowed(datasette, actor, action, resource):
+@hookimpl(tryfirst=True, specname=""permission_allowed"")
+def permission_allowed_default(datasette, actor, action, resource):
async def inner():
if action in (
""permissions-debug"",
@@ -57,6 +57,44 @@ def permission_allowed(datasette, actor, action, resource):
return inner
+@hookimpl(specname=""permission_allowed"")
+def permission_allowed_actor_restrictions(actor, action, resource):
+ if actor is None:
+ return None
+ _r = actor.get(""_r"")
+ if not _r:
+ # No restrictions, so we have no opinion
+ return None
+ action_initials = """".join([word[0] for word in action.split(""-"")])
+ # If _r is defined then we use those to further restrict the actor
+ # 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
+ all_allowed = _r.get(""a"")
+ if all_allowed is not None:
+ assert isinstance(all_allowed, list)
+ if action_initials in all_allowed:
+ return None
+ # How about for the current database?
+ if action in (""view-database"", ""view-database-download"", ""execute-sql""):
+ database_allowed = _r.get(""d"", {}).get(resource)
+ if database_allowed is not None:
+ assert isinstance(database_allowed, list)
+ if action_initials in database_allowed:
+ return None
+ # Or the current table? That's any time the resource is (database, table)
+ if not isinstance(resource, str) and len(resource) == 2:
+ database, table = resource
+ table_allowed = _r.get(""t"", {}).get(database, {}).get(table)
+ # TODO: What should this do for canned queries?
+ if table_allowed is not None:
+ assert isinstance(table_allowed, list)
+ if action_initials in table_allowed:
+ return None
+ # This action is not specifically allowed, so reject it
+ return False
+
+
@hookimpl
def actor_from_request(datasette, request):
prefix = ""dstok_""
diff --git a/datasette/templates/allow_debug.html b/datasette/templates/allow_debug.html
index 0f1b30f0..ae43f0f5 100644
--- a/datasette/templates/allow_debug.html
+++ b/datasette/templates/allow_debug.html
@@ -35,7 +35,7 @@ p.message-warning {