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 = []
|
resources = []
|
||||||
for row in result.rows:
|
for row in result.rows:
|
||||||
resource = self.resource_for_action(action, parent=row[0], child=row[1])
|
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))
|
resources.append(AllowedResource(resource=resource, reason=reason))
|
||||||
|
|
||||||
return resources
|
return resources
|
||||||
|
|
|
||||||
|
|
@ -177,7 +177,12 @@ function displayResults(data) {
|
||||||
html += `<td>${escapeHtml(item.parent || '—')}</td>`;
|
html += `<td>${escapeHtml(item.parent || '—')}</td>`;
|
||||||
html += `<td>${escapeHtml(item.child || '—')}</td>`;
|
html += `<td>${escapeHtml(item.child || '—')}</td>`;
|
||||||
if (hasDebugPermission) {
|
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>';
|
html += '</tr>';
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -154,7 +154,6 @@ function displayResults(data) {
|
||||||
html += '<th>Parent</th>';
|
html += '<th>Parent</th>';
|
||||||
html += '<th>Child</th>';
|
html += '<th>Child</th>';
|
||||||
html += '<th>Reason</th>';
|
html += '<th>Reason</th>';
|
||||||
html += '<th>Source Plugin</th>';
|
|
||||||
html += '</tr></thead>';
|
html += '</tr></thead>';
|
||||||
html += '<tbody>';
|
html += '<tbody>';
|
||||||
|
|
||||||
|
|
@ -170,7 +169,6 @@ function displayResults(data) {
|
||||||
html += `<td>${escapeHtml(item.parent || '—')}</td>`;
|
html += `<td>${escapeHtml(item.parent || '—')}</td>`;
|
||||||
html += `<td>${escapeHtml(item.child || '—')}</td>`;
|
html += `<td>${escapeHtml(item.child || '—')}</td>`;
|
||||||
html += `<td>${escapeHtml(item.reason || '—')}</td>`;
|
html += `<td>${escapeHtml(item.reason || '—')}</td>`;
|
||||||
html += `<td>${escapeHtml(item.source_plugin || '—')}</td>`;
|
|
||||||
html += '</tr>';
|
html += '</tr>';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -261,8 +261,8 @@ async def _build_single_action_sql(
|
||||||
" SELECT b.parent, b.child,",
|
" SELECT b.parent, b.child,",
|
||||||
" MAX(CASE WHEN ar.allow = 0 THEN 1 ELSE 0 END) AS any_deny,",
|
" 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 = 1 THEN 1 ELSE 0 END) AS any_allow,",
|
||||||
" MAX(CASE WHEN ar.allow = 0 THEN ar.reason ELSE NULL END) AS deny_reason,",
|
" json_group_array(CASE WHEN ar.allow = 0 THEN ar.reason END) AS deny_reasons,",
|
||||||
" MAX(CASE WHEN ar.allow = 1 THEN ar.reason ELSE NULL END) AS allow_reason",
|
" json_group_array(CASE WHEN ar.allow = 1 THEN ar.reason END) AS allow_reasons",
|
||||||
" FROM base b",
|
" FROM base b",
|
||||||
" LEFT JOIN all_rules ar ON ar.parent = b.parent AND ar.child = b.child",
|
" LEFT JOIN all_rules ar ON ar.parent = b.parent AND ar.child = b.child",
|
||||||
" GROUP BY b.parent, b.child",
|
" GROUP BY b.parent, b.child",
|
||||||
|
|
@ -271,8 +271,8 @@ async def _build_single_action_sql(
|
||||||
" SELECT b.parent, b.child,",
|
" SELECT b.parent, b.child,",
|
||||||
" MAX(CASE WHEN ar.allow = 0 THEN 1 ELSE 0 END) AS any_deny,",
|
" 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 = 1 THEN 1 ELSE 0 END) AS any_allow,",
|
||||||
" MAX(CASE WHEN ar.allow = 0 THEN ar.reason ELSE NULL END) AS deny_reason,",
|
" json_group_array(CASE WHEN ar.allow = 0 THEN ar.reason END) AS deny_reasons,",
|
||||||
" MAX(CASE WHEN ar.allow = 1 THEN ar.reason ELSE NULL END) AS allow_reason",
|
" json_group_array(CASE WHEN ar.allow = 1 THEN ar.reason END) AS allow_reasons",
|
||||||
" FROM base b",
|
" FROM base b",
|
||||||
" LEFT JOIN all_rules ar ON ar.parent = b.parent AND ar.child IS NULL",
|
" LEFT JOIN all_rules ar ON ar.parent = b.parent AND ar.child IS NULL",
|
||||||
" GROUP BY b.parent, b.child",
|
" GROUP BY b.parent, b.child",
|
||||||
|
|
@ -281,8 +281,8 @@ async def _build_single_action_sql(
|
||||||
" SELECT b.parent, b.child,",
|
" SELECT b.parent, b.child,",
|
||||||
" MAX(CASE WHEN ar.allow = 0 THEN 1 ELSE 0 END) AS any_deny,",
|
" 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 = 1 THEN 1 ELSE 0 END) AS any_allow,",
|
||||||
" MAX(CASE WHEN ar.allow = 0 THEN ar.reason ELSE NULL END) AS deny_reason,",
|
" json_group_array(CASE WHEN ar.allow = 0 THEN ar.reason END) AS deny_reasons,",
|
||||||
" MAX(CASE WHEN ar.allow = 1 THEN ar.reason ELSE NULL END) AS allow_reason",
|
" json_group_array(CASE WHEN ar.allow = 1 THEN ar.reason END) AS allow_reasons",
|
||||||
" FROM base b",
|
" FROM base b",
|
||||||
" LEFT JOIN all_rules ar ON ar.parent IS NULL AND ar.child IS NULL",
|
" LEFT JOIN all_rules ar ON ar.parent IS NULL AND ar.child IS NULL",
|
||||||
" GROUP BY b.parent, b.child",
|
" GROUP BY b.parent, b.child",
|
||||||
|
|
@ -363,13 +363,13 @@ async def _build_single_action_sql(
|
||||||
" ELSE 0",
|
" ELSE 0",
|
||||||
" END AS is_allowed,",
|
" END AS is_allowed,",
|
||||||
" CASE",
|
" CASE",
|
||||||
" WHEN cl.any_deny = 1 THEN cl.deny_reason",
|
" WHEN cl.any_deny = 1 THEN cl.deny_reasons",
|
||||||
" WHEN cl.any_allow = 1 THEN cl.allow_reason",
|
" WHEN cl.any_allow = 1 THEN cl.allow_reasons",
|
||||||
" WHEN pl.any_deny = 1 THEN pl.deny_reason",
|
" WHEN pl.any_deny = 1 THEN pl.deny_reasons",
|
||||||
" WHEN pl.any_allow = 1 THEN pl.allow_reason",
|
" WHEN pl.any_allow = 1 THEN pl.allow_reasons",
|
||||||
" WHEN gl.any_deny = 1 THEN gl.deny_reason",
|
" WHEN gl.any_deny = 1 THEN gl.deny_reasons",
|
||||||
" WHEN gl.any_allow = 1 THEN gl.allow_reason",
|
" WHEN gl.any_allow = 1 THEN gl.allow_reasons",
|
||||||
" ELSE 'default deny'",
|
" ELSE '[]'",
|
||||||
" END AS reason",
|
" END AS reason",
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -327,7 +327,7 @@ class AllowedResourcesView(BaseView):
|
||||||
"resource": resource_path,
|
"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:
|
if reason is not None:
|
||||||
row["reason"] = reason
|
row["reason"] = reason
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -163,10 +163,11 @@ async def test_allowed_resources_with_reasons(test_ds):
|
||||||
# Check we can access both resource and reason
|
# Check we can access both resource and reason
|
||||||
for item in allowed:
|
for item in allowed:
|
||||||
assert isinstance(item.resource, TableResource)
|
assert isinstance(item.resource, TableResource)
|
||||||
assert isinstance(item.reason, str)
|
assert isinstance(item.reason, list)
|
||||||
if item.resource.parent == "analytics":
|
if item.resource.parent == "analytics":
|
||||||
# Should mention parent-level reason
|
# Should mention parent-level reason in at least one of the reasons
|
||||||
assert "analyst access" in item.reason.lower()
|
reasons_text = " ".join(item.reason).lower()
|
||||||
|
assert "analyst access" in reasons_text
|
||||||
|
|
||||||
finally:
|
finally:
|
||||||
pm.unregister(plugin, name="test_plugin")
|
pm.unregister(plugin, name="test_plugin")
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue