Semaine 11

This commit is contained in:
gauvainboiche
2026-07-11 21:38:00 +02:00
parent d3d2416dea
commit 0d071d2843
281 changed files with 54412 additions and 0 deletions
@@ -0,0 +1,336 @@
# Examen final — Programmation web en Python (Modules 1 à 5)
**Nom / Prénom :** BOICHÉ Gauvain **Groupe :** MCS P 2027.2
## Consignes
- Épreuve **individuelle**, **notes de cours autorisées**.
- **45 questions fermées (QCM)** : une **seule** réponse correcte par question — entourez la lettre.
- **13 questions ouvertes** : répondez dans l'espace prévu ; pour les analyses de code, soyez précis (ligne, cause, correction).
- Contexte de toutes les questions : la **gestion d'un parc d'équipements réseau** (serveurs, switchs, routeurs, interfaces).
---
**Q1.** Que vaut `len(set([22, 80, 443, 22, 80]))` ?
- b) 3 (set garde les occurences uniques)
**Q2.** Parmi ces méthodes HTTP, laquelle **n'est pas** idempotente ?
- a) GET (Selon Wikipédia : "En mathématiques et en informatique, l'idempotence signifie qu'une opération a le même effet qu'on l'applique une ou plusieurs fois.")
**Q3.** Quel code de statut HTTP indique qu'une ressource a été **créée** ?
- b) 201
**Question ouverte O1 *(2 pts)*.** Expliquez en deux phrases la différence entre **Internet** et le **Web**.
> Réponse : Le "Web" c'est le concept. C'est la "toile" qui relie tout et forme un tout navigable. "Internet" c'est l'infrastructure, le maillage des machines qui fournissent le service. Pour "filer" la métaphore (filer, toile... humour, drôle) "Internet" ce sont les meubles sur lesquels on tisse la "Toile" pour aller de l'un à l'autre.
**Q4.** *(Analyse de code)* Que produit l'exécution de cet extrait ?
```python
adresse = ("10.0.0.1", 22)
adresse[1] = 443
```
- b) Lève une `TypeError` (j'ai failli me faire avoir : ce n'est pas une liste ni un dictionnaire mais un tuple. Ca ne se mute pas.)
**Q5.** Quelle structure Python **n'autorise pas les doublons** et **n'est pas ordonnée** ?
- c) `set` (cf. question 1)
**Q6.** Dans une API REST, quelle URL respecte les bonnes pratiques ?
- b) `/equipements` (en vrai je vais faire mon chieur : la convention voudrait du 100% anglais)
**Q7.** *(Analyse de code)* Qu'affiche `print(2 ** 6 - 2)` (nombre d'hôtes d'un /26) ?
- b) 62
**Question ouverte O2 *(4 pts)*.** Écrivez une fonction Python `hotes_disponibles(bits_hote)` qui renvoie le nombre d'adresses **hôtes utilisables** d'un sous-réseau, puis donnez le résultat pour un **/24**.
> Réponse :
```python
def hotes_disponibles(bits_hote):
return 2 ** (32 - bits_hote) - 2
# bits_hote = 24
# => 254
```
**Q8.** Que renvoie une fonction Python qui ne contient **aucun** `return` ?
- c) `None`
**Q9.** À quoi sert l'en-tête HTTP `Content-Type: application/json` ?
- b) À indiquer le format du corps du message
**Q10.** Quel format de données est le standard d'échange des API REST modernes ?
- c) JSON
**Question ouverte O3 *(3 pts)*.** Qu'est-ce que l'**idempotence** d'une méthode HTTP ? Donnez une méthode idempotente et une non idempotente, en justifiant.
> Réponse : J'ai défini en quesiton 2 : Idempotente, en informatique et en maths, c'est l'obtention d'un même résultat quel que soit le nombre de fois où une requête est exécutée. Aussi, "GET" est idempotente en ce que le résultat sera toujours le même à la millionième demande, tandis qu'un DELETE conduira à une erreur dès la deuxième (ressource supprimée : plus d'action à mener), de même qu'un POST (ressource déjà créée, pas de doublon !) ou un PUT (modifications déjà faites). Pour QUERY je ne saurais rien affirmer encore, méthode trop jeune. Mais je la penserais idempotente.
---
**Q11.** À quoi sert une **clé primaire** ?
- b) À identifier de façon unique chaque ligne (colonne ID par convention)
**Q12.** Une **clé étrangère** permet de…
- b) créer une relation vers la clé primaire d'une autre table
**Q13.** *(Analyse de code)* Qu'affiche cet extrait ?
```python
resultats = [("srv-web-01", "192.168.1.10"), ("sw-access-02", "192.168.1.2")]
print(resultats[1][0])
```
- c) `sw-access-02` (Premier élément du deuxième élément)
**Question ouverte O4 *(3 pts)*.** Expliquez ce qu'est une **clé étrangère** et à quoi elle sert, à l'aide d'un exemple `equipement` / `interface`.
> Réponse : Une clef étrangère est une référence à la clef primaire d'une autre table pour faire des croisements logiques. Dans un exemple "Equipements / Interfaces", une "Interface" aura une clef primaire, mettons ID "1" pour une interface "HTTP" au port "80". Un équipement fera donc appel à la clef étrangère de l'interface, "1" pour l'affecter à son propre équipement. Ainsi les deux tables sauront que l'interface 1 (HTTP, 80) est liée à l'équipement 1 (Serveur, 15.15.15.15, etc).
> En tout cas ce qui est sûr, c'est qu'une clef étrangère ne vote pas Le Pen.
**Q14.** En SQL, quelle commande permet de **lire** des données ?
- b) `SELECT`
**Q15.** Pourquoi utiliser des `?` (requête paramétrée) avec `sqlite3` ?
- b) Pour éviter les injections SQL (requête préparée)
**Q16.** « Un équipement possède plusieurs interfaces » : de quel type de relation s'agit-il ?
- b) Un-à-plusieurs (1-N)
**Q17.** Dans SQLAlchemy 2.0, quel objet gère les opérations `add()` / `commit()` ?
- a) `Engine`
**Question ouverte O5 *(4 pts — analyse de code)*.** Identifiez la **faille de sécurité** de cet extrait et **corrigez-la**.
```python
hostname = input("hostname ? ")
cur.execute("SELECT * FROM equipement WHERE hostname = '{hostname}'")
```
> Réponse : Ah bah j'en vois plusieurs. La première je dirais [Injection de Script Croisé (XSS)](https://cheatsheetseries.owasp.org/cheatsheets/SQL_Injection_Prevention_Cheat_Sheet.html) et ensuite on ouvre les vannes à tous les résultats sans discernement, puis aucune validation de la string.
```python
hostname = input("hostname ? ").strip()
if not hostname or len(hostname) > 128:
raise ValueError("Hostname invalide.")
cur.execute("SELECT * FROM equipement WHERE hostname = %s", (hostname,))
```
**Q18.** Que fait `cursor.fetchall()` ?
- a) Exécute la requête ([The method fetches all (or all remaining) rows of a query result set and returns a list of tuples.](https://dev.mysql.com/doc/connector-python/en/connector-python-api-mysqlcursor-fetchall.html))
**Q19.** Une relation **N-N** (plusieurs-à-plusieurs) nécessite…
- b) une table de liaison (d'association)
**Q20.** Quel est un **avantage** de l'ORM par rapport au SQL brut ?
- b) Il manipule des objets Python et facilite le changement de SGBD
**Question ouverte O6 *(3 pts — analyse de code)*.** Après ce code, la ligne n'apparaît pas dans la base. **Pourquoi** et **comment corriger** ?
```python
conn = sqlite3.connect("inventaire.db")
cur = conn.cursor()
cur.execute("INSERT INTO equipement (hostname, ip, type) VALUES (?, ?, ?)",
("srv-dns-01", "192.168.1.20", "serveur"))
conn.close()
```
> Réponse : SQLite3 c'est comme Git : sans commit, ça reste en stase.
```python
conn = sqlite3.connect("inventaire.db")
cur = conn.cursor()
cur.execute("INSERT INTO equipement (hostname, ip, type) VALUES (?, ?, ?)",
("srv-dns-01", "192.168.1.20", "serveur"))
conn.commit()
conn.close()
```
---
**Q21.** Flask est un…
- a) framework full-stack
**Q22.** *(Analyse de code)* Que renvoie cette vue Flask ?
```python
@app.route("/api/statut")
def statut():
return {"etat": "ok"}
```
- c) Une réponse **JSON** avec le code **200**
**Q23.** Quel décorateur associe une URL à une fonction en Flask ?
- b) `@app.route`
**Question ouverte O7 *(3 pts)*.** À quoi sert la bibliothèque `requests` ? Donnez un exemple d'appel **GET** avec un **timeout**.
> Réponse : [Comme le dit la doc](https://pypi.org/project/requests/), ça envoie des requêtes HTTP/1.1 très facilement.
```python
import requests
req = requests.get('https://httpbin.org/basic-auth/user/pass', auth=('user', 'pass'), timeout=10)
```
**Q24.** Comment lit-on le corps **JSON** d'une requête entrante en Flask ?
- b) `request.get_json()`
**Q25.** Avec `requests`, comment envoyer un corps **JSON** dans un POST ?
- c) `json=...`
**Q26.** Que fait `response.raise_for_status()` ?
- b) Lève une exception si le code est ≥ 400
**Q27.** En Jinja2, quelle syntaxe **affiche** une variable ?
- b) `{{ variable }}`
**Question ouverte O8 *(4 pts — analyse de code)*.** Un client envoie `POST /api/equipements` et reçoit `405 Method Not Allowed`. Voici la route. **Pourquoi** ? **Corrigez-la.**
```python
@app.route("/api/equipements")
def creer_equipement():
donnees = request.get_json()
...
```
> Réponse : La méthode n'est pas donnée. Deux solutions (en soit une pour l'API mais on va faire comme si) :
```python
# 1 : forcer la méthode
@app.route("/api/equipements", methods=["POST"])
def creer_equipement():
donnees = request.get_json()
# 2 : uniquement pour les API, pas de GUI
@app.post("/api/equipements")
def creer_equipement():
donnees = request.get_json()
```
**Q28.** En Jinja2, quelle syntaxe permet d'écrire une **boucle** ?
- b) `{% for e in liste %} … {% endfor %}`
**Q29.** Quel moteur de templates Flask utilise-t-il par défaut ?
- b) Jinja2 (https://flask.palletsprojects.com/en/stable/templating/)
**Q30.** Comment renvoyer un JSON **avec le code 201** en Flask ?
- b) `return jsonify(x), 201`
**Question ouverte O9 *(3 pts)*.** Écrivez une route Flask `GET /api/equipements/<int:equipement_id>` qui renvoie l'équipement en JSON, ou une erreur **404** s'il n'existe pas (liste `equipements` en mémoire). Expliquez brièvement votre code.
> Réponse : Une méthode (issue des cours de cette semaine) qui lit les équipements, compare les ID présents avec l'ID fourni, et retourne l'élément s'il trouve une correspondance. Mais comme tout bon code se passe de commentaire, il est assez clair de lui-même :
```python
@app.route('/api/equipments/<int:equipment_id>', methods=['GET'])
def get_equipment(equipment_id):
equipment = next((e for e in equipments if e["id"] == equipment_id), None)
if equipment is None:
return jsonify({"erreur": "Équipement non trouvé"}), 404
return jsonify(equipment), 200
```
---
**Q31.** Django est un framework…
- b) full-stack (« batteries included »)
**Q32.** Que signifie l'architecture **MTV** de Django ?
- a) Model-Template-View
**Q33.** Quelle commande **génère** les fichiers de migration ?
- b) `makemigrations`
**Question ouverte O10 *(3 pts)*.** Qu'est-ce qu'une **migration** Django et pourquoi en a-t-on besoin ? (une analogie est acceptée)
> Réponse : Il s'agit d'une propagation des changements. Djangon compare l'ancienne et la nouvelle structure du projet, avec ses applications, et va appliquer les changements idoines dès lors qu'on lui demande. Une analogie que je peux tirer [de leur documentation](https://docs.djangoproject.com/en/6.0/topics/migrations/), c'est l'équivalent d'un `git pull origin main`. Une fois toutes les *merge requests* faites, on prend la dernière version de la branche principale.
**Q34.** Quelle commande **applique** les migrations à la base de données ?
- b) `migrate`
**Q35.** *(Analyse de code)* L'équipement 999 n'existe pas. Que se passe-t-il ?
```python
Equipement.objects.get(id=999)
```
- d) Renvoie une erreur 404
**Q36.** Dans DRF, le rôle d'un **sérialiseur** est de…
- b) convertir les objets ↔ JSON et valider les données
**Q37.** Quelle classe DRF fournit **automatiquement** tout le CRUD ?
- b) `ModelViewSet`
**Question ouverte O11 *(4 pts — analyse de code)*.** Cette vue déclenche l'erreur « *didn't return an HttpResponse object* ». **Pourquoi** ? **Corrigez-la.**
```python
def liste_equipements(request):
return Equipement.objects.filter(actif=True)
```
> Réponse : Je... ne... vois pas en quoi, j'ai ça et ça marchait :
```python
@login_required
def list_active_equipments(request):
equipments = Equipment.objects.filter(is_active=True)
return render(request, "parc/list.html", {"equipments": equipments})
```
> Ah bah c'est peut-être ça alors. Faire un `return` aussi tôt sans formater avec un `render` doit renvoyer quelque chose de vide. [Et après recherche, c'est bien ça](https://stackoverflow.com/a/26258906). Il faut un *Wrapper* autour de cette réponse.
**Q38.** À quoi sert un `DefaultRouter` en DRF ?
(La doc : *The default router extends the SimpleRouter, but also adds in a default API root view, and adds format suffix patterns to the URLs.*)
- b) À générer automatiquement les URLs d'un ViewSet
**Q39.** La permission `IsAuthenticatedOrReadOnly` autorise…
- b) la lecture à tous, l'écriture aux utilisateurs authentifiés
**Q40.** Quel attribut booléen du modèle `User` donne accès à l'interface `/admin/` ?
- c) `is_superuser` (https://docs.djangoproject.com/en/6.0/ref/contrib/admin/#:~:text=form%20like%20so%3A-,class%20MyModelAdmin(admin.ModelAdmin)%3A,-def%20get_form()
**Question ouverte O12 *(4 pts)*.** Écrivez un `EquipementSerializer` (DRF `ModelSerializer`) exposant `id`, `hostname`, `ip`, `actif`, puis expliquez en une phrase la différence entre `APIView` et `ModelViewSet`.
> Réponse : [En résumé :](https://stackoverflow.com/a/60121034) une APIView offre toute possibilité mais demande encore une construction manuelle de la logique des points de terminaisons. Le ModelViewSet offre l'ensemble des méthodes CRUD prêt à l'emploi, même si ça reste personnalisable. En gros on a la V1 et la V2.
```python
class EquipmentSerializer(serializers.ModelSerializer):
class Meta:
model = Equipment
fields = ["id", "hostname", "ip", "is_active"]
```
**Q41.** Un utilisateur authentifié accède aux équipements d'un autre en modifiant simplement l'identifiant dans l'URL (/api/equipements/42/). De quel risque OWASP s'agit-il ?
- b) [API1 — Broken Object Level Authorization (BOLA)](https://owasp.org/API-Security/editions/2023/en/0xa1-broken-object-level-authorization/)
**Q42.** Dans un ViewSet DRF, quelle technique garantit que chaque utilisateur ne voie que ses ressources ?
- b) Surcharger get_queryset() en filtrant sur request.user
**Q43.** Pourquoi éviter fields = "__all__" dans un ModelSerializer exposé par une API ?
- c) Cela peut exposer des champs sensibles et autoriser le mass assignment (API3 BOPLA)
**Q44.** Une API récupère une URL fournie par l'utilisateur (webhook). Quelle est la meilleure protection contre la SSRF (API7) ?
- c) Valider le schéma puis bloquer les IP privées/loopback/link-local (ou utiliser une allowlist) (j'ai hésité avec la B) mais ça semble bien être ça (https://owasp.org/API-Security/editions/2023/en/0xa7-server-side-request-forgery/))
**Q45.** Quel code de statut HTTP DRF renvoie-t-il lorsqu'un client dépasse la limite de débit (throttling, API4) ?
- c) 429 Too Many Requests
**Question ouverte O13** - Trouver et corriger la faille (analyse de code)
Le développeur veut renvoyer « mes équipements ». Voici sa vue DRF. Identifiez la faille, nommez le risque OWASP, expliquez l'impact, puis corrigez le code.
```python
# parc/views.py
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Equipement
from .serializers import EquipementSerializer
@api_view(["GET"])
def mes_equipements(request):
user_id = request.query_params.get("user_id") # ex. /api/mes-equipements/?user_id=1
equipements = Equipement.objects.filter(proprietaire_id=user_id)
return Response(EquipementSerializer(equipements, many=True).data)
```
> Réponse : J'ai mis une petite minute à trouver quand même, mais je pense l'avoir : on peut itérer comme on veut sur chaque ID et avoir tout l'inventaire sans besoin de s'authentifier. Ca ne compare jamais son PROPRE identifiant (si applicable) pour avoir SON parc d'équipements. C'est un OWASP API1, un BOLA. N'importe qui peut faire son organigramme d'équipements dans l'orga, c'est pratique pour un pirate. Je vais juste reprendre mon propre code en correction :
```python
from rest_framework.permissions import IsAuthenticated
@api_view(["GET"])
def mes_equipements(request):
permission_classes = [IsAuthenticated]
equipements = Equipement.objects.filter(proprietaire_id=request.user)
return Response(EquipementSerializer(equipements, many=True).data)
```
FIN DU DOCUMENT
+405
View File
@@ -0,0 +1,405 @@
# Examen final — Programmation web en Python (Modules 1 à 5)
**Nom / Prénom :** …………………………………………… **Groupe :** …………
## Consignes
- Épreuve **individuelle**, **notes de cours autorisées**.
- **45 questions fermées (QCM)** : une **seule** réponse correcte par question — entourez la lettre.
- **13 questions ouvertes** : répondez dans l'espace prévu ; pour les analyses de code, soyez précis (ligne, cause, correction).
- Contexte de toutes les questions : la **gestion d'un parc d'équipements réseau** (serveurs, switchs, routeurs, interfaces).
---
**Q1.** Que vaut `len(set([22, 80, 443, 22, 80]))` ?
- a) 5
- b) 3
- c) 2
- d) 4
**Q2.** Parmi ces méthodes HTTP, laquelle **n'est pas** idempotente ?
- a) GET
- b) PUT
- c) POST
- d) DELETE
**Q3.** Quel code de statut HTTP indique qu'une ressource a été **créée** ?
- a) 200
- b) 201
- c) 301
- d) 404
**Question ouverte O1 *(2 pts)*.** Expliquez en deux phrases la différence entre **Internet** et le **Web**.
> Réponse : ………………………………………………………………………………………………………………………
**Q4.** *(Analyse de code)* Que produit l'exécution de cet extrait ?
```python
adresse = ("10.0.0.1", 22)
adresse[1] = 443
```
- a) Affiche `('10.0.0.1', 443)`
- b) Lève une `TypeError`
- c) Affiche `('10.0.0.1', 22)`
- d) Lève une `IndexError`
**Q5.** Quelle structure Python **n'autorise pas les doublons** et **n'est pas ordonnée** ?
- a) `list`
- b) `tuple`
- c) `set`
- d) `dict`
**Q6.** Dans une API REST, quelle URL respecte les bonnes pratiques ?
- a) `/getEquipements`
- b) `/equipements`
- c) `/equipement/delete/5`
- d) `/list`
**Q7.** *(Analyse de code)* Qu'affiche `print(2 ** 6 - 2)` (nombre d'hôtes d'un /26) ?
- a) 64
- b) 62
- c) 126
- d) 30
**Question ouverte O2 *(4 pts)*.** Écrivez une fonction Python `hotes_disponibles(bits_hote)` qui renvoie le nombre d'adresses **hôtes utilisables** d'un sous-réseau, puis donnez le résultat pour un **/24**.
> Réponse :
> ```python
>
> ```
**Q8.** Que renvoie une fonction Python qui ne contient **aucun** `return` ?
- a) `0`
- b) `""`
- c) `None`
- d) `False`
**Q9.** À quoi sert l'en-tête HTTP `Content-Type: application/json` ?
- a) À authentifier le client
- b) À indiquer le format du corps du message
- c) À définir la méthode HTTP
- d) À fixer le code de statut
**Q10.** Quel format de données est le standard d'échange des API REST modernes ?
- a) XML
- b) CSV
- c) JSON
- d) YAML
**Question ouverte O3 *(3 pts)*.** Qu'est-ce que l'**idempotence** d'une méthode HTTP ? Donnez une méthode idempotente et une non idempotente, en justifiant.
> Réponse : ………………………………………………………………………………………………………………………
---
**Q11.** À quoi sert une **clé primaire** ?
- a) À relier deux tables
- b) À identifier de façon unique chaque ligne
- c) À trier les résultats
- d) À chiffrer les données
**Q12.** Une **clé étrangère** permet de…
- a) accélérer les requêtes
- b) créer une relation vers la clé primaire d'une autre table
- c) empêcher les doublons
- d) définir le type d'une colonne
**Q13.** *(Analyse de code)* Qu'affiche cet extrait ?
```python
resultats = [("srv-web-01", "192.168.1.10"), ("sw-access-02", "192.168.1.2")]
print(resultats[1][0])
```
- a) `srv-web-01`
- b) `192.168.1.10`
- c) `sw-access-02`
- d) `192.168.1.2`
**Question ouverte O4 *(3 pts)*.** Expliquez ce qu'est une **clé étrangère** et à quoi elle sert, à l'aide d'un exemple `equipement` / `interface`.
> Réponse : ………………………………………………………………………………………………………………………
**Q14.** En SQL, quelle commande permet de **lire** des données ?
- a) `INSERT`
- b) `SELECT`
- c) `UPDATE`
- d) `DELETE`
**Q15.** Pourquoi utiliser des `?` (requête paramétrée) avec `sqlite3` ?
- a) C'est plus rapide
- b) Pour éviter les injections SQL
- c) C'est obligatoire
- d) Pour trier les résultats
**Q16.** « Un équipement possède plusieurs interfaces » : de quel type de relation s'agit-il ?
- a) Un-à-un (1-1)
- b) Un-à-plusieurs (1-N)
- c) Plusieurs-à-plusieurs (N-N)
- d) Aucune relation
**Q17.** Dans SQLAlchemy 2.0, quel objet gère les opérations `add()` / `commit()` ?
- a) `Engine`
- b) `Session`
- c) `Model`
- d) `Cursor`
**Question ouverte O5 *(4 pts — analyse de code)*.** Identifiez la **faille de sécurité** de cet extrait et **corrigez-la**.
```python
hostname = input("hostname ? ")
cur.execute(f"SELECT * FROM equipement WHERE hostname = '{hostname}'")
```
> Réponse : ………………………………………………………………………………………………………………………
**Q18.** Que fait `cursor.fetchall()` ?
- a) Exécute la requête
- b) Valide la transaction
- c) Renvoie toutes les lignes du résultat
- d) Ferme la connexion
**Q19.** Une relation **N-N** (plusieurs-à-plusieurs) nécessite…
- a) uniquement une clé primaire composite
- b) une table de liaison (d'association)
- c) un index
- d) rien de particulier
**Q20.** Quel est un **avantage** de l'ORM par rapport au SQL brut ?
- a) Il est toujours plus rapide
- b) Il manipule des objets Python et facilite le changement de SGBD
- c) Il supprime le besoin de base de données
- d) Il empêche toute erreur
**Question ouverte O6 *(3 pts — analyse de code)*.** Après ce code, la ligne n'apparaît pas dans la base. **Pourquoi** et **comment corriger** ?
```python
conn = sqlite3.connect("inventaire.db")
cur = conn.cursor()
cur.execute("INSERT INTO equipement (hostname, ip, type) VALUES (?, ?, ?)",
("srv-dns-01", "192.168.1.20", "serveur"))
conn.close()
```
> Réponse : ………………………………………………………………………………………………………………………
---
**Q21.** Flask est un…
- a) framework full-stack
- b) micro-framework
- c) ORM
- d) serveur web de production
**Q22.** *(Analyse de code)* Que renvoie cette vue Flask ?
```python
@app.route("/api/statut")
def statut():
return {"etat": "ok"}
```
- a) Une erreur 500
- b) Du texte brut
- c) Une réponse **JSON** avec le code **200**
- d) Une réponse 204 sans contenu
**Q23.** Quel décorateur associe une URL à une fonction en Flask ?
- a) `@app.get_route`
- b) `@app.route`
- c) `@route`
- d) `@flask.url`
**Question ouverte O7 *(3 pts)*.** À quoi sert la bibliothèque `requests` ? Donnez un exemple d'appel **GET** avec un **timeout**.
> Réponse :
> ```python
>
> ```
**Q24.** Comment lit-on le corps **JSON** d'une requête entrante en Flask ?
- a) `request.body`
- b) `request.get_json()`
- c) `request.json_body`
- d) `flask.read()`
**Q25.** Avec `requests`, comment envoyer un corps **JSON** dans un POST ?
- a) `data=...`
- b) `body=...`
- c) `json=...`
- d) `payload=...`
**Q26.** Que fait `response.raise_for_status()` ?
- a) Fixe le code de statut
- b) Lève une exception si le code est ≥ 400
- c) Convertit la réponse en JSON
- d) Ferme la connexion
**Q27.** En Jinja2, quelle syntaxe **affiche** une variable ?
- a) `{% variable %}`
- b) `{{ variable }}`
- c) `${variable}`
- d) `<%= variable %>`
**Question ouverte O8 *(4 pts — analyse de code)*.** Un client envoie `POST /api/equipements` et reçoit `405 Method Not Allowed`. Voici la route. **Pourquoi** ? **Corrigez-la.**
```python
@app.route("/api/equipements")
def creer_equipement():
donnees = request.get_json()
...
```
> Réponse : ………………………………………………………………………………………………………………………
**Q28.** En Jinja2, quelle syntaxe permet d'écrire une **boucle** ?
- a) `{{ for e in liste }}`
- b) `{% for e in liste %} … {% endfor %}`
- c) `<for e in liste>`
- d) `@for(e in liste)`
**Q29.** Quel moteur de templates Flask utilise-t-il par défaut ?
- a) Mako
- b) Jinja2
- c) Django Templates
- d) Handlebars
**Q30.** Comment renvoyer un JSON **avec le code 201** en Flask ?
- a) `return jsonify(x)`
- b) `return jsonify(x), 201`
- c) `return 201, x`
- d) `return status(201)`
**Question ouverte O9 *(3 pts)*.** Écrivez une route Flask `GET /api/equipements/<int:equipement_id>` qui renvoie l'équipement en JSON, ou une erreur **404** s'il n'existe pas (liste `equipements` en mémoire). Expliquez brièvement votre code.
> Réponse :
> ```python
>
> ```
---
**Q31.** Django est un framework…
- a) micro
- b) full-stack (« batteries included »)
- c) uniquement pour les API
- d) uniquement frontend
**Q32.** Que signifie l'architecture **MTV** de Django ?
- a) Model-Template-View
- b) Model-View-Template
- c) Method-Type-Value
- d) Module-Test-Vue
**Q33.** Quelle commande **génère** les fichiers de migration ?
- a) `migrate`
- b) `makemigrations`
- c) `startapp`
- d) `runserver`
**Question ouverte O10 *(3 pts)*.** Qu'est-ce qu'une **migration** Django et pourquoi en a-t-on besoin ? (une analogie est acceptée)
> Réponse : ………………………………………………………………………………………………………………………
**Q34.** Quelle commande **applique** les migrations à la base de données ?
- a) `makemigrations`
- b) `migrate`
- c) `syncdb`
- d) `commit`
**Q35.** *(Analyse de code)* L'équipement 999 n'existe pas. Que se passe-t-il ?
```python
Equipement.objects.get(id=999)
```
- a) Renvoie `None`
- b) Renvoie `[]`
- c) Lève `Equipement.DoesNotExist`
- d) Renvoie une erreur 404
**Q36.** Dans DRF, le rôle d'un **sérialiseur** est de…
- a) router les URLs
- b) convertir les objets ↔ JSON et valider les données
- c) gérer les sessions
- d) créer la base de données
**Q37.** Quelle classe DRF fournit **automatiquement** tout le CRUD ?
- a) `APIView`
- b) `ModelViewSet`
- c) `Serializer`
- d) `Router`
**Question ouverte O11 *(4 pts — analyse de code)*.** Cette vue déclenche l'erreur « *didn't return an HttpResponse object* ». **Pourquoi** ? **Corrigez-la.**
```python
def liste_equipements(request):
return Equipement.objects.filter(actif=True)
```
> Réponse : ………………………………………………………………………………………………………………………
**Q38.** À quoi sert un `DefaultRouter` en DRF ?
- a) À valider les données
- b) À générer automatiquement les URLs d'un ViewSet
- c) À sérialiser les objets
- d) À authentifier les utilisateurs
**Q39.** La permission `IsAuthenticatedOrReadOnly` autorise…
- a) tout à tout le monde
- b) la lecture à tous, l'écriture aux utilisateurs authentifiés
- c) rien aux anonymes
- d) l'écriture à tous
**Q40.** Quel attribut booléen du modèle `User` donne accès à l'interface `/admin/` ?
- a) `is_active`
- b) `is_staff`
- c) `is_superuser`
- d) `is_admin`
**Question ouverte O12 *(4 pts)*.** Écrivez un `EquipementSerializer` (DRF `ModelSerializer`) exposant `id`, `hostname`, `ip`, `actif`, puis expliquez en une phrase la différence entre `APIView` et `ModelViewSet`.
> Réponse :
> ```python
>
> ```
**Q41.** Un utilisateur authentifié accède aux équipements d'un autre en modifiant simplement l'identifiant dans l'URL (/api/equipements/42/). De quel risque OWASP s'agit-il ?
- a) API8 — Security Misconfiguration
- b) API1 — Broken Object Level Authorization (BOLA)
- c) API7 — Server Side Request Forgery
- d) API4 — Unrestricted Resource Consumption
**Q42.** Dans un ViewSet DRF, quelle technique garantit que chaque utilisateur ne voie que ses ressources ?
- a) Mettre fields = "__all__" dans le sérialiseur
- b) Surcharger get_queryset() en filtrant sur request.user
- c) Définir permission_classes = [AllowAny]
- d) Activer la pagination
**Q43.** Pourquoi éviter fields = "__all__" dans un ModelSerializer exposé par une API ?
- a) Cela ralentit les requêtes
- b) Cela désactive l'authentification
- c) Cela peut exposer des champs sensibles et autoriser le mass assignment (API3 BOPLA)
- d) Cela empêche la pagination
**Q44.** Une API récupère une URL fournie par l'utilisateur (webhook). Quelle est la meilleure protection contre la SSRF (API7) ?
- a) Augmenter le timeout de la requête
- b) Tenir une liste noire de chaînes comme "127.0.0.1"
- c) Valider le schéma puis bloquer les IP privées/loopback/link-local (ou utiliser une allowlist)
- d) Renvoyer le corps de la réponse distante au client
**Q45.** Quel code de statut HTTP DRF renvoie-t-il lorsqu'un client dépasse la limite de débit (throttling, API4) ?
- a) 401 Unauthorized
- b) 403 Forbidden
- c) 429 Too Many Requests
- d) 500 Internal Server Error
**Question ouverte O13** - Trouver et corriger la faille (analyse de code)
Le développeur veut renvoyer « mes équipements ». Voici sa vue DRF. Identifiez la faille, nommez le risque OWASP, expliquez l'impact, puis corrigez le code.
# parc/views.py
from rest_framework.decorators import api_view
from rest_framework.response import Response
from .models import Equipement
from .serializers import EquipementSerializer
@api_view(["GET"])
def mes_equipements(request):
user_id = request.query_params.get("user_id") # ex. /api/mes-equipements/?user_id=1
equipements = Equipement.objects.filter(proprietaire_id=user_id)
return Response(EquipementSerializer(equipements, many=True).data)
> Réponse : ………………………………………………………………………………………………………………………
FIN DU DOCUMENT
+2
View File
@@ -0,0 +1,2 @@
resultats = [("srv-web-01", "192.168.1.10"), ("sw-access-02", "192.168.1.2")]
print(resultats[1][0])