diff --git a/tests/conftest.py b/tests/conftest.py index 159a282f..376a61b4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -147,8 +147,8 @@ def check_permission_actions_are_documented(): def before(hook_name, hook_impls, kwargs): if hook_name == "permission_allowed": datasette = kwargs["datasette"] - assert kwargs["action"] in datasette.permissions, ( - "'{}' has not been registered with register_permissions()".format( + assert kwargs["action"] in datasette.actions, ( + "'{}' has not been registered with register_actions()".format( kwargs["action"] ) + " (or maybe a test forgot to do await ds.invoke_startup())" diff --git a/tests/fixtures.py b/tests/fixtures.py index e26789a7..48b3653a 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -45,13 +45,13 @@ EXPECTED_PLUGINS = [ "homepage_actions", "menu_links", "permission_allowed", + "permission_resources_sql", "prepare_connection", "prepare_jinja2_environment", "query_actions", "register_actions", "register_facet_classes", "register_magic_parameters", - "register_permissions", "register_routes", "render_cell", "row_actions", diff --git a/tests/plugins/my_plugin.py b/tests/plugins/my_plugin.py index ba26b502..b0a4ce24 100644 --- a/tests/plugins/my_plugin.py +++ b/tests/plugins/my_plugin.py @@ -1,9 +1,9 @@ import asyncio -from datasette import hookimpl, Permission +from datasette import hookimpl from datasette.facets import Facet from datasette import tracer from datasette.permissions import Action -from datasette.resources import DatabaseResource +from datasette.resources import DatabaseResource, InstanceResource from datasette.utils import path_with_added_args from datasette.utils.asgi import asgi_send_json, Response import base64 @@ -474,37 +474,20 @@ def skip_csrf(scope): return scope["path"] == "/skip-csrf" -@hookimpl -def register_permissions(datasette): - extras = datasette.plugin_config("datasette-register-permissions") or {} - permissions = [ - Permission( - name="permission-from-plugin", - abbr="np", - description="New permission added by a plugin", - takes_database=True, - takes_resource=False, - default=False, - ) - ] - if extras: - permissions.extend( - Permission( - name=p["name"], - abbr=p["abbr"], - description=p["description"], - takes_database=p["takes_database"], - takes_resource=p["takes_resource"], - default=p["default"], - ) - for p in extras["permissions"] - ) - return permissions - - @hookimpl def register_actions(datasette): - return [ + extras_old = datasette.plugin_config("datasette-register-permissions") or {} + extras_new = datasette.plugin_config("datasette-register-actions") or {} + + actions = [ + Action( + name="action-from-plugin", + abbr="ap", + description="New action added by a plugin", + takes_parent=True, + takes_child=False, + resource_class=DatabaseResource, + ), Action( name="view-collection", abbr="vc", @@ -514,3 +497,75 @@ def register_actions(datasette): resource_class=DatabaseResource, ) ] + + # Support old-style config for backwards compatibility + if extras_old: + for p in extras_old["permissions"]: + # Map old takes_database/takes_resource to new takes_parent/takes_child + actions.append( + Action( + name=p["name"], + abbr=p["abbr"], + description=p["description"], + takes_parent=p.get("takes_database", False), + takes_child=p.get("takes_resource", False), + resource_class=DatabaseResource if p.get("takes_database") else InstanceResource, + ) + ) + + # Support new-style config + if extras_new: + for a in extras_new["actions"]: + # Map string resource_class to actual class + resource_class_map = { + "InstanceResource": InstanceResource, + "DatabaseResource": DatabaseResource, + } + resource_class = resource_class_map.get(a.get("resource_class", "InstanceResource"), InstanceResource) + + actions.append( + Action( + name=a["name"], + abbr=a["abbr"], + description=a["description"], + takes_parent=a.get("takes_parent", False), + takes_child=a.get("takes_child", False), + resource_class=resource_class, + ) + ) + + return actions + + +@hookimpl +def permission_resources_sql(datasette, actor, action): + from datasette.permissions import PermissionSQL + + # Handle test actions used in test_hook_permission_allowed + if action == "this_is_allowed": + sql = "SELECT NULL AS parent, NULL AS child, 1 AS allow, 'test plugin allows this_is_allowed' AS reason, 'my_plugin' AS source_plugin" + return PermissionSQL(source="my_plugin", sql=sql, params={}) + elif action == "this_is_denied": + sql = "SELECT NULL AS parent, NULL AS child, 0 AS allow, 'test plugin denies this_is_denied' AS reason, 'my_plugin' AS source_plugin" + return PermissionSQL(source="my_plugin", sql=sql, params={}) + elif action == "this_is_allowed_async": + sql = "SELECT NULL AS parent, NULL AS child, 1 AS allow, 'test plugin allows this_is_allowed_async' AS reason, 'my_plugin' AS source_plugin" + return PermissionSQL(source="my_plugin", sql=sql, params={}) + elif action == "this_is_denied_async": + sql = "SELECT NULL AS parent, NULL AS child, 0 AS allow, 'test plugin denies this_is_denied_async' AS reason, 'my_plugin' AS source_plugin" + return PermissionSQL(source="my_plugin", sql=sql, params={}) + elif action == "view-database-download": + # Return rule based on actor's can_download permission + if actor and actor.get("can_download"): + sql = "SELECT NULL AS parent, NULL AS child, 1 AS allow, 'actor has can_download' AS reason, 'my_plugin' AS source_plugin" + else: + return None # No opinion + return PermissionSQL(source="my_plugin", sql=sql, params={}) + elif action in ("insert-row", "create-table", "drop-table", "delete-row", "update-row"): + # Special permissions for latest.datasette.io demos + actor_id = actor.get("id") if actor else None + if actor_id == "todomvc": + sql = f"SELECT NULL AS parent, NULL AS child, 1 AS allow, 'todomvc actor allowed for {action}' AS reason, 'my_plugin' AS source_plugin" + return PermissionSQL(source="my_plugin", sql=sql, params={}) + + return None