Paillasse/portail/public/js/contenus-plus-4.js
Adam Belkacemi 30e14b1c54 ajoute le portail Laravel : connexion prof/élève, session unique, gestion de classe, salle d'attente
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-10-08 02:01:58 +02:00

69 lines
22 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Niveau 4 : parties de CM supplémentaires, exercice résolu, fiche mémo, vrai/faux
(function () {
var X = window.CONTENUS_PLUS = window.CONTENUS_PLUS || {};
var add = function (id, o) { var c = X[id] = X[id] || {}; c.cm = (c.cm || []).concat(o.cm || []); c.qcm = (c.qcm || []).concat(o.qcm || []); c.memo = o.memo; c.resolu = o.resolu; c.vf = o.vf; };
add('binaire', {
cm: [{ t: 'Nombres à virgule : IEEE 754 en bref', d: '15 min', html: 'Un <b>float</b> est stocké en trois parties : un bit de signe, un exposant et une mantisse, comme une notation scientifique en base 2. La plupart des décimaux n’ont pas d’écriture binaire finie : 0,1 en binaire est une suite infinie, arrondie.', exemple: '>>> 0.1 + 0.2\n0.30000000000000004\n>>> round(0.1 + 0.2, 2) == 0.3\nTrue', attention: 'Ne jamais comparer deux float avec == : on compare leur écart à une petite tolérance, ou on travaille en entiers (centimes plutôt qu’euros).' },
{ t: 'Le texte aussi est un nombre : ASCII et UTF-8', d: '15 min', html: 'Chaque caractère a un numéro. <b>ASCII</b> code 128 caractères sur 7 bits (« A » = 65). <b>UTF-8</b> code tous les caractères du monde sur 1 à 4 octets et reste compatible avec ASCII. Les accents cassés (« é » au lieu de « é ») viennent d’un fichier UTF-8 lu comme du Latin-1.', exemple: '>>> ord("A"), ord("é")\n(65, 233)\n>>> "é".encode("utf-8")\nb\'\\xc3\\xa9\' # 2 octets' }],
qcm: [['Pourquoi 0.1 + 0.2 ne vaut pas exactement 0.3 en Python ?', ['Bug de Python', '0,1 n’a pas d’écriture binaire finie', 'Le processeur est trop lent', 'Il faut importer math'], 1, 'La valeur est arrondie à la représentation la plus proche.'], ['Combien d’octets pour « é » en UTF-8 ?', ['1', '2', '3', '4'], 1, '0xC3 0xA9.']],
memo: [['Conversion', 'Méthode'], ['binaire → décimal', 'additionner les poids des bits à 1'], ['décimal → binaire', 'divisions par 2, restes de bas en haut'], ['binaire ↔ hexa', 'groupes de 4 bits depuis la droite'], ['négatif sur n bits', 'inverser puis +1'], ['n bits', '2ⁿ valeurs, de 0 à 2ⁿ − 1'], ['1 octet', '8 bits, 0 à 255, 0x00 à 0xFF']],
resolu: { enonce: 'Le poste 192.168.1.77/27 et le poste 192.168.1.100/27 sont-ils dans le même sous-réseau ?', etapes: [['Identifier l’octet utile', '/27 = 24 + 3 : seul le 4e octet est partagé, avec 3 bits de réseau.'], ['Écrire le masque', '3 bits à 1 sur le dernier octet : 1110 0000 = 224.'], ['Convertir les deux octets', '77 = 0100 1101 · 100 = 0110 0100'], ['Appliquer le ET', '0100 1101 & 1110 0000 = 0100 0000 = 64\n0110 0100 & 1110 0000 = 0110 0000 = 96'], ['Conclure', 'Réseaux .64 et .96 : ils ne sont pas dans le même sous-réseau. Il faut passer par un routeur.']] },
vf: [['Un octet peut valoir 256.', 0, 'Maximum 255.'], ['0xFF = 255.', 1, '15 × 16 + 15.'], ['Sur 8 bits signés, 1000 0000 vaut −128.', 1, 'C’est la plus petite valeur.'], ['1 Kio = 1 000 octets.', 0, '1 Kio = 1 024 octets.']] });
add('terminal', {
cm: [{ t: 'Lire et éditer un fichier', d: '15 min', html: '<code>cat</code> affiche un fichier court, <code>less</code> un fichier long (flèches, / pour chercher, q pour quitter), <code>head -n 20</code> et <code>tail -n 20</code> le début et la fin. <code>nano</code> est l’éditeur le plus simple : Ctrl+O enregistre, Ctrl+X quitte.', exemple: 'less /var/log/syslog\ntail -n 50 /var/log/auth.log\nnano ~/notes.txt' },
{ t: 'Processus et services', d: '20 min', html: 'Chaque programme lancé est un <b>processus</b> avec un numéro (PID). <code>ps aux</code> les liste, <code>top</code> ou <code>htop</code> les montre en direct, <code>kill PID</code> en arrête un. Les <b>services</b> (serveur web, SSH) sont gérés par systemd avec <code>systemctl</code>.', exemple: 'ps aux | grep apache2\nsystemctl status ssh\nsudo systemctl restart apache2\njournalctl -u ssh --since "1 hour ago"' },
{ t: 'Espace disque et archives', d: '10 min', html: '<code>df -h</code> montre l’occupation des disques, <code>du -sh dossier</code> la taille d’un dossier. <code>tar czf archive.tar.gz dossier/</code> crée une archive compressée, <code>tar xzf</code> l’extrait.' }],
qcm: [['Quelle commande montre les processus en temps réel ?', ['ps', 'top', 'ls', 'df'], 1, 'top (ou htop) se rafraîchit en continu.'], ['Comment redémarrer le serveur web ?', ['restart apache2', 'sudo systemctl restart apache2', 'kill apache2', 'apache2 --restart'], 1, 'systemctl gère les services.'], ['Quelle commande donne l’espace libre des disques ?', ['du -sh', 'df -h', 'free', 'ls -h'], 1, 'df = disk free.']],
memo: [['Besoin', 'Commande'], ['Où suis-je ?', 'pwd'], ['Que contient ce dossier ?', 'ls -la'], ['Aller dans un dossier', 'cd chemin'], ['Créer', 'mkdir -p · touch'], ['Copier / déplacer', 'cp -r · mv'], ['Supprimer', 'rm · rm -r'], ['Lire un fichier', 'cat · less · tail -f'], ['Chercher', 'find · grep'], ['Aide', 'man · --help'], ['Services', 'systemctl status|restart'], ['Disque', 'df -h · du -sh']],
resolu: { enonce: 'Le disque du serveur est plein. Trouver ce qui prend de la place et libérer de l’espace sans rien casser.', etapes: [['Mesurer', 'df -h → / est à 98 %.'], ['Localiser', 'sudo du -sh /* 2>/dev/null | sort -h → /var est le plus gros.'], ['Descendre', 'sudo du -sh /var/* | sort -h → /var/log fait 18 Go.'], ['Identifier', 'ls -lhS /var/log | head → un fichier debug.log de 17 Go.'], ['Agir proprement', 'sudo truncate -s 0 /var/log/debug.log puis corriger la configuration qui l’a produit.'], ['Vérifier', 'df -h → / redescend à 41 %. Noter l’intervention.']] },
vf: [['Linux a une corbeille pour rm.', 0, 'rm supprime définitivement.'], ['~ désigne /home/utilisateur.', 1, 'Le dossier personnel.'], ['cd sans argument ramène dans le dossier personnel.', 1, 'Comme cd ~.'], ['Les fichiers cachés commencent par un tiret.', 0, 'Par un point.']] });
add('droits', {
cm: [{ t: 'Droits et services web', d: '15 min', html: 'Le serveur Apache tourne sous l’utilisateur <b>www-data</b>. Pour qu’il lise un site, les fichiers doivent être lisibles par lui ; pour qu’il écrive (dossier d’envoi de fichiers), seul ce dossier doit lui appartenir.', exemple: 'sudo chown -R root:www-data /var/www/site\nsudo find /var/www/site -type d -exec chmod 750 {} \\;\nsudo find /var/www/site -type f -exec chmod 640 {} \\;\nsudo chown www-data /var/www/site/uploads' },
{ t: 'Sécuriser les clés et les secrets', d: '10 min', html: 'SSH refuse une clé privée trop ouverte. Le dossier <code>~/.ssh</code> doit être en 700, la clé privée en 600, <code>authorized_keys</code> en 600. Même règle pour un fichier <code>.env</code> contenant des mots de passe de base de données.' }],
qcm: [['Sous quel compte tourne Apache sous Debian ?', ['root', 'apache', 'www-data', 'nobody'], 2, 'www-data.'], ['Droits attendus pour ~/.ssh/id_ed25519 ?', ['644', '600', '755', '777'], 1, 'Lisible par le seul propriétaire.']],
memo: [['Octal', 'Symbolique', 'Usage typique'], ['600', 'rw-------', 'clé privée, .env'], ['644', 'rw-r--r--', 'fichier public'], ['640', 'rw-r-----', 'fichier de site web'], ['700', 'rwx------', '~/.ssh, dossier privé'], ['750', 'rwxr-x---', 'dossier de site, script'], ['755', 'rwxr-xr-x', 'programme public'], ['2770', 'rwxrws---', 'dossier partagé de groupe'], ['1777', 'rwxrwxrwt', '/tmp']],
resolu: { enonce: 'Le site affiche « 403 Forbidden » après une mise en ligne. Diagnostiquer et corriger.', etapes: [['Lire le journal', 'sudo tail /var/log/apache2/error.log → « Permission denied: /var/www/site/index.html ».'], ['Vérifier le fichier', 'ls -l → -rw------- alice alice : www-data ne peut pas lire.'], ['Vérifier les dossiers', 'namei -l /var/www/site/index.html → tous les dossiers sont traversables.'], ['Corriger', 'sudo chown -R root:www-data /var/www/site && sudo chmod 640 index.html'], ['Tester', 'curl -I localhost → 200 OK.']] },
vf: [['chmod 777 est une bonne solution temporaire.', 0, 'Jamais sur un serveur.'], ['Seul root peut utiliser chown.', 1, 'Pour changer de propriétaire.'], ['Sans x, on ne peut pas entrer dans un dossier.', 1, 'x = traverser.'], ['/etc/shadow est lisible par tous.', 0, 'root uniquement.']] });
add('tubes', {
cm: [{ t: 'xargs et tee', d: '10 min', html: '<code>xargs</code> transforme des lignes en arguments pour une autre commande. <code>tee</code> écrit dans un fichier tout en laissant passer le flux, pratique pour garder une trace.', exemple: 'find . -name "*.log" -mtime +30 | xargs rm\n./diagnostic.sh | tee rapport-$(date +%F).txt' },
{ t: 'Planifier avec cron', d: '15 min', html: '<code>crontab -e</code> ouvre la liste des tâches planifiées. Cinq champs : minute, heure, jour du mois, mois, jour de la semaine.', exemple: '# Tous les jours à 7 h 30\n30 7 * * * /home/admin/alerte-ssh.sh\n# Chaque lundi à minuit\n0 0 * * 1 /home/admin/sauvegarde.sh', attention: 'cron n’a pas le même PATH que vous : utilisez des chemins absolus dans vos scripts.' }],
qcm: [['Que signifie 0 2 * * * dans une crontab ?', ['Toutes les 2 minutes', 'Tous les jours à 2 h', 'Le 2 de chaque mois', 'Toutes les 2 heures'], 1, 'minute 0, heure 2.'], ['À quoi sert tee ?', ['Trier', 'Écrire dans un fichier et à l’écran', 'Compter', 'Compresser'], 1, 'Comme un T de plomberie.']],
memo: [['Syntaxe', 'Effet'], ['cmd > f', 'sortie dans f (écrase)'], ['cmd >> f', 'sortie ajoutée à f'], ['cmd 2> f', 'erreurs dans f'], ['cmd &> f', 'tout dans f'], ['a | b', 'sortie de a → entrée de b'], ['sort | uniq -c | sort -rn', 'classement par fréquence'], ['tail -f f', 'suivre f en direct'], ['$(cmd)', 'insérer la sortie de cmd']],
resolu: { enonce: 'Trouver les 3 utilisateurs qui se sont le plus connectés en SSH cette semaine.', etapes: [['Filtrer', 'grep "Accepted" /var/log/auth.log'], ['Repérer la colonne', 'Ligne type : … Accepted publickey for alice from … → le nom est le 9e champ.'], ['Extraire', "… | awk '{print $9}'"], ['Compter', '… | sort | uniq -c'], ['Classer', '… | sort -rn | head -3'], ['Résultat', ' 42 alice\n 17 bob\n 3 admin']] },
vf: [['> ajoute à la fin du fichier.', 0, 'Il écrase ; >> ajoute.'], ['uniq -c compte les doublons même non consécutifs.', 0, 'Il faut trier avant.'], ['2>/dev/null masque les erreurs.', 1, 'Elles sont jetées.'], ['grep -i ignore la casse.', 1, 'i = insensitive.']] });
add('osi', {
cm: [{ t: 'Le DNS : l’annuaire d’Internet', d: '15 min', html: 'Le <b>DNS</b> traduit un nom (www.lycee.fr) en adresse IP. Le poste interroge un résolveur, qui remonte si besoin la hiérarchie : racine, puis .fr, puis lycee.fr. Les réponses sont gardées en cache.', exemple: '$ dig +short www.lycee.fr\n93.184.216.34\n$ nslookup www.lycee.fr' },
{ t: 'Le DHCP : obtenir une adresse automatiquement', d: '10 min', html: 'À la connexion, le poste diffuse une demande. Le serveur <b>DHCP</b> lui attribue une IP, un masque, une passerelle et des serveurs DNS pour une durée donnée (le bail). Quatre messages : Discover, Offer, Request, Ack.' }],
qcm: [['Quel service traduit un nom en adresse IP ?', ['DHCP', 'DNS', 'ARP', 'NAT'], 1, 'DNS.'], ['Dans quel ordre se déroule un échange DHCP ?', ['Offer, Discover, Ack, Request', 'Discover, Offer, Request, Ack', 'Request, Ack, Discover, Offer', 'Ack, Request, Offer, Discover'], 1, 'DORA.']],
memo: [['Couche', 'Adresse', 'Équipement', 'Commande de test'], ['1 Physique', '—', 'câble, hub', 'ip link'], ['2 Liaison', 'MAC', 'switch', 'ip neigh'], ['3 Réseau', 'IP', 'routeur', 'ping, traceroute'], ['4 Transport', 'port', 'pare-feu', 'ss -tlnp'], ['7 Application', 'nom, URL', 'serveur', 'curl, dig']],
resolu: { enonce: 'Un poste obtient l’adresse 169.254.12.7. Que se passe-t-il et comment corriger ?', etapes: [['Reconnaître l’adresse', '169.254.x.x est une adresse auto-attribuée : le poste n’a reçu aucune réponse DHCP.'], ['Couche 1-2', 'ip link → lien UP ; le câble est bon.'], ['Tester le DHCP', 'sudo dhclient -v eth0 → DHCPDISCOVER sans réponse.'], ['Localiser', 'Un autre poste de la même prise obtient une IP : le problème vient du port du switch (VLAN mal affecté).'], ['Corriger et vérifier', 'Remettre le port dans le bon VLAN, relancer dhclient → IP 192.168.10.42.']] },
vf: [['Un routeur travaille en couche 3.', 1, 'Il route selon l’IP.'], ['UDP garantit la livraison.', 0, 'C’est TCP.'], ['L’adresse MAC change à chaque routeur.', 1, 'Nouvelle trame à chaque saut.'], ['Le DNS utilise le port 80.', 0, 'Port 53.']] });
add('sql', {
cm: [{ t: 'Sous-requêtes', d: '15 min', html: 'Une requête peut en contenir une autre, dans le WHERE ou le FROM. Utile pour comparer à une moyenne ou filtrer selon une liste calculée.', exemple: '-- Élèves au-dessus de la moyenne générale\nSELECT nom, moyenne FROM eleves\nWHERE moyenne > (SELECT AVG(moyenne) FROM eleves);\n\n-- Élèves des classes de BTS\nSELECT nom FROM eleves\nWHERE classe_id IN (SELECT id FROM classes WHERE libelle LIKE \'BTS%\');' },
{ t: 'Vues et droits', d: '10 min', html: 'Une <b>vue</b> est une requête enregistrée qu’on interroge comme une table. On donne aux utilisateurs l’accès à la vue plutôt qu’aux tables complètes, pour ne montrer que ce qui est nécessaire.', exemple: 'CREATE VIEW v_moyennes AS\nSELECT c.libelle, AVG(e.moyenne) AS moy FROM eleves e JOIN classes c ON c.id = e.classe_id GROUP BY c.libelle;\nGRANT SELECT ON v_moyennes TO direction;' }],
qcm: [['Qu’est-ce qu’une vue ?', ['Une copie de table', 'Une requête enregistrée interrogeable comme une table', 'Un index', 'Une sauvegarde'], 1, 'Elle ne stocke pas les données.'], ['Que renvoie IN (SELECT …) ?', ['Une erreur', 'Les lignes dont la valeur est dans la liste calculée', 'Un nombre', 'Une jointure'], 1, 'Filtre par appartenance.']],
memo: [['Clause', 'Rôle', 'Exemple'], ['SELECT', 'colonnes', 'SELECT nom, prenom'], ['FROM / JOIN', 'tables', 'FROM eleves e JOIN classes c ON …'], ['WHERE', 'filtrer les lignes', "WHERE ville = 'Lyon'"], ['GROUP BY', 'regrouper', 'GROUP BY classe_id'], ['HAVING', 'filtrer les groupes', 'HAVING COUNT(*) > 20'], ['ORDER BY', 'trier', 'ORDER BY nom DESC'], ['LIMIT', 'limiter', 'LIMIT 10']],
resolu: { enonce: 'Afficher, pour chaque classe, le nombre d’élèves ayant rendu le DM 3 et le taux de rendu.', etapes: [['Identifier les tables', 'classes, eleves, rendus(eleve_id, devoir_id).'], ['Partir des élèves', 'FROM eleves e JOIN classes c ON c.id = e.classe_id'], ['Garder les non-rendus', "LEFT JOIN rendus r ON r.eleve_id = e.id AND r.devoir_id = 3 (condition dans le ON, sinon le LEFT JOIN est annulé)"], ['Regrouper', 'GROUP BY c.libelle'], ['Calculer', 'SELECT c.libelle, COUNT(r.eleve_id) AS rendus, ROUND(100.0 * COUNT(r.eleve_id) / COUNT(e.id)) AS taux']] },
vf: [['WHERE peut contenir COUNT(*).', 0, 'On utilise HAVING.'], ['Une clé primaire peut être NULL.', 0, 'Jamais.'], ['LEFT JOIN garde toutes les lignes de gauche.', 1, 'Avec NULL à droite.'], ['DELETE sans WHERE supprime la table.', 0, 'Il vide la table, qui reste.']] });
add('python', {
cm: [{ t: 'Parcourir listes et dictionnaires', d: '15 min', html: '<code>enumerate</code> donne l’indice et la valeur, <code>.items()</code> les paires clé/valeur d’un dictionnaire. Les <b>listes en compréhension</b> construisent une liste en une ligne.', exemple: 'notes = {"Léa": 12, "Inès": 17, "Tom": 8}\nfor nom, n in notes.items():\n print(nom, n)\n\nadmis = [nom for nom, n in notes.items() if n >= 10]\n# [\'Léa\', \'Inès\']' },
{ t: 'Modules et bibliothèques', d: '10 min', html: '<code>import</code> charge un module. La bibliothèque standard couvre déjà beaucoup : <code>math</code>, <code>random</code>, <code>statistics</code>, <code>datetime</code>, <code>csv</code>, <code>json</code>. Pour la data, on installe pandas et matplotlib.', exemple: 'import statistics as st\nst.mean([12, 17, 8]) # 12.33…\nst.median([12, 17, 8]) # 12' }],
qcm: [['Que produit [x * 2 for x in range(3)] ?', ['[0, 2, 4]', '[2, 4, 6]', '[0, 1, 2]', 'Erreur'], 0, 'range(3) = 0, 1, 2.'], ['Quel module calcule une médiane ?', ['math', 'statistics', 'random', 'csv'], 1, 'statistics.median.']],
memo: [['Besoin', 'Syntaxe'], ['Afficher', 'print(f"{x} ans")'], ['Lire un nombre', 'int(input())'], ['Condition', 'if … : / elif … : / else :'], ['Répéter n fois', 'for i in range(n):'], ['Tant que', 'while condition:'], ['Fonction', 'def f(x): return …'], ['Liste', 'l.append(x) · len(l) · l[-1]'], ['Dictionnaire', 'd[k] · d.get(k, 0) · d.items()'], ['Erreur', 'try: … except ValueError:'], ['Test', 'assert f(2) == 4']],
resolu: { enonce: 'Écrire une fonction qui renvoie la mention d’une liste de notes (moyenne), en refusant une liste vide.', etapes: [['Définir le contrat', 'Entrée : liste de nombres. Sortie : texte. Erreur si liste vide.'], ['Écrire les tests d’abord', 'assert mention([16, 18]) == "TB"\nassert mention([9, 11]) == "Passable"'], ['Calculer la moyenne', 'm = sum(notes) / len(notes)'], ['Gérer le cas limite', 'if not notes: raise ValueError("liste vide")'], ['Choisir la mention', 'if m >= 16: return "TB" … return "Insuffisant"'], ['Lancer les tests', 'Tous les assert passent : la fonction est prête.']] },
vf: [['Python compte les indices à partir de 1.', 0, 'À partir de 0.'], ['input() renvoie un texte.', 1, 'Toujours.'], ['range(5) contient 5.', 0, '0 à 4.'], ['L’indentation est obligatoire.', 1, 'Elle délimite les blocs.']] });
add('web', {
cm: [{ t: 'Formulaires et traitement côté serveur', d: '15 min', html: 'Le formulaire envoie des paires nom=valeur. Côté serveur (PHP, Python), on récupère ces valeurs, on les <b>valide</b>, puis on les enregistre. Toute donnée venant du navigateur est suspecte, même si un champ est marqué required en HTML.', exemple: '<form method="post" action="/inscription">\n <label>Email <input type="email" name="email" required></label>\n <button>Envoyer</button>\n</form>' },
{ t: 'Accessibilité et responsive', d: '15 min', html: 'Un site accessible se lit au clavier et au lecteur d’écran : images avec <code>alt</code>, champs avec <code>label</code>, contrastes suffisants, titres hiérarchisés. En France, le <b>RGAA</b> fixe ces règles pour les services publics. Le <b>responsive</b> adapte la mise en page à l’écran avec les media queries.', exemple: '@media (max-width: 700px) {\n .grille { grid-template-columns: 1fr; }\n}' }],
qcm: [['Pourquoi valider côté serveur si le HTML a required ?', ['Inutile', 'Le navigateur peut être contourné', 'Pour la vitesse', 'Pour le SEO'], 1, 'N’importe qui peut envoyer une requête à la main.'], ['Quel référentiel encadre l’accessibilité des sites publics en France ?', ['RGPD', 'RGAA', 'ANSSI', 'W3C-FR'], 1, 'Référentiel général d’amélioration de l’accessibilité.']],
memo: [['Élément', 'Rôle'], ['<h1>…<h6>', 'titres hiérarchisés'], ['<p>', 'paragraphe'], ['<a href>', 'lien'], ['<img alt>', 'image + texte alternatif'], ['<ul><li>', 'liste'], ['<form method>', 'formulaire GET ou POST'], ['.classe {}', 'règle CSS'], ['display: grid', 'grille'], ['@media', 'adaptation à l’écran'], ['200 · 301 · 403 · 404 · 500', 'codes HTTP clés']],
resolu: { enonce: 'Publier une page « Planning BTS » sur un serveur Debian neuf et la rendre accessible en HTTPS.', etapes: [['Installer', 'sudo apt update && sudo apt install apache2'], ['Publier', 'sudo nano /var/www/html/index.html → page HTML avec h1 et tableau'], ['Vérifier', 'curl -I localhost → 200 OK'], ['Nommer le site', 'Enregistrer planning.lycee.fr dans le DNS vers l’IP du serveur'], ['Passer en HTTPS', 'sudo apt install certbot python3-certbot-apache && sudo certbot --apache -d planning.lycee.fr'], ['Contrôler', 'Ouvrir https://planning.lycee.fr, cadenas présent, puis tail /var/log/apache2/access.log']] },
vf: [['<title> s’affiche dans la page.', 0, 'Dans l’onglet.'], ['POST cache les données dans l’URL.', 0, 'Il les met dans le corps.'], ['Le code 404 signifie « introuvable ».', 1, 'Not Found.'], ['HTTPS utilise le port 443.', 1, 'Par défaut.']] });
add('crypto', {
cm: [{ t: 'Authentification à deux facteurs', d: '10 min', html: 'Un facteur est quelque chose que l’on <b>sait</b> (mot de passe), que l’on <b>possède</b> (téléphone, clé de sécurité) ou que l’on <b>est</b> (empreinte). Combiner deux facteurs rend inutile un mot de passe volé. Les codes TOTP à 6 chiffres changent toutes les 30 secondes.' },
{ t: 'Chiffrer un fichier en pratique', d: '15 min', html: 'Pour transmettre un document sensible, on le chiffre avec un outil éprouvé plutôt qu’avec un mot de passe de fichier ZIP.', exemple: '# Symétrique (mot de passe partagé)\ngpg -c bulletin.pdf # → bulletin.pdf.gpg\ngpg -d bulletin.pdf.gpg > bulletin.pdf\n\n# Vérifier une signature de téléchargement\ngpg --verify debian.iso.sign debian.iso' }],
qcm: [['Un code reçu par application sur son téléphone est un facteur :', ['que l’on sait', 'que l’on possède', 'que l’on est', 'aucun'], 1, 'Le téléphone est possédé.'], ['Que fait gpg -c fichier ?', ['Le compresse', 'Le chiffre avec un mot de passe', 'Le signe', 'Le hache'], 1, '-c = chiffrement symétrique.']],
memo: [['Besoin', 'Outil recommandé'], ['Vérifier un fichier', 'SHA-256 (sha256sum)'], ['Stocker un mot de passe', 'Argon2id ou bcrypt, avec sel'], ['Chiffrer un disque', 'AES-256 (LUKS, BitLocker)'], ['Chiffrer un fichier', 'gpg, age'], ['Se connecter à un serveur', 'SSH + clé Ed25519'], ['Site web', 'TLS 1.2 ou 1.3, certificat valide'], ['Comptes utilisateurs', 'double authentification'], ['À bannir', 'MD5, SHA-1, DES, mots de passe en clair']],
resolu: { enonce: 'Mettre en place une connexion SSH par clé vers le serveur 192.168.10.10 et interdire les mots de passe.', etapes: [['Générer la paire', 'ssh-keygen -t ed25519 -C "prof@lycee" (phrase de passe conseillée)'], ['Déposer la clé publique', 'ssh-copy-id prof@192.168.10.10'], ['Tester avant de fermer', 'ssh prof@192.168.10.10 → connexion sans mot de passe. Garder cette session ouverte.'], ['Durcir sshd', 'sudo nano /etc/ssh/sshd_config → PasswordAuthentication no, PermitRootLogin no'], ['Appliquer', 'sudo sshd -t && sudo systemctl restart ssh'], ['Vérifier', 'Depuis un autre terminal : ssh -o PubkeyAuthentication=no prof@… → refusé.']] },
vf: [['Un hachage se déchiffre avec la bonne clé.', 0, 'Il est à sens unique.'], ['La clé publique peut être diffusée.', 1, 'C’est son rôle.'], ['MD5 convient pour les mots de passe.', 0, 'Cassé et trop rapide.'], ['HTTPS chiffre le trafic avec une clé de session.', 1, 'Symétrique, négociée au départ.']] });
})();