Offer the search box to whoever can actually search
All checks were successful
CI / naming (push) Successful in 3s
CI / frontend (push) Successful in 12s
CI / migrations-mysql (push) Successful in 42s
CI / backend (push) Successful in 4m24s

The sidebar box was drawn for anyone signed in, on the reasoning that
offering it to a signed-out reader gives them a control that can only
answer 401. That stopped being the right test when the guest role became
able to hold assets.view: the box was hidden from exactly the visitor the
grant was reopened for, and a guest could reach search only by typing
/search?q= by hand.

The client cannot decide this. Its hasPermission getter reads the signed-in
user's permission list and a guest has no user at all, so asking it about a
guest answers no for everyone. get_navigation already had the problem and
already solved it by letting the server decide, so capabilities does the
same thing and reports what the CALLER holds.

It reports booleans about the caller and never what the guest role holds in
general, so it tells an anonymous reader nothing the rendered page does not.
Public because it is the pre-login call that decides whether the box is
drawn at all.

Members who lack assets.view now lose a box that only ever 401'd for them.
On a failed call the box stays hidden rather than falling back to the old
signed-in test, which would put it back in front of those same members.

Scope: product
This commit is contained in:
cproudlock
2026-08-31 09:56:38 -04:00
parent 723a4ff19b
commit df083776b3
8 changed files with 131 additions and 5 deletions

View File

@@ -255,3 +255,38 @@ def test_a_bad_token_is_still_refused(client, db, guestrole):
headers={'Authorization': 'Bearer not.a.token'})
assert response.status_code == 422
# ---------------------------------------------------------------------------
# What the chrome is allowed to draw
# ---------------------------------------------------------------------------
def test_a_guest_is_not_offered_search_before_the_grant(client, guestrole):
"""The box is hidden until somebody opens the door, not by default."""
body = client.get('/api/dashboard/capabilities').get_json()['data']
assert body['cansearch'] is False
def test_granting_a_guest_assets_view_offers_them_search(client, db, guestrole):
"""The point of the whole exercise: a signed-out visitor standing at a
machine gets the box, so they can look up the number in front of them."""
grant(db, guestrole, 'assets.view')
body = client.get('/api/dashboard/capabilities').get_json()['data']
assert body['cansearch'] is True
def test_a_member_without_assets_view_is_not_offered_search(client, db,
member_headers):
"""It used to be drawn for anyone signed in, and answered 401 to the ones
who could not use it."""
body = client.get('/api/dashboard/capabilities',
headers=member_headers).get_json()['data']
assert body['cansearch'] is False
def test_capabilities_does_not_report_what_the_guest_role_holds(client, db,
guestrole):
"""It answers about the CALLER only. A signed-out reader learns whether
their own box is drawn, not the shape of the site's guest policy."""
grant(db, guestrole, 'assets.view')
body = client.get('/api/dashboard/capabilities').get_json()['data']
assert set(body) == {'cansearch'}

View File

@@ -28,6 +28,10 @@ PUBLIC_ENDPOINTS = {
'plugins.list_enabled_plugins',
'dashboard.get_navigation', # filtered per caller; a guest sees only
# what the guest role can reach
'dashboard.get_capabilities', # booleans about the CALLER, used to decide
# whether the search box is drawn at all;
# it must answer a signed-out visitor or
# the guest grant cannot reach the chrome
'dashboard.health_check', # monitoring has no account
# Settings: the handler serves an unauthenticated caller only the public