Chapitre 11.1 — Rôles & capabilities
⏱️ TL;DR — L’autorisation dans Moodle repose sur les capabilities (permissions atomiques comme
mod/assign:grade) attribuées à des rôles (enseignant, étudiant, gestionnaire), dans un contexte (système, cours, activité, utilisateur). C’est un modèle RBAC + contextes puissant. Tout code qui accède à une ressource doit : (1) exiger une session avecrequire_login($course), et (2) vérifier la permission avecrequire_capability('...', $context)(ouhas_capability()pour un test conditionnel). Oublier ces appels — fréquent dans les plugins tiers — recrée exactement les failles de contrôle d’accès de la Partie 5 (IDOR, accès non autorisé). Les capabilities sont les voters de Moodle : on les utilise, on ne les contourne pas.
🎯 Objectifs
- Comprendre le modèle rôles / capabilities / contextes de Moodle.
- Utiliser
require_login()etrequire_capability()au bon niveau. - Faire le parallèle avec le contrôle d’accès de la Partie 5 (RBAC + par objet/contexte).
- Repérer le code (souvent de plugin) qui oublie ces vérifications.
Le modèle : capabilities, rôles, contextes
Moodle a un système d’autorisation fin, structuré autour de trois notions :
- Capability : une permission atomique nommée, par ex.
mod/assign:grade(noter un devoir),moodle/course:manageactivities(gérer les activités d’un cours),mod/quiz:attempt(tenter un quiz). Il en existe des centaines. - Rôle : un ensemble de capabilities (Enseignant, Étudiant, Gestionnaire, Administrateur…). Un utilisateur reçoit un rôle, qui porte ses permissions — c’est du RBAC (Partie 5).
- Contexte : la portée où la capability s’applique, organisée en hiérarchie — Système → Catégorie → Cours → Module (activité) → Bloc → Utilisateur. Une capability accordée dans un contexte descend dans les contextes enfants.
Ce modèle répond aux deux questions de la Partie 5 à la fois : le rôle dit « ce type d’utilisateur peut-il faire ce type d’action ? » (RBAC), et le contexte dit « … dans ce cours / cette activité précise ? » (l’équivalent du contrôle par objet). Un enseignant a mod/assign:grade dans les cours où il est enseignant, pas ailleurs — c’est le contexte qui borne, comme un voter borne par l’objet.
Les deux vérifications à ne jamais oublier
Tout script/plugin Moodle qui affiche ou modifie une ressource doit faire deux choses, dans l’ordre :
1. require_login() — exiger l’authentification (et l’inscription)
require_login($course, true, $cm);
// - verifie que l'utilisateur est CONNECTE (sinon -> page de login)
// - verifie qu'il est INSCRIT / a acces au cours $course
// - ($cm : le module/activite courant, pour l'acces a l'activite)require_login fait plus qu’authentifier : il vérifie l’accès au cours (inscription, disponibilité). C’est la première barrière, en tête de chaque page.
2. require_capability() — exiger la permission dans le contexte
$context = context_module::instance($cm->id); // le contexte de l'activite
require_capability('mod/assign:grade', $context);
// -> stoppe (exception, page d'erreur) si l'utilisateur n'a PAS la permission ICIrequire_capability vérifie que l’utilisateur possède la capability dans le contexte donné — c’est le contrôle d’autorisation par contexte (l’anti-IDOR de Moodle). Pour un test conditionnel (afficher un bouton, choisir une branche), on utilise has_capability('...', $context) qui renvoie un booléen sans interrompre.
// Test conditionnel (affichage, logique) : has_capability
if (has_capability('mod/assign:grade', $context)) {
// afficher les outils de notation
}⚠️ Piège — Le piège classique dans les plugins Moodle mal codés : afficher/modifier une ressource sans
require_capability()(ou en vérifiant seulementrequire_login, qui authentifie mais n’autorise pas l’action précise). Résultat : un étudiant accède à une fonction d’enseignant, ou consulte les données d’un autre cours, en manipulant uniddans l’URL — un IDOR pur (Partie 5).require_login≠require_capability. Chaque action sensible exige la capability dans le bon contexte, pas seulement une session.
Le bon contexte : ni trop haut, ni trop bas
Vérifier la capability dans le mauvais contexte est une faille subtile : la vérifier dans le contexte système ou cours alors que l’action porte sur une activité précise peut autoriser trop largement (ou l’inverse). On vérifie la capability dans le contexte de la ressource visée :
// L'action porte sur une activite -> contexte de l'activite (le plus precis pertinent)
$context = context_module::instance($cm->id);
require_capability('mod/assign:grade', $context);
// Une action sur le cours entier -> contexte cours
$coursecontext = context_course::instance($course->id);Le principe (moindre privilège + contrôle par objet) : la vérification doit correspondre exactement à la portée de l’action.
💡 Réflexe — Pour tout code Moodle (surtout un plugin), vérifie la présence du duo en tête de traitement :
require_login($course, ...)puisrequire_capability('capability/precise', $context)dans le bon contexte. Si l’un manque, tu as (au mieux) une authentification sans autorisation → accès non contrôlé. Les capabilities sont le mécanisme d’autorisation de Moodle, l’équivalent des voters Symfony (Partie 9) : on les utilise systématiquement, on n’invente pas un contrôle « maison ».
🎯 Côté attaquant — Sur un Moodle, l’attaquant cible surtout les plugins tiers (chapitre 11.3) et cherche les scripts qui oublient
require_capability: il manipule les paramètres (idde cours, de devoir, d’utilisateur) pour accéder à ce qui ne le concerne pas — noter le devoir d’un autre cours, voir les copies d’autrui, atteindre une fonction d’admin. Il teste aussi les vérifications faites au mauvais contexte (trop haut = trop permissif). Unrequire_loginsansrequire_capabilityest une prise directe : authentifié mais non autorisé, exactement le trou de la Partie 5.
🧭 Sur FormaCampus — FormaCampus héberge ses cours dans Moodle. Les développements internes (un plugin de reporting, une activité sur mesure) respectent la règle d’or : chaque page commence par
require_login($course, true, $cm)puisrequire_capability('la/capability:precise', $context)dans le contexte exact de la ressource. Les capabilities custom du plugin sont déclarées dansdb/access.phpavec des valeurs par défaut restrictives (secure by default). Avant d’installer un plugin tiers, l’équipe vérifie qu’il appelle bien ces contrôles (revue du code sur les pages sensibles) — un plugin qui oublierequire_capabilityest refusé. C’est le contrôle d’accès de la Partie 5, en langage Moodle.
✏️ Exercices
Exercice 1 — Le duo manquant. Une page de plugin affiche les notes d’un devoir : elle appelle require_login($course) puis lit et affiche toutes les notes du devoir $id reçu en paramètre. Quelle vérification manque, et quel est le risque ?
✅ Solution
Il manque require_capability() dans le contexte de l’activité. require_login($course) authentifie et vérifie l’accès au cours, mais n’autorise pas l’action « voir toutes les notes » — qui devrait exiger une capability d’enseignant (ex. mod/assign:grade ou mod/assign:viewgrades). Risque : un étudiant inscrit au cours (donc passant require_login) peut accéder à cette page et voir les notes de tous — voire, en changeant $id, les notes d’un autre devoir/cours (IDOR). Correctif : après require_login, ajouter $context = context_module::instance($cm->id); require_capability('mod/assign:viewgrades', $context); (capability et contexte adaptés). Authentifié ≠ autorisé.
Exercice 2 — require_capability vs has_capability. Quand utilise-t-on l’un plutôt que l’autre ?
✅ Solution
require_capability('...', $context) : quand l’action exige la permission — il interrompt (exception/erreur) si l’utilisateur ne l’a pas. On l’utilise comme barrière en tête de traitement d’une action sensible (l’équivalent d’un denyAccessUnlessGranted). has_capability('...', $context) : quand on veut tester la permission sans interrompre — il renvoie un booléen. On l’utilise pour la logique conditionnelle et l’affichage (montrer/masquer un bouton, choisir une branche). Attention : has_capability pour masquer un bouton est du confort d’UI — la vraie protection de l’action reste require_capability côté serveur (comme is_granted vs voter en Symfony, Partie 9).
🧠 Quiz de révision
1. Quels sont les trois piliers de l’autorisation Moodle ?
Les capabilities (permissions atomiques, ex. mod/assign:grade), les rôles (ensembles de capabilities : Enseignant, Étudiant…) et les contextes (portée hiérarchique : Système → Cours → Activité…). Le rôle donne le « type d’action », le contexte borne à la « ressource précise ».
2. Quelles deux vérifications un script Moodle doit-il faire ?
require_login($course, ...) (authentification + accès au cours) puis require_capability('capability', $context) (autorisation de l’action précise dans le bon contexte). Les deux, dans l’ordre, en tête de traitement.
3. Pourquoi require_login ne suffit-il pas ?
require_login ne suffit-il pas ?Parce qu’il authentifie (et vérifie l’accès au cours) mais n’autorise pas l’action précise. Sans require_capability, un utilisateur connecté et inscrit peut accéder à des fonctions/données qui ne le concernent pas (IDOR, accès enseignant depuis un compte étudiant). Authentifié ≠ autorisé.
4. Différence entre require_capability et has_capability ?
require_capability et has_capability ?require_capability interrompt si la permission manque (barrière pour une action sensible). has_capability renvoie un booléen sans interrompre (logique conditionnelle, affichage). Le masquage d’UI via has_capability ne remplace pas la barrière require_capability côté serveur.
5. Pourquoi le contexte de la vérification est-il important ?
Parce que vérifier la capability dans le mauvais contexte autorise trop (système/cours au lieu de l’activité) ou trop peu. On vérifie dans le contexte de la ressource visée (moindre privilège + contrôle par objet) : une action sur une activité → context_module, sur un cours → context_course.
Chapitre suivant : sesskey, $DB & formulaires — la protection CSRF de Moodle, les requêtes paramétrées via $DB, et l’échappement de sortie.