datasette
datasette copied to clipboard
Baked in way of configuring arbitrary permissions in metadata
The "allow" block mechanism can already be used to configure various default permissions. When adding permissions to datasette-tiddlywiki I realized it would be good to be able to configure arbitrary permissions such as edit-tiddlywiki there too.
I keep running into a need for this. Every time I create a new plugin that defines a new permission I wish there was a clean way to grant that permission to new users without installing some other permissions plugin.
I keep shipping plugins that set a special hook just so the root user can try them out.
Current design:
{
"databases": {
"private": {
"allow": {
"id": "*"
}
}
}
}
This can be applied at the instance, database, table or query level within the nested JSON.
https://docs.datasette.io/en/stable/authentication.html#controlling-access-to-specific-databases
It's actually controlling the following permissions:
view-instanceview-databaseview-tableview-query
There's also a special case for allowing SQL queries,at the instance and database level:
{
"databases": {
"mydatabase": {
"allow_sql": {
"id": "root"
}
}
}
}
So the new mechanism needs to extend that to handle all of the other permissions as well.
The simplest design I can think of is this (here illustrated using YAML):
# instance-level permissions - give every logged in user the debug menu:
permissions:
debug-menu:
id: *
databases:
content:
# Allow bob to create-table in the content database
permissions:
create-table:
id: bob
Should I call this key permissions or something else?
Some options:
permissionsperms- shorter to typeallow- I like the word, but might be confusing to change its meaning since we use it already
Also, this is another thing which should live in config.yml rather than being crammed into metadata.yml - but I can fix that when I address:
- #493
Thankfully all of the logic for this already lives in just one place:
https://github.com/simonw/datasette/blob/d7e5e3c9f98d194fdfb12f1ecc60ed5b3afbc464/datasette/default_permissions.py#L23-L59
I'm going to write the documentation for this first.
What if you want to grant insert-row to a user for ALL tables in a database, or even for all tables in all databases?
You should be able to do that by putting that in the root permissions: block. Need to figure out how the implementation will handle that.
Also: there are some permissions like view-instance or debug-menu for which putting them at the database or table or query level doesn't actually make any sense.
Ideally the implementation would spot those on startup and refuse to start the server, with a helpful error message.
First draft of documentation: https://datasette--1938.org.readthedocs.build/en/1938/authentication.html#other-permissions-in-metadata
I may need to consult this file to figure out if the permission that is being checked can act at the database/table/instance level:
https://github.com/simonw/datasette/blob/e539c1c024bc62d88df91d9107cbe37e7f0fe55f/datasette/permissions.py#L1-L19
A bunch of the work for this just landed - in particular the new scheme is now documented (even though it doesn't work yet):
https://docs.datasette.io/en/latest/authentication.html#other-permissions-in-metadata
The implementation for this will go here: https://github.com/simonw/datasette/blob/8bf06a76b51bc9ace7cf72cf0cca8f1da7704ea7/datasette/default_permissions.py#L81-L83
Here's the start of the tests (currently marked as xfail):
https://github.com/simonw/datasette/blob/8bf06a76b51bc9ace7cf72cf0cca8f1da7704ea7/tests/test_permissions.py#L652-L689
The thing I'm stuck on at the moment is how to implement it such that an allow block for create-table at the root of the metadata will be checked correctly.
Maybe the algorithm when _resolve_metadata_permissions_blocks(datasette, actor, action, resource) is called should do this:
- If a root permission block matching that action exists, test with that
- Next, if resource has been passed, check at the database level
- If the resource included a table/query, check at that level too
So everything is keyed off the incoming action name.