Migrate views from ds.permissions to ds.actions, refs #2528

Updates all permission debugging views to use the new ds.actions dict
instead of the old ds.permissions dict. Changes include:

- Replace all ds.permissions references with ds.actions
- Update field references: takes_database/takes_resource → takes_parent/takes_child
- Remove default field from permission display
- Rename sorted_permissions to sorted_actions in templates
- Remove source_plugin from SQL queries and responses
- Update test expectations to not check for source_plugin field

This aligns the views with the new Action dataclass structure.
This commit is contained in:
Simon Willison 2025-10-25 08:53:03 -07:00
commit 5c6b76f2f0
5 changed files with 15 additions and 30 deletions

View file

@ -110,7 +110,7 @@
<label for="action">Action (permission name):</label> <label for="action">Action (permission name):</label>
<select id="action" name="action" required> <select id="action" name="action" required>
<option value="">Select an action...</option> <option value="">Select an action...</option>
{% for permission_name in sorted_permissions %} {% for permission_name in sorted_actions %}
<option value="{{ permission_name }}">{{ permission_name }}</option> <option value="{{ permission_name }}">{{ permission_name }}</option>
{% endfor %} {% endfor %}
</select> </select>

View file

@ -26,7 +26,7 @@
<label for="action">Action (permission name):</label> <label for="action">Action (permission name):</label>
<select id="action" name="action" required> <select id="action" name="action" required>
<option value="">Select an action...</option> <option value="">Select an action...</option>
{% for permission_name in sorted_permissions %} {% for permission_name in sorted_actions %}
<option value="{{ permission_name }}">{{ permission_name }}</option> <option value="{{ permission_name }}">{{ permission_name }}</option>
{% endfor %} {% endfor %}
</select> </select>

View file

@ -57,7 +57,7 @@ textarea {
<p><label for="permission" style="display:block">Permission</label> <p><label for="permission" style="display:block">Permission</label>
<select name="permission" id="permission"> <select name="permission" id="permission">
{% for permission in permissions %} {% for permission in permissions %}
<option value="{{ permission.name }}">{{ permission.name }} (default {{ permission.default }})</option> <option value="{{ permission.name }}">{{ permission.name }}</option>
{% endfor %} {% endfor %}
</select> </select>
<p><label for="resource_1">Database name</label><input type="text" id="resource_1" name="resource_1"></p> <p><label for="resource_1">Database name</label><input type="text" id="resource_1" name="resource_1"></p>
@ -131,9 +131,6 @@ debugPost.addEventListener('submit', function(ev) {
{% else %} {% else %}
<span class="check-result check-result-false"></span> <span class="check-result check-result-false"></span>
{% endif %} {% endif %}
{% if check.used_default %}
<span class="check-used-default">(used default)</span>
{% endif %}
</h2> </h2>
<p><strong>Actor:</strong> {{ check.actor|tojson }}</p> <p><strong>Actor:</strong> {{ check.actor|tojson }}</p>
{% if check.resource %} {% if check.resource %}

View file

@ -144,11 +144,10 @@ class PermissionsDebugView(BaseView):
"name": p.name, "name": p.name,
"abbr": p.abbr, "abbr": p.abbr,
"description": p.description, "description": p.description,
"takes_database": p.takes_database, "takes_parent": p.takes_parent,
"takes_resource": p.takes_resource, "takes_child": p.takes_child,
"default": p.default,
} }
for p in self.ds.permissions.values() for p in self.ds.actions.values()
], ],
}, },
) )
@ -182,7 +181,6 @@ class PermissionsDebugView(BaseView):
"permission": permission, "permission": permission,
"resource": resource_for_response, "resource": resource_for_response,
"result": result, "result": result,
"default": self.ds.permissions[permission].default,
} }
) )
@ -232,7 +230,7 @@ class AllowedResourcesView(BaseView):
action = request.args.get("action") action = request.args.get("action")
if not action: if not action:
return Response.json({"error": "action parameter is required"}, status=400) return Response.json({"error": "action parameter is required"}, status=400)
if action not in self.ds.permissions: if action not in self.ds.actions:
return Response.json({"error": f"Unknown action: {action}"}, status=404) return Response.json({"error": f"Unknown action: {action}"}, status=404)
if action not in self.CANDIDATE_SQL: if action not in self.CANDIDATE_SQL:
return Response.json( return Response.json(
@ -313,8 +311,6 @@ class AllowedResourcesView(BaseView):
# Add debug fields if available # Add debug fields if available
if has_debug_permission and hasattr(resource, "_reason"): if has_debug_permission and hasattr(resource, "_reason"):
row["reason"] = resource._reason row["reason"] = resource._reason
if has_debug_permission and hasattr(resource, "_source_plugin"):
row["source_plugin"] = resource._source_plugin
allowed_rows.append(row) allowed_rows.append(row)
@ -380,7 +376,7 @@ class PermissionRulesView(BaseView):
["debug_rules.html"], ["debug_rules.html"],
request, request,
{ {
"sorted_permissions": sorted(self.ds.permissions.keys()), "sorted_actions": sorted(self.ds.actions.keys()),
}, },
) )
@ -388,7 +384,7 @@ class PermissionRulesView(BaseView):
action = request.args.get("action") action = request.args.get("action")
if not action: if not action:
return Response.json({"error": "action parameter is required"}, status=400) return Response.json({"error": "action parameter is required"}, status=400)
if action not in self.ds.permissions: if action not in self.ds.actions:
return Response.json({"error": f"Unknown action: {action}"}, status=404) return Response.json({"error": f"Unknown action: {action}"}, status=404)
actor = request.actor if isinstance(request.actor, dict) else None actor = request.actor if isinstance(request.actor, dict) else None
@ -431,7 +427,7 @@ class PermissionRulesView(BaseView):
WITH rules AS ( WITH rules AS (
{union_sql} {union_sql}
) )
SELECT parent, child, allow, reason, source_plugin SELECT parent, child, allow, reason
FROM rules FROM rules
ORDER BY allow DESC, (parent IS NOT NULL), parent, child ORDER BY allow DESC, (parent IS NOT NULL), parent, child
LIMIT :limit OFFSET :offset LIMIT :limit OFFSET :offset
@ -450,7 +446,6 @@ class PermissionRulesView(BaseView):
"resource": _resource_path(parent, child), "resource": _resource_path(parent, child),
"allow": row["allow"], "allow": row["allow"],
"reason": row["reason"], "reason": row["reason"],
"source_plugin": row["source_plugin"],
} }
) )
@ -505,7 +500,7 @@ class PermissionCheckView(BaseView):
["debug_check.html"], ["debug_check.html"],
request, request,
{ {
"sorted_permissions": sorted(self.ds.permissions.keys()), "sorted_actions": sorted(self.ds.actions.keys()),
}, },
) )
@ -513,7 +508,7 @@ class PermissionCheckView(BaseView):
action = request.args.get("action") action = request.args.get("action")
if not action: if not action:
return Response.json({"error": "action parameter is required"}, status=400) return Response.json({"error": "action parameter is required"}, status=400)
if action not in self.ds.permissions: if action not in self.ds.actions:
return Response.json({"error": f"Unknown action: {action}"}, status=404) return Response.json({"error": f"Unknown action: {action}"}, status=404)
parent = request.args.get("parent") parent = request.args.get("parent")
@ -564,12 +559,10 @@ class PermissionCheckView(BaseView):
response["actor_id"] = request.actor["id"] response["actor_id"] = request.actor["id"]
if info is not None: if info is not None:
response["used_default"] = info.get("used_default")
response["depth"] = info.get("depth") response["depth"] = info.get("depth")
# Only include sensitive fields if user has permissions-debug # Only include sensitive fields if user has permissions-debug
if has_debug_permission: if has_debug_permission:
response["reason"] = info.get("reason") response["reason"] = info.get("reason")
response["source_plugin"] = info.get("source_plugin")
return Response.json(response) return Response.json(response)
@ -687,16 +680,12 @@ class CreateTokenView(BaseView):
) )
return { return {
"actor": request.actor, "actor": request.actor,
"all_permissions": self.ds.permissions.keys(), "all_permissions": self.ds.actions.keys(),
"database_permissions": [ "database_permissions": [
key key for key, value in self.ds.actions.items() if value.takes_database
for key, value in self.ds.permissions.items()
if value.takes_database
], ],
"resource_permissions": [ "resource_permissions": [
key key for key, value in self.ds.actions.items() if value.takes_resource
for key, value in self.ds.permissions.items()
if value.takes_resource
], ],
"database_with_tables": database_with_tables, "database_with_tables": database_with_tables,
} }

View file

@ -247,7 +247,6 @@ async def test_rules_json_response_structure(ds_with_permissions):
assert "resource" in item assert "resource" in item
assert "allow" in item assert "allow" in item
assert "reason" in item assert "reason" in item
assert "source_plugin" in item
@pytest.mark.asyncio @pytest.mark.asyncio