Better display of recent permissions checks, refs #2543

This commit is contained in:
Simon Willison 2025-10-26 17:42:10 -07:00
commit 5da3c9f4bd
2 changed files with 65 additions and 56 deletions

View file

@ -134,31 +134,33 @@ debugPost.addEventListener('submit', function(ev) {
{% if filter != "only-yours" %}<a href="?filter=only-yours">Only yours</a>{% else %}<strong>Only yours</strong>{% endif %}
</p>
{% for check in permission_checks %}
<div class="check">
<h2>
<span class="check-action">{{ check.action }}</span>
checked at
<span class="check-when">{{ check.when }}</span>
{% if check.result %}
<span class="check-result check-result-true"></span>
{% elif check.result is none %}
<span class="check-result check-result-no-opinion">none</span>
{% else %}
<span class="check-result check-result-false"></span>
{% endif %}
</h2>
<p><strong>Actor:</strong> {{ check.actor|tojson }}</p>
{% if check.parent %}
<p><strong>Resource:</strong>
{% if check.child %}
{{ check.parent }} / {{ check.child }}
{% else %}
{{ check.parent }}
{% endif %}
</p>
{% endif %}
</div>
{% endfor %}
{% if permission_checks %}
<table class="rows-and-columns permission-checks-table" id="permission-checks-table">
<thead>
<tr>
<th>When</th>
<th>Action</th>
<th>Parent</th>
<th>Child</th>
<th>Actor</th>
<th>Result</th>
</tr>
</thead>
<tbody>
{% for check in permission_checks %}
<tr>
<td><span style="font-size: 0.8em">{{ check.when.split('T', 1)[0] }}</span><br>{{ check.when.split('T', 1)[1].split('+', 1)[0].split('-', 1)[0].split('Z', 1)[0] }}</td>
<td><code>{{ check.action }}</code></td>
<td>{{ check.parent or '—' }}</td>
<td>{{ check.child or '—' }}</td>
<td>{% if check.actor %}<code>{{ check.actor|tojson }}</code>{% else %}<span class="check-actor-anon">anonymous</span>{% endif %}</td>
<td>{% if check.result %}<span class="check-result check-result-true">Allowed</span>{% elif check.result is none %}<span class="check-result check-result-no-opinion">No opinion</span>{% else %}<span class="check-result check-result-false">Denied</span>{% endif %}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% else %}
<p class="no-results">No permission checks have been recorded yet.</p>
{% endif %}
{% endblock %}

View file

@ -398,24 +398,27 @@ async def test_permissions_debug(ds_client, filter_):
assert fragment in response.text
# Should show one failure and one success
soup = Soup(response.text, "html.parser")
check_divs = soup.find_all("div", {"class": "check"})
checks = [
{
"action": div.select_one(".check-action").text,
# True = green tick, False = red cross, None = gray None
"result": (
None
if div.select(".check-result-no-opinion")
else bool(div.select(".check-result-true"))
),
"actor": json.loads(
div.find(
"strong", string=lambda text: text and "Actor" in text
).parent.text.split(": ", 1)[1]
),
}
for div in check_divs
]
table = soup.find("table", {"id": "permission-checks-table"})
rows = table.find("tbody").find_all("tr")
checks = []
for row in rows:
cells = row.find_all("td")
result_cell = cells[5]
if result_cell.select_one(".check-result-true"):
result = True
elif result_cell.select_one(".check-result-false"):
result = False
else:
result = None
actor_code = cells[4].find("code")
actor = json.loads(actor_code.text) if actor_code else None
checks.append(
{
"action": cells[1].text.strip(),
"result": result,
"actor": actor,
}
)
expected_checks = [
{
"action": "permissions-debug",
@ -723,22 +726,26 @@ async def test_actor_restricted_permissions(
},
cookies=cookies,
)
# Build expected_resource to match API behavior:
# - None when no resources
# - Single string when only resource_1
# - List when both resource_1 and resource_2 (JSON serializes tuples as lists)
if resource_1 and resource_2:
expected_resource = [resource_1, resource_2]
elif resource_1:
expected_resource = resource_1
# Response mirrors /-/check JSON structure
if resource_1 is None:
expected_path = "/"
elif resource_2 is None:
expected_path = f"/{resource_1}"
else:
expected_resource = None
expected = {
"actor": actor,
"permission": permission,
"resource": expected_resource,
"result": expected_result,
expected_path = f"/{resource_1}/{resource_2}"
expected_resource = {
"parent": resource_1,
"child": resource_2,
"path": expected_path,
}
expected = {
"action": permission,
"allowed": expected_result,
"resource": expected_resource,
}
if actor.get("id"):
expected["actor_id"] = actor["id"]
assert response.json() == expected