fix(authz): finir SEC-16, que mon propre correctif avait laisse a moitie

La vague K a pose un schema sur create_team et edit_team et a laisse cinq
routes soeurs du meme fichier lire int(request.form.get(...)) : add_coach,
add_manager, remove_coach, remove_manager et add_player. Un identifiant non
numerique y etait un 500 dans chacune.

C'est exactement la lecon que ce projet repete depuis la vague D -- corriger
un motif fautif dans une seule couche le laisse dans les autres -- et cette
fois c'est le correctif lui-meme qui l'a commise. Notee comme telle.

Deux defauts de plus, trouves en finissant.

add_player ecrivait status tel quel dans une colonne NOT NULL String(20). Et
toggle_player_status lit "substitute si status == starter, sinon starter" :
une valeur inconnue devenait donc starter a la premiere bascule, c'est-a-dire
promouvait son porteur. Liste blanche dans TEAM_PLAYER_STATUSES.

Et aucune de ces routes ne regardait is_active_account. La requete qui
alimente la liste deroulante des joueurs ne le filtrait pas non plus, alors
que les deux requetes juste au-dessus, coachs et gerants, le posaient -- deux
lignes d'ecart, meme fichier. Un compte desactive etait donc propose et
accepte, alors que is_active_account est precisement ce qui dit que la
personne a quitte le club. Meme oubli dans tryouts.py.

_staff_member delegue desormais a _assignable au lieu de repeter isinstance :
deux fonctions du meme fichier repondant differemment a "ce compte peut-il
prendre ce role" est la forme de tous les defauts qu'a eus ce module.

Verifie par mutation : retirer le controle d'activite ou la liste blanche
fait tomber trois tests.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
GGThed
2026-08-11 20:42:26 -04:00
co-authored by Claude Opus 5
parent 838b247649
commit 20dabecd67
8 changed files with 633 additions and 361 deletions
+56
View File
@@ -460,6 +460,62 @@ class UploadContractSchema(StripMixin):
)
#: Where a player stands on a team roster.
#:
#: `TeamPlayer.status` is a NOT NULL String(20) that `add_player` filled from
#: `request.form.get('status', 'starter')` with no check at all, so a forged
#: submission stored any string it liked. It then survived until someone
#: pressed the toggle, which reads `'substitute' if status == 'starter' else
#: 'starter'` — so an unknown value silently became `starter`, i.e. promoted
#: whoever held it (SEC-16).
TEAM_PLAYER_STATUSES = ('starter', 'substitute')
class TeamStaffSchema(StripMixin):
"""One staff id, posted by the add/remove coach and manager forms.
Both ids are optional here even though each route needs exactly one:
`remove_coach` treats an absent id as "remove every coach", and the add
routes already carry their own translated "Please select a coach."
message. Making the field required would replace that message with a
generic one for no gain.
What this schema is for is the conversion. Wave K closed `create_team`
and `edit_team` and left five sibling routes reading `int(...)` straight
off the form — a non-numeric id was a 500 in each. Fixing a pattern in
one place and not its neighbours is the mistake this project keeps
making; this is the same mistake, made by the fix for it.
"""
coach_id = fields.Integer(
allow_none=True,
load_default=None,
validate=validate.Range(min=1),
error_messages={'invalid': _l('Invalid coach selection.')},
)
manager_id = fields.Integer(
allow_none=True,
load_default=None,
validate=validate.Range(min=1),
error_messages={'invalid': _l('Invalid manager selection.')},
)
class TeamPlayerSchema(StripMixin):
"""A player being put on a team roster, and where they stand on it."""
player_id = fields.Integer(
allow_none=True,
load_default=None,
validate=validate.Range(min=1),
error_messages={'invalid': _l('Invalid player selection.')},
)
status = fields.String(
load_default='starter',
validate=validate.OneOf(TEAM_PLAYER_STATUSES, error=_l('Unknown roster status.')),
)
class OneOnOneRequestSchema(StripMixin):
"""A player asking their coach for a session (MNT-12).