fix(authz): verifier le rattachement equipe/tryout, et nettoyer le code mort

SEC-AUTHZ-002. add_to_team recevait tryout_id et team_id independamment
dans l'URL, controlait l'autorisation sur le tryout, puis operait sur
l'equipe sans jamais etablir de lien entre les deux. Un gestionnaire du
tryout A pouvait donc modifier une equipe du tryout B.

Le lint pointait exactement dessus : `team` etait charge ligne 463 puis
jamais utilise. La correction automatique proposee etait de supprimer la
variable, ce qui aurait fait taire l'avertissement en cimentant la faille.
Elle est desormais utilisee pour ce a quoi elle servait.

Trois defauts sur la meme route, corriges ensemble :
  - team.tryout_id != tryout_id repond maintenant 404
  - seuls les joueurs inscrits au tryout peuvent rejoindre ses equipes
  - int(player_id) sur une entree de formulaire brute levait ValueError,
    donc une erreur 500, sur toute valeur non numerique

Nettoyage automatique par ruff : 34 imports et variables morts retires
sur l'ensemble du paquet. La suite de tests a servi de filet, elle passe
a l'identique avant et apres. Aucun changement de comportement.

A noter, OneOnOneRequestSchema figurait aussi parmi les imports morts :
c'est un quatrieme schema jamais appele, la route one_on_one validant ses
dates a la main. Unifier la validation reste a faire (ARCH-005).

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
GGThed
2026-08-07 19:47:00 -04:00
co-authored by Claude Opus 5
parent b277c453f9
commit a793b7ed0d
8 changed files with 33 additions and 20 deletions
+20 -3
View File
@@ -4,7 +4,7 @@ This module handles CRUD operations for tryouts and player registrations.
Uses polymorphic isinstance checks instead of role-string comparisons.
"""
from flask import Blueprint, render_template, redirect, url_for, flash, request
from flask import Blueprint, render_template, redirect, url_for, flash, request, abort
from flask_login import login_required, current_user
from app.extensions import db
from app.models import (
@@ -466,13 +466,30 @@ def add_to_team(tryout_id, team_id):
flash('Permission denied.', 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
player_id = request.form.get('player_id')
# The two ids arrive independently in the URL. Without this check, being
# allowed to manage tryout A was enough to modify a team belonging to
# tryout B, since only the tryout was authorised.
if team.tryout_id != tryout_id:
abort(404)
player_id = request.form.get('player_id', type=int)
if not player_id:
flash('Please select a player.', 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
# Only players registered for this tryout may be placed on its teams.
is_registered = TryoutRegistration.query.filter_by(
tryout_id=tryout_id, player_id=player_id).first() is not None
if not is_registered:
flash('That player is not registered for this tryout.', 'danger')
return redirect(url_for('tryouts.view_tryout', tryout_id=tryout_id))
position = request.form.get('position', '')
existing = TeamMember.query.filter_by(team_id=team_id, player_id=player_id).first()
if existing:
flash('Player is already on this team.', 'info')
else:
member = TeamMember(team_id=team_id, player_id=int(player_id), position=position)
member = TeamMember(team_id=team_id, player_id=player_id, position=position)
db.session.add(member)
db.session.commit()
flash('Player added to team!', 'success')