SEC-P0-001B4 — AI quota authorization hardening
Scope
- Route corrigée :
GET /api/v1/ai/quota?actor_id=... - Domaine : lecture du quota IA rattaché à un acteur.
- Hors périmètre : B5 Backoffice/noCreditDebit, B6 cache/quota structurels, DB-001, unicité
ai_user_quotas, tarification, Stripe, nouvelle permission spécialisée, frontend.
Divergence initiale et arbitrage
La règle générique issue des routes B1/B2/B3 pouvait laisser penser que toute route IA avec actor_id devait utiliser une permission acteur (manage_publications ou manage_actor).
L’arbitrage B4 distingue volontairement la lecture de quota :
manage_actorn’est pas suffisant ;- aucune nouvelle permission n’est créée ;
- l’accès est réservé au propriétaire réel de l’acteur ou à l’administrateur global selon le comportement existant.
Raison : la réponse expose des données d’usage et commerciales (monthly_limit, monthly_used, bonus_credits, available, is_unlimited, reset_at) et le service sous-jacent peut lire actor_subscriptions et subscription_plans.
Mécanismes d’autorisation utilisés
Le contrôle utilise le mécanisme existant ActorAccessService::isOwner($user, $acteurId).
Ce mécanisme s’appuie sur le modèle canonique acteur_collaborateurs :
- owner réel : rôle
ownerdansacteur_collaborateurspour l’acteur demandé ; - admin global :
Profile::isAdmin()via les rôles applicatifsadminouplatform_admin.
La projection legacy user_acteurs seule ne constitue pas une preuve d’ownership canonique pour cette route.
Ordre d’exécution sécurisé
Avant tout appel à AIQuotaService::getOrCreate() :
- l’utilisateur est authentifié par le middleware existant ;
actor_idest validé au format UUID ;ActorAccessService::isOwner(...)vérifie owner ou admin global ;- un refus retourne
403.
Ainsi, sur 403, la route ne déclenche pas :
- création ou modification dans
ai_user_quotas; - lecture de plan via
actor_subscriptionsousubscription_plans; - écriture de cache ;
- journalisation d’usage IA ;
- consommation de crédit ou de boost ;
- exposition de données quota tiers.
Consommateur actuel
Le consommateur frontend réel identifié est le Backoffice admin. Aucun changement frontend n’est requis : l’URI, la query string, la structure JSON et le calcul de quota restent inchangés pour les accès autorisés.
Tests ajoutés
Les tests AITest couvrent :
- owner →
200; - admin global →
200; - collaborateur actif avec
manage_actor→403; - collaborateur avec autre permission →
403; - collaborateur inactif →
403; - utilisateur propriétaire d’un autre acteur →
403; - utilisateur sans acteur →
403; - relation legacy
user_acteursseule →403; - régression IDOR avec
actor_idfalsifié →403.
Pour les refus, les tests prouvent que AIQuotaService::getOrCreate() n’est pas appelé, qu’aucune donnée quota n’est retournée, que les quotas existants restent inchangés, qu’aucune ligne quota n’est créée quand elle n’existe pas, et que les compteurs ai_usage_logs, ai_cache et boost_usages restent inchangés.
Décision future
Si le Workspace doit afficher les crédits IA à des collaborateurs non owners, une décision produit/RBAC dédiée sera nécessaire. Cette évolution ne fait pas partie de B4 et ne doit pas réutiliser implicitement manage_actor.