Commit 9566d08
authored
fix(auth): guard the aggregate user-groups route and align 10 auth annotations (#456)
TWO SEPARATE THINGS, both authorisation.
1. A PRIVILEGE BYPASS BY SIBLING ROUTE.
Four routes each return one slice of the user-groups configuration and
each carries an explicit `isAdmin() === false -> 403` guard:
GET /api/settings/user-groups/generic
GET /api/settings/user-groups/organization-admin
GET /api/settings/user-groups/super-user
GET /api/settings/user-groups/all
A fifth returns all four at once and checked only that the caller was
logged in:
GET /api/user-groups/config -> SettingsController::getUserGroupsConfig()
SettingsService::getUserGroupsConfig() is literally the union of the four
guarded getters, so any authenticated user could read through it exactly
what the four dedicated routes refuse them — including `allGroups`, the
full group list of the instance.
Which side to fix was NOT obvious from the finding. The four guarded
getters have ZERO callers in src/: every consumer, including
UserGroupsConfiguration.vue via the settings store, hits
/api/user-groups/config. Read as "dead code", the tempting move is to
delete the four guarded endpoints — which would have left the UNGUARDED
aggregate as the only surviving reader. Tracing the sibling seam gives
the opposite answer: the four are the correct implementation, and the
live route is the one missing the guard. So the guard was added there,
and to updateUserGroupsConfig() alongside it, which was admin-only
through the ABSENCE of @NoAdminRequired — middleware alone, with no
in-body backstop, unlike every one of its siblings.
Neither gate could see this. gate-7 (no-admin-idor) counts
`getUser() === null` as an auth guard and passes. gate-9 (semantic-auth)
compares the annotation against the body and finds no mismatch, because
a @NoAdminRequired method with no admin check is self-consistent. The
defect lives in the relationship between two endpoints, which neither
gate models.
2. TEN ANNOTATION/BODY MISMATCHES (gate-9, full-tree).
Eight SettingsController methods declared @NoAdminRequired while their
bodies return 403 to non-admins. Their own sibling setters
(setGenericUserGroups, setSuperUserGroups, ...) already omit the
annotation, so the file's intended pattern was unambiguous: drop it, and
let the middleware refuse before the controller runs. Same effective
policy, enforced one layer earlier.
Two more — AanbodController::getAanbod and
AangebodenGebruikController::getGebruiksWhereAfnemer — carried
@publicpage while their specs (REQ-009, REQ-004) require an
authenticated caller and their bodies enforce it. These are NOT the
self-authenticating webhook/portal shape that makes gate-9's advice
dangerous: nothing authenticates the caller from the request, the body
tests the SESSION, so @publicpage only ever admitted callers the body
would then reject. Replaced with @NoAdminRequired; the in-body guard
stays as deny-before-grant.
Full-tree gate-9: 10 findings -> PASS.
EVIDENCE. SettingsControllerUserGroupsConfigAuthTest, five arms:
non-admin refused (403, payload absent, service never consulted), admin
still served the config itself and not merely a 200, anonymous still 401
rather than promoted to 403, and both arms again for the write half.
Reverting SettingsController.php to origin/development turns exactly the
two non-admin arms red — "Failed asserting that 200 is identical to 403"
— i.e. a non-admin received the configuration.
The deny-before-grant check uses a call counter, not
expects($this->never()): the controller wraps its body in
catch (\Exception), which swallows a PHPUnit expectation failure into a
500 and reports the leak as an unrelated server error. Measured — the
first version of this test failed with "500 is identical to 403".
phpcs: lib/ is 0 errors / 87 warnings, unchanged. Unit suite: 486 green.1 parent 8bf2436 commit 9566d08
4 files changed
Lines changed: 380 additions & 15 deletions
File tree
- lib/Controller
- tests/Unit/Controller
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
83 | 91 | | |
84 | | - | |
| 92 | + | |
85 | 93 | | |
86 | 94 | | |
87 | 95 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
87 | 87 | | |
88 | 88 | | |
89 | 89 | | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
90 | 98 | | |
91 | | - | |
92 | | - | |
93 | | - | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
94 | 102 | | |
95 | 103 | | |
96 | 104 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2290 | 2290 | | |
2291 | 2291 | | |
2292 | 2292 | | |
2293 | | - | |
| 2293 | + | |
| 2294 | + | |
| 2295 | + | |
| 2296 | + | |
2294 | 2297 | | |
2295 | 2298 | | |
2296 | 2299 | | |
| |||
2387 | 2390 | | |
2388 | 2391 | | |
2389 | 2392 | | |
2390 | | - | |
| 2393 | + | |
| 2394 | + | |
| 2395 | + | |
| 2396 | + | |
2391 | 2397 | | |
2392 | 2398 | | |
2393 | 2399 | | |
| |||
2484 | 2490 | | |
2485 | 2491 | | |
2486 | 2492 | | |
2487 | | - | |
| 2493 | + | |
| 2494 | + | |
| 2495 | + | |
| 2496 | + | |
2488 | 2497 | | |
2489 | 2498 | | |
2490 | 2499 | | |
| |||
2581 | 2590 | | |
2582 | 2591 | | |
2583 | 2592 | | |
2584 | | - | |
| 2593 | + | |
| 2594 | + | |
| 2595 | + | |
| 2596 | + | |
2585 | 2597 | | |
2586 | 2598 | | |
2587 | 2599 | | |
| |||
2631 | 2643 | | |
2632 | 2644 | | |
2633 | 2645 | | |
2634 | | - | |
| 2646 | + | |
| 2647 | + | |
| 2648 | + | |
2635 | 2649 | | |
2636 | 2650 | | |
2637 | 2651 | | |
| |||
2682 | 2696 | | |
2683 | 2697 | | |
2684 | 2698 | | |
2685 | | - | |
| 2699 | + | |
| 2700 | + | |
| 2701 | + | |
2686 | 2702 | | |
2687 | | - | |
| 2703 | + | |
2688 | 2704 | | |
2689 | 2705 | | |
2690 | 2706 | | |
| |||
2728 | 2744 | | |
2729 | 2745 | | |
2730 | 2746 | | |
2731 | | - | |
| 2747 | + | |
| 2748 | + | |
| 2749 | + | |
2732 | 2750 | | |
2733 | 2751 | | |
2734 | 2752 | | |
| |||
2773 | 2791 | | |
2774 | 2792 | | |
2775 | 2793 | | |
2776 | | - | |
| 2794 | + | |
| 2795 | + | |
2777 | 2796 | | |
2778 | 2797 | | |
2779 | 2798 | | |
| |||
3322 | 3341 | | |
3323 | 3342 | | |
3324 | 3343 | | |
3325 | | - | |
| 3344 | + | |
| 3345 | + | |
| 3346 | + | |
| 3347 | + | |
| 3348 | + | |
| 3349 | + | |
| 3350 | + | |
| 3351 | + | |
3326 | 3352 | | |
3327 | 3353 | | |
3328 | 3354 | | |
3329 | 3355 | | |
3330 | 3356 | | |
3331 | 3357 | | |
3332 | 3358 | | |
3333 | | - | |
| 3359 | + | |
| 3360 | + | |
3334 | 3361 | | |
3335 | 3362 | | |
3336 | 3363 | | |
| 3364 | + | |
| 3365 | + | |
| 3366 | + | |
| 3367 | + | |
3337 | 3368 | | |
3338 | 3369 | | |
3339 | 3370 | | |
| |||
3365 | 3396 | | |
3366 | 3397 | | |
3367 | 3398 | | |
| 3399 | + | |
| 3400 | + | |
| 3401 | + | |
| 3402 | + | |
| 3403 | + | |
| 3404 | + | |
| 3405 | + | |
| 3406 | + | |
| 3407 | + | |
3368 | 3408 | | |
3369 | 3409 | | |
3370 | 3410 | | |
| |||
0 commit comments