Let a guest actually use search: declare the shipped guest grants, and draw the box for whoever can search

This commit is contained in:
2026-08-31 09:56:49 -04:00
parent 0e3d5a6226
commit 64c51ce805
8 changed files with 170 additions and 4 deletions

View File

@@ -125,6 +125,28 @@ def test_a_view_permission_is_not_grantable_merely_for_being_a_view(app):
assert name not in grantable, name
def test_the_guest_features_that_already_shipped_stay_grantable(app):
"""Both were granted on a live site before a grant had to be declared.
Inverting the rule to explicit declaration dropped every permission that
had qualified by its .view suffix, including two the product ships guest
flows for: guides, whose nav item and list route both reason about a
signed-out reader, and the tools page, without which a guest holding
tools.secrets cannot reach Secure Send at all.
A site that granted them under the old rule was then unable to save the
guest role AT ALL - the API refuses the whole request when any submitted
permission is undeclared, and the grid resubmits what the role already
holds. That deadlock is what this guards against, so anyone narrowing the
grantable set again has to look at what is already granted in the field.
"""
from shopdb.core.models import guest_grantable_permissions
with app.app_context():
grantable = guest_grantable_permissions()
assert 'guides.view' in grantable
assert 'tools.view' in grantable
def test_the_bytes_behind_applications_are_not_grantable(app):
"""applications.view is grantable only because the bytes moved off it."""
from shopdb.core.models import guest_grantable_permissions
@@ -232,3 +254,39 @@ def test_a_bad_token_is_still_refused(client, db, guestrole):
response = client.get('/api/auditlogs',
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