diff --git a/docs/upgrade-1.0a20.md b/docs/upgrade-1.0a20.md index ec2b9a5a..339ab588 100644 --- a/docs/upgrade-1.0a20.md +++ b/docs/upgrade-1.0a20.md @@ -6,10 +6,10 @@ orphan: true -Datasette 1.0a20 makes some breaking changes to Datasette's permission system. Plugins need to be updated if they use any of the following: +Datasette 1.0a20 makes some breaking changes to Datasette's permission system. Plugins need to be updated if they use **any of the following**: - The `register_permissions()` plugin hook - this should be replaced with `register_actions` -- The `permission_allowed()` plugin hook - this should be upgraded to `permission_resources_sql()`. +- The `permission_allowed()` plugin hook - this should be upgraded to use `permission_resources_sql()`. - The `datasette.permission_allowed()` internal method - this should be replaced with `datasette.allowed()` - Logic that grants access to the `"root"` actor can be removed. @@ -24,47 +24,37 @@ Old code: def register_permissions(datasette): return [ Permission( - name="datasette-pins-write", + name="explain-sql", abbr=None, - description="Can pin, unpin, and re-order pins for datasette-pins", - takes_database=False, + description="Can explain SQL queries", + takes_database=True, takes_resource=False, default=False, ), Permission( - name="datasette-pins-read", + name="annotate-rows", abbr=None, - description="Can read pinned items.", + description="Can annotate rows", + takes_database=True, + takes_resource=True, + default=False, + ), + Permission( + name="view-debug-info", + abbr=None, + description="Can view debug information", takes_database=False, takes_resource=False, default=False, ), ] ``` -The new `Action` does not have a `default=` parameter. For global actions (those that don't apply to specific resources), omit `resource_class`: - -```python -from datasette.permissions import Action - -@hookimpl -def register_actions(datasette): - return [ - Action( - name="datasette-pins-write", - abbr=None, - description="Can pin, unpin, and re-order pins for datasette-pins", - ), - Action( - name="datasette-pins-read", - abbr=None, - description="Can read pinned items.", - ), - ] -``` - -For actions that apply to specific resources (like databases or tables), specify the `resource_class` instead of `takes_parent` and `takes_child`: +The new `Action` does not have a `default=` parameter. + +Here's the equivalent new code: ```python +from datasette import hookimpl from datasette.permissions import Action from datasette.resources import DatabaseResource, TableResource @@ -72,21 +62,26 @@ from datasette.resources import DatabaseResource, TableResource def register_actions(datasette): return [ Action( - name="execute-sql", - abbr="es", - description="Execute SQL queries", - resource_class=DatabaseResource, # Parent-level resource + name="explain-sql", + abbr=None, + description="Explain SQL queries", + resource_class=DatabaseResource, ), Action( - name="insert-row", - abbr="ir", - description="Insert rows", - resource_class=TableResource, # Child-level resource + name="annotate-rows", + abbr=None, + description="Annotate rows", + resource_class=TableResource, + ), + Action( + name="view-debug-info", + abbr=None, + description="View debug information", ), ] ``` -The hierarchy information (whether an action takes parent/child parameters) is now derived from the `Resource` class hierarchy. `Action` has `takes_parent` and `takes_child` properties that are computed based on the `resource_class` and its `parent_class` attribute. +For actions that apply to specific resources (like databases or tables), specify the `resource_class` instead of `takes_parent` and `takes_child`. Note that `view-debug-info` does not specify a `resource_class` because it applies globally. ## permission_allowed() hook is replaced by permission_resources_sql() @@ -110,6 +105,76 @@ A `.deny(reason="")` class method is also available. For more complex permission checks consult the documentation for that plugin hook: +## Using datasette.allowed() to check permissions instead of datasette.permission_allowed() + +The internal method `datasette.permission_allowed()` has been replaced by `datasette.allowed()`. + +The old method looked like this: +```python +can_debug = await datasette.permission_allowed( + request.actor, + "view-debug-info", +) +can_explain_sql = await datasette.permission_allowed( + request.actor, + "explain-sql", + resource="database_name", +) +can_annotate_rows = await datasette.permission_allowed( + request.actor, + "annotate-rows", + resource=(database_name, table_name), +) +``` +Note the confusing design here where `resource` could be either a string or a tuple depending on the permission being checked. + +The new keyword-only design makes this a lot more clear: +```python +from datasette.resources import DatabaseResource, TableResource +can_debug = await datasette.allowed( + actor=request.actor, + action="view-debug-info", +) +can_explain_sql = await datasette.allowed( + actor=request.actor, + action="explain-sql", + resource=DatabaseResource(database_name), +) +can_annotate_rows = await datasette.allowed( + actor=request.actor, + action="annotate-rows", + resource=TableResource(database_name, table_name), +) +``` + +## Root user checks are no longer necessary + +Some plugins would introduce their own custom permission and then ensure the `"root"` actor had access to it using a pattern like this: + +```python +@hookimpl +def register_permissions(datasette): + return [ + Permission( + name="upload-dbs", + abbr=None, + description="Upload SQLite database files", + takes_database=False, + takes_resource=False, + default=False, + ) + ] + + +@hookimpl +def permission_allowed(actor, action): + if action == "upload-dbs" and actor and actor.get("id") == "root": + return True +``` +This is no longer necessary in Datasette 1.0a20 - the `"root"` actor automatically has all permissions when Datasette is started with the `datasette --root` option. + +The `permission_allowed()` hook in this example can be entirely removed. + ## Fixing async with httpx.AsyncClient(app=app) Some older plugins may use the following pattern in their tests, which is no longer supported: @@ -121,5 +186,5 @@ async with httpx.AsyncClient(app=app) as client: The new pattern is to use `ds.client` like this: ```python ds = Datasette([], memory=True) -response = ds.client.get("/path") +response = await ds.client.get("/path") ```