mirror of
https://github.com/simonw/datasette.git
synced 2025-12-10 16:51:24 +01:00
Show multiple permission reasons as JSON arrays, refs #2531
- Modified /-/allowed to show all reasons that grant access to a resource - Changed from MAX(reason) to json_group_array() in SQL to collect all reasons - Reasons now displayed as JSON arrays in both HTML and JSON responses - Only show Reason column to users with permissions-debug permission - Removed obsolete "Source Plugin" column from /-/rules interface - Updated allowed_resources_with_reasons() to parse and return reason lists - Fixed alert() on /-/allowed by replacing with disabled input state
This commit is contained in:
parent
ee4fcff5c0
commit
d769e97ab8
6 changed files with 40 additions and 21 deletions
|
|
@ -1233,7 +1233,22 @@ class Datasette:
|
|||
resources = []
|
||||
for row in result.rows:
|
||||
resource = self.resource_for_action(action, parent=row[0], child=row[1])
|
||||
reason = row[2]
|
||||
reason_json = row[2]
|
||||
|
||||
# Parse JSON array of reasons and filter out nulls
|
||||
try:
|
||||
import json
|
||||
|
||||
reasons_array = (
|
||||
json.loads(reason_json) if isinstance(reason_json, str) else []
|
||||
)
|
||||
reasons_filtered = [r for r in reasons_array if r is not None]
|
||||
# Store as list for multiple reasons, or keep empty list
|
||||
reason = reasons_filtered
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
# Fallback for backward compatibility
|
||||
reason = [reason_json] if reason_json else []
|
||||
|
||||
resources.append(AllowedResource(resource=resource, reason=reason))
|
||||
|
||||
return resources
|
||||
|
|
|
|||
|
|
@ -177,7 +177,12 @@ function displayResults(data) {
|
|||
html += `<td>${escapeHtml(item.parent || '—')}</td>`;
|
||||
html += `<td>${escapeHtml(item.child || '—')}</td>`;
|
||||
if (hasDebugPermission) {
|
||||
html += `<td>${escapeHtml(item.reason || '—')}</td>`;
|
||||
// Display reason as JSON array
|
||||
let reasonHtml = '—';
|
||||
if (item.reason && Array.isArray(item.reason)) {
|
||||
reasonHtml = `<code>${escapeHtml(JSON.stringify(item.reason))}</code>`;
|
||||
}
|
||||
html += `<td>${reasonHtml}</td>`;
|
||||
}
|
||||
html += '</tr>';
|
||||
}
|
||||
|
|
|
|||
|
|
@ -154,7 +154,6 @@ function displayResults(data) {
|
|||
html += '<th>Parent</th>';
|
||||
html += '<th>Child</th>';
|
||||
html += '<th>Reason</th>';
|
||||
html += '<th>Source Plugin</th>';
|
||||
html += '</tr></thead>';
|
||||
html += '<tbody>';
|
||||
|
||||
|
|
@ -170,7 +169,6 @@ function displayResults(data) {
|
|||
html += `<td>${escapeHtml(item.parent || '—')}</td>`;
|
||||
html += `<td>${escapeHtml(item.child || '—')}</td>`;
|
||||
html += `<td>${escapeHtml(item.reason || '—')}</td>`;
|
||||
html += `<td>${escapeHtml(item.source_plugin || '—')}</td>`;
|
||||
html += '</tr>';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -261,8 +261,8 @@ async def _build_single_action_sql(
|
|||
" SELECT b.parent, b.child,",
|
||||
" MAX(CASE WHEN ar.allow = 0 THEN 1 ELSE 0 END) AS any_deny,",
|
||||
" MAX(CASE WHEN ar.allow = 1 THEN 1 ELSE 0 END) AS any_allow,",
|
||||
" MAX(CASE WHEN ar.allow = 0 THEN ar.reason ELSE NULL END) AS deny_reason,",
|
||||
" MAX(CASE WHEN ar.allow = 1 THEN ar.reason ELSE NULL END) AS allow_reason",
|
||||
" json_group_array(CASE WHEN ar.allow = 0 THEN ar.reason END) AS deny_reasons,",
|
||||
" json_group_array(CASE WHEN ar.allow = 1 THEN ar.reason END) AS allow_reasons",
|
||||
" FROM base b",
|
||||
" LEFT JOIN all_rules ar ON ar.parent = b.parent AND ar.child = b.child",
|
||||
" GROUP BY b.parent, b.child",
|
||||
|
|
@ -271,8 +271,8 @@ async def _build_single_action_sql(
|
|||
" SELECT b.parent, b.child,",
|
||||
" MAX(CASE WHEN ar.allow = 0 THEN 1 ELSE 0 END) AS any_deny,",
|
||||
" MAX(CASE WHEN ar.allow = 1 THEN 1 ELSE 0 END) AS any_allow,",
|
||||
" MAX(CASE WHEN ar.allow = 0 THEN ar.reason ELSE NULL END) AS deny_reason,",
|
||||
" MAX(CASE WHEN ar.allow = 1 THEN ar.reason ELSE NULL END) AS allow_reason",
|
||||
" json_group_array(CASE WHEN ar.allow = 0 THEN ar.reason END) AS deny_reasons,",
|
||||
" json_group_array(CASE WHEN ar.allow = 1 THEN ar.reason END) AS allow_reasons",
|
||||
" FROM base b",
|
||||
" LEFT JOIN all_rules ar ON ar.parent = b.parent AND ar.child IS NULL",
|
||||
" GROUP BY b.parent, b.child",
|
||||
|
|
@ -281,8 +281,8 @@ async def _build_single_action_sql(
|
|||
" SELECT b.parent, b.child,",
|
||||
" MAX(CASE WHEN ar.allow = 0 THEN 1 ELSE 0 END) AS any_deny,",
|
||||
" MAX(CASE WHEN ar.allow = 1 THEN 1 ELSE 0 END) AS any_allow,",
|
||||
" MAX(CASE WHEN ar.allow = 0 THEN ar.reason ELSE NULL END) AS deny_reason,",
|
||||
" MAX(CASE WHEN ar.allow = 1 THEN ar.reason ELSE NULL END) AS allow_reason",
|
||||
" json_group_array(CASE WHEN ar.allow = 0 THEN ar.reason END) AS deny_reasons,",
|
||||
" json_group_array(CASE WHEN ar.allow = 1 THEN ar.reason END) AS allow_reasons",
|
||||
" FROM base b",
|
||||
" LEFT JOIN all_rules ar ON ar.parent IS NULL AND ar.child IS NULL",
|
||||
" GROUP BY b.parent, b.child",
|
||||
|
|
@ -363,13 +363,13 @@ async def _build_single_action_sql(
|
|||
" ELSE 0",
|
||||
" END AS is_allowed,",
|
||||
" CASE",
|
||||
" WHEN cl.any_deny = 1 THEN cl.deny_reason",
|
||||
" WHEN cl.any_allow = 1 THEN cl.allow_reason",
|
||||
" WHEN pl.any_deny = 1 THEN pl.deny_reason",
|
||||
" WHEN pl.any_allow = 1 THEN pl.allow_reason",
|
||||
" WHEN gl.any_deny = 1 THEN gl.deny_reason",
|
||||
" WHEN gl.any_allow = 1 THEN gl.allow_reason",
|
||||
" ELSE 'default deny'",
|
||||
" WHEN cl.any_deny = 1 THEN cl.deny_reasons",
|
||||
" WHEN cl.any_allow = 1 THEN cl.allow_reasons",
|
||||
" WHEN pl.any_deny = 1 THEN pl.deny_reasons",
|
||||
" WHEN pl.any_allow = 1 THEN pl.allow_reasons",
|
||||
" WHEN gl.any_deny = 1 THEN gl.deny_reasons",
|
||||
" WHEN gl.any_allow = 1 THEN gl.allow_reasons",
|
||||
" ELSE '[]'",
|
||||
" END AS reason",
|
||||
]
|
||||
)
|
||||
|
|
|
|||
|
|
@ -327,7 +327,7 @@ class AllowedResourcesView(BaseView):
|
|||
"resource": resource_path,
|
||||
}
|
||||
|
||||
# Add reason if we have it
|
||||
# Add reason if we have it (it's already a list from allowed_resources_with_reasons)
|
||||
if reason is not None:
|
||||
row["reason"] = reason
|
||||
|
||||
|
|
|
|||
|
|
@ -163,10 +163,11 @@ async def test_allowed_resources_with_reasons(test_ds):
|
|||
# Check we can access both resource and reason
|
||||
for item in allowed:
|
||||
assert isinstance(item.resource, TableResource)
|
||||
assert isinstance(item.reason, str)
|
||||
assert isinstance(item.reason, list)
|
||||
if item.resource.parent == "analytics":
|
||||
# Should mention parent-level reason
|
||||
assert "analyst access" in item.reason.lower()
|
||||
# Should mention parent-level reason in at least one of the reasons
|
||||
reasons_text = " ".join(item.reason).lower()
|
||||
assert "analyst access" in reasons_text
|
||||
|
||||
finally:
|
||||
pm.unregister(plugin, name="test_plugin")
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue