SoftDesk Support — API REST sécurisée avec Django REST Framework (DRF)


Projet réalisé dans le cadre de ma formation Développeur d’application Python chez OpenClassrooms. Conception et livraison d’une API REST de niveau production pour une plateforme de suivi de problèmes — authentification JWT, permissions OWASP, documentation OpenAPI générée depuis le code et pipeline CI.


Domaine métier et modèle de données

L’API gère quatre ressources principales, liées par une hiérarchie d’appartenance stricte :

  • User — avec deux drapeaux de consentement RGPD (can_be_contacted, can_data_be_shared) et une vérification d’âge minimum de 15 ans à l’inscription.
  • Contributor — table de liaison entre un utilisateur et un projet. Pas un simple champ ManyToMany : une ressource à part entière, avec métadonnées (qui a ajouté ce contributeur, quand).
  • Issue — appartient à un projet. Priorité (LOW / MEDIUM / HIGH), balise (BUG / FEATURE / TASK), statut (To Do / In Progress / Finished), auteur et assignation à un autre contributeur.
  • Comment — appartient à une issue, identifié par UUID. Seul l’auteur peut modifier ou supprimer son commentaire.

Le choix de faire de Contributor une ressource explicite (et non un champ implicite) détermine directement la structure des URL, le modèle de permissions et la logique de scoping des querysets. C’est la décision de conception la plus structurante du projet.


Architecture de l’API


Quand les ressources ont une hiérarchie d’appartenance claire, les URL doivent la refléter. Des endpoints plats (/issues/?project=3) déplacent le scoping dans les paramètres de requête et compliquent l’application des permissions. SoftDesk utilise des routes imbriquées pour la création et des routes canoniques pour l’accès multi-projets.


Structure des URL


/api/v1/projects/ 

  GET, POST

/api/v1/projects/{id}/

GET, PUT, PATCH, DELETE

/api/v1/projects/{id}/contributors/

GET, POST

/api/v1/projects/{id}/contributors/{user_id}/

DELETE

/api/v1/projects/{id}/issues/

GET, POST

/api/v1/projects/{id}/issues/{issue_id}/

GET, PUT, PATCH, DELETE

/api/v1/issues/{id}/assignees/

GET, POST

/api/v1/issues/{id}/assignees/{user_id}/

DELETE

/api/v1/issues/{id}/comments/

GET, POST

/api/v1/issues/{id}/comments/{uuid}/

GET, PUT, PATCH, DELETE

/api/v1/comments/

GET (audit global)


Sérialisation séparée en lecture et en écriture


Chaque ressource dispose de sérialiseurs distincts selon le cas d’usage : listage (payload compact), détail (lecture complète) et écriture (validation des entrées uniquement). Le contrat API est ainsi explicite — les clients savent exactement ce qu’ils envoient et ce qu’ils reçoivent.


Sécurité — Modèle AAA de l’OWASP


Le cahier des charges imposait le modèle AAA (Authentification, Autorisation, Accès) de l’OWASP. Voici comment chaque niveau est implémenté.


Authentification — JWT avec SimpleJWT

La connexion retourne un access token (courte durée, 15 min) et un refresh token (7 jours). Au-delà de l’exigence de base, trois choix de durcissement :

  • Rotation du refresh token : chaque rafraîchissement émet un nouveau token et blackliste l’ancien. Un token volé ne peut être utilisé qu’une seule fois.
  • Blacklistage à la déconnexion : POST /auth/logout/ invalide définitivement le refresh token, terminant la session.
  • SessionAuth désactivée en production : activée uniquement en mode DEBUG pour l’API navigable, évitant d’affaiblir les défenses par défaut.


Autorisation — Permissions à trois niveaux

DRF distingue les permissions au niveau de la vue (avant exécution) et au niveau de l’objet (lors de get_object()). Les trois niveaux du cahier des charges se mappent directement :

  1. Authentifié — défaut global IsAuthenticated, aucune configuration par vue.
  2. Contributeur — vérification au niveau vue : l’utilisateur doit être dans la table Contributor du projet concerné.
  3. Auteur — vérification au niveau objet : seul l’auteur (ou le staff) peut modifier ou supprimer.

Ces classes sont définies une seule fois dans common/permissions.py et réutilisées dans chaque app.


Accès — Scoping des querysets (404 plutôt que 403)


Les classes de permission s’exécutent après que l’objet a été récupéré. Si le queryset retourne des objets non autorisés, un 403 divulgue leur existence. La solution : scoper le queryset lui-même.

def get_queryset(self):
    if self.request.user.is_staff:
        return Project.objects.all()
    # Uniquement les projets où une ligne contributeur existe
    return Project.objects.filter(memberships__user=self.request.user)


Résultat : un non-contributeur qui tente d’accéder à /projects/42/ reçoit un 404 — pas un 403. Bonne pratique OWASP A01 : ne pas confirmer l’existence d’une ressource non autorisée.


Conformité RGPD

  • Vérification d’âge : la date de naissance est obligatoire à l’inscription. Un validateur personnalisé rejette toute inscription pour un utilisateur de moins de 15 ans.
  • Consentement : deux drapeaux booléens (can_be_contacted, can_data_be_shared), valant False par défaut, modifiables à tout moment via PATCH /users/{id}/.
  • Droit à la suppression : DELETE /users/{id}/ est disponible pour l’utilisateur lui-même ou le staff. Le comportement CASCADE de Django supprime toutes les ressources liées. Pas de soft-delete — les données sont définitivement effacées.
  • Droit d’accès et de rectification : GET et PATCH /users/{id}/ disponibles pour l’utilisateur lui-même.


Documentation de l’API


Schéma OpenAPI généré depuis le code

drf-spectacular génère un schéma OpenAPI 3 directement depuis les vues, sérialiseurs et docstrings. La documentation est toujours synchronisée avec l’implémentation — impossible de l’oublier de mettre à jour.

python manage.py spectacular --validate --file postman/openapi.yaml


Le YAML produit peut être importé dans Postman, Swagger UI, Redoc ou tout outil compatible OpenAPI.


Collection Postman avec auto-sauvegarde des tokens


La collection partagée est livrée avec un environnement local préconfiguré. Un script de test sur la requête de connexion sauvegarde automatiquement les tokens dans les variables d’environnement — toute la collection est authentifiée après une seule connexion.

// Script exécuté après POST /api/v1/auth/login/
pm.environment.set("access_token",  pm.response.json().access);
pm.environment.set("refresh_token", pm.response.json().refresh);


Tests et qualité du code


Les tests les plus utiles ne sont pas ceux qui vérifient que les happy paths retournent 200. Ce sont ceux qui vérifient le modèle de permissions dans des conditions adverses : un non-contributeur qui accède au projet, un contributeur qui tente de supprimer l’issue d’un autre, un token expiré réutilisé.


Ce qui est couvert

  • Cycle d’authentification : connexion, rafraîchissement, déconnexion, token invalide, token expiré
  • CRUD projets : visibilité du listage (scoping), mise à jour et suppression (auteur seulement), verrou contributeur
  • Gestion des contributeurs : ajout par username ou email, rejet si inexistant, refus de supprimer l’auteur du projet
  • CRUD issues : création imbriquée, permissions de modification, lecture par les contributeurs
  • Assignations : rejet d’un non-contributeur, prévention des doublons
  • CRUD commentaires : scoping du listage global, permissions de modification et suppression
  • Utilisateurs : inscription avec vérification d’âge, auto-modification, droit à la suppression


Pipeline CI GitHub Actions


Quatre étapes séquentielles à chaque push et pull request :

  1. Lint Ruff — échoue sur toute erreur de lint.
  2. Vérification du formatage — échoue si le code diffère du format canonique.
  3. Pytest — échoue si un test échoue. Couverture ≥ 80 %.
  4. Artefact de couverture — rapport HTML téléchargeable à chaque exécution.


Green code — Performance et efficacité

Le cahier des charges imposait la pagination. Au-delà de cette exigence, deux patterns ORM évitent les problèmes de performance les plus courants en Django.

  • Pagination globale : configurée une seule fois dans common/paginator.py et appliquée automatiquement à tous les endpoints de liste. page_size = 10 par défaut, max 100.
  • select_related() : récupère les objets liés en un seul JOIN pour éliminer les requêtes N+1 sur toutes les relations ForeignKey.
  • annotate() : calcule les compteurs (issues_count, contributors_count) directement en SQL plutôt qu’en Python — une seule requête au lieu d’une boucle.
  • Unicité en base : constraints au niveau modèle pour éviter les doublons à l’écriture, éliminant le besoin de .distinct() à la lecture.

Description

Contexte :

SoftDesk Support est un projet réalisé dans le cadre de ma formation de développeur back-end Python chez OpenClassrooms. L’objectif était de concevoir et livrer une API REST de niveau production pour une plateforme de suivi de problèmes, en intégrant d’emblée la sécurité (OWASP), la conformité RGPD et une approche green code.

Le cahier des charges définissait quoi construire. Les choix d’architecture — modèle de données, structure des URL, stratégie de permissions, couverture de tests et pipeline CI — m’appartenaient entièrement.

 

Résultat final

Une API REST entièrement fonctionnelle, documentée, avec une couverture de tests supérieure à 80 %, un badge CI vert à chaque commit et une collection Postman prête à partager. Le projet illustre comment appliquer les principes de sécurité OWASP de manière pragmatique dans une base de code Django/DRF, sans sur-ingénierie, tout en conservant un code lisible, testable et maintenable.

Point clé : chaque décision de conception — du scoping des querysets à la rotation des tokens en passant par la structure des URL imbriquées — peut être reliée soit aux exigences du cahier des charges, soit à un arbitrage délibéré et justifié.

Outils & Technologies

BACK-END

  • Python 3.12  ·  Django 5  ·  Django REST Framework
  • SimpleJWT — tokens access/refresh, rotation, blacklist
  • SQLite — base de données locale, zéro configuration

DOCUMENTATION & OUTILLAGE API

  • drf-spectacular — génération du schéma OpenAPI 3
  • Postman — collection partagée + environnement local avec auto-sauvegarde des tokens

TESTS & QUALITÉ

  • Pytest · pytest-django · pytest-cov
  • Ruff — lint + auto-formatage
  • GitHub Actions — pipeline CI

GESTION DE PROJET

  • Poetry — gestion des dépendances avec lock file déterministe
  • GitHub Projects — épics, user stories et tâches techniques via templates d’issues
Sierra Ripoche
Résumé de la politique de confidentialité

Ce site utilise des cookies afin que nous puissions vous fournir la meilleure expérience utilisateur possible. Les informations sur les cookies sont stockées dans votre navigateur et remplissent des fonctions telles que vous reconnaître lorsque vous revenez sur notre site Web et aider notre équipe à comprendre les sections du site que vous trouvez les plus intéressantes et utiles.