Skip to content

Hide restricted and empty categories from the Doctrines filter chips - #133

Merged
TrueMessenger merged 2 commits into
mainfrom
fix/category-chip-leak
Jul 8, 2026
Merged

TrueMessenger merged 2 commits into
mainfrom
fix/category-chip-leak

Conversation

@TrueMessenger

@TrueMessenger TrueMessenger commented Jul 8, 2026

Copy link
Copy Markdown
Owner

Summary

  • The Doctrines page filter chip bar listed every DoctrineCategory unconditionally, so a member could see the name/color of a restricted (opsec) category they had no access to, or a chip for a category with nothing visible in it.
  • Add visible_categories_for(user) (fitcheck/managers.py): for a manager, every category; otherwise only categories that admit the user AND currently contain at least one active, visible doctrine. Used by the index view for the chip bar.
  • The same OR-across-categories exposure existed anywhere a doctrine's or fit's full category set was rendered without per-category filtering: a doctrine/fit admitted via one category could still carry a second, restricted category in its badge row. Add visible_categories_among(user, categories) and apply it to:
    • the per-doctrine category badges on the Doctrines index cards
    • the doctrine detail page's category badges
    • the fit detail page's category badges
  • The fit detail page's doctrine chip row had the same leak one level up: it was built from the fit's full doctrine set, so a fitting shared between a visible doctrine and a restricted one named the restricted doctrine (whose chip linked to a visibility-gated page). The chip list is now built only from doctrines visible to the viewer, and the combined category badge row derives from that same visible set. When none of a fitting's doctrines are visible, the page falls back to the existing "Standalone standard" empty state.
  • All helpers route their manager bypass through the existing _is_manager check (directly or via visible_to) rather than re-implementing it, so managers - including the Edit Doctrines UI on the fit page - see everything, unchanged.
  • Manager-gated pages (Manage Doctrines, Compliance Reports) are unaffected - they intentionally show every category.

Test plan

  • manage.py test fitcheck - 751 tests, all passing
  • manage.py check - no issues
  • manage.py makemigrations fitcheck --check --dry-run - no changes (no migration in this PR)
  • New tests in fitcheck/tests/test_category_visibility.py cover: admitted category with a visible doctrine (shown), restricted category (hidden), admitted-but-empty category (hidden), public category (shown), and manager bypass - at both the helper level and via a GET of the Doctrines index page; a doctrine/fit carrying one admitted and one restricted category, checked on the index cards, doctrine detail page, and fit detail page; and the fit detail doctrine chips - member sees only the visible doctrine's chip, manager sees both, and a fit whose only doctrine is restricted renders the "Standalone standard" empty state.

The Doctrines page filter chip bar listed every DoctrineCategory
regardless of whether the viewing member was admitted to it, revealing
the names and colors of restricted (opsec) categories and offering
chips for categories with nothing visible in them.

Add visible_categories_for(user), which restricts the chip list to
categories that admit the user and currently contain at least one
active, visible doctrine (managers still see everything, routed
through the existing _is_manager check).

The same OR-across-categories exposure existed wherever a doctrine's
or fit's full category set was rendered without checking each category
individually: a doctrine or fit admitted via one category could still
carry a second, restricted category in its badge row. Add
visible_categories_among(user, categories) and apply it to the
per-doctrine category badges on the Doctrines index cards, the
doctrine detail page, and the fit detail page.
The fit detail page built its doctrine chip row from the fit's full
doctrine set, so a member viewing a fitting shared between a visible
doctrine and a restricted one saw the restricted doctrine's name - and
its chip linked to a page that is visibility-gated anyway.

Build the chip list only from doctrines visible to the viewer
(managers keep everything via visible_to's existing _is_manager
bypass, so the Edit Doctrines UI is unaffected), and derive the
combined category badge row from that same visible set. When none of
a fitting's doctrines are visible, the chip row falls back to the
existing "Standalone standard" empty state.
@TrueMessenger
TrueMessenger merged commit 6429d1a into main Jul 8, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant