profil.inc.php 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514
  1. <?php
  2. declare(strict_types=1);
  3. $ALLOWED_SYMBOLS = array('#', '_', '-', '!', '[', ']', '=', '~', '*', '@','/','\\','$','%','?','&','(',')');
  4. // --------
  5. // Sécurité
  6. // --------
  7. if (isValidConstant() != 1) {
  8. fermeSessionUtilisateur();
  9. }
  10. use chillerlan\QRCode\QRCode;
  11. use chillerlan\QRCode\QROptions;
  12. require_once("lib/Password.php");
  13. require_once("lib/lib2fa.inc.php");
  14. // Style spécial pour le profil
  15. echo "<link type=\"text/css\" href=\"css/style_viewer.css\" rel=\"stylesheet\" />\n";
  16. // Gestionnaire de mot de passe JavaScript
  17. // https://www.cssscript.com/check-strength-passwords-pwstrength/
  18. // https://www.netwrix.com/password_best_practice.html
  19. // Retenir les 10 derniers mots de passe pour éviter une réutilisation.
  20. // Rotation au 6 mois
  21. // Notification par courriel de l'expiration du mot de passe.
  22. // En cas de doute, changer le mot de passe pour votre sécurité.
  23. // Possibilité d'utiliser une phrase comme mot de passe.
  24. // Proposer un générateur de mot de passe pour navigateur.
  25. // Historique des mots de passe
  26. // https://phpgurukul.com/maintain-password-history-using-php-mysql/
  27. // https://huboftutorials.com/how-to-maintain-password-history-using-php-and-mysql/
  28. // ------------------------------
  29. // Caractéristiques du module
  30. // ------------------------------
  31. unset($module);
  32. $module = array(
  33. "titre" => "Profil de l'utilisateur",
  34. "urldest" => $SCRIPT_NAME . "?page=profil",
  35. "recherche" => array("engin" => false),
  36. "jeton" => $jeton,
  37. "compte" => 0,
  38. "taille" => "800px",
  39. "msg" => array(
  40. "maj_ok" => "Mise à jour du mot de passe effectuée avec succès.",
  41. "maj_echec" => "L'opération de mise à jour du mot de passe a échouée.",
  42. "maj_info_ok" => "Mise à jour des informations personnelles effectuée avec succès.",
  43. "maj_info_echec" => "L'opération de mise à jour des informations personnelles a échouée.",
  44. "maj_jeton_ok" => "Mise à jour du jeton de sécurité effectuée avec succès.",
  45. "maj_jeton_echec" => "L'opération de mise à jour du jeton de sécurité a échouée."
  46. )
  47. );
  48. $sqlparam["connexion"] = "maitre";
  49. $sqlparam["table"][] = "administration";
  50. $sqlparam["where"][] = "refutilisateur =" . $_SESSION["noutilisateur"];
  51. $result = executerRequeteSql($sqlparam);
  52. if (isset($result) && !empty($result)) {
  53. $lcellulaire = $result[0]["cellulaire"];
  54. $lfournisseur = $result[0]["fournisseur"];
  55. }
  56. // ------------------
  57. // Requête sur le tri
  58. // ------------------
  59. if (isset($action)) {
  60. $msg = "";
  61. //Cueillette de données du formulaire
  62. if ($action == "Changer votre mot de passe") {
  63. $password = new PHPPassword\Password(array(
  64. 'minLength' => 12,
  65. 'minNumbers' => 3,
  66. 'minLetters' => 6,
  67. 'minLowerCase' => 3,
  68. 'minUpperCase' => 3,
  69. 'minSymbols' => 2,
  70. 'maxSymbols' => 3,
  71. 'allowedSymbols' => $ALLOWED_SYMBOLS
  72. ));
  73. $pattern = "/[^a-zA-Z]/";
  74. if (!empty(filter_input(INPUT_POST, 'motDePasseActuel'))) {
  75. $motDePasseActuel = antiInjection(filter_input(INPUT_POST, 'motDePasseActuel'));
  76. }
  77. if (!empty(filter_input(INPUT_POST, 'motDePasseNouveau'))) {
  78. $motDePasseNouveau = antiInjection(filter_input(INPUT_POST, 'motDePasseNouveau'));
  79. }
  80. if (!empty(filter_input(INPUT_POST, 'confirmation'))) {
  81. $confirmation = antiInjection(filter_input(INPUT_POST, 'confirmation'));
  82. }
  83. $badResult = false;
  84. //Mot de passe actuel vide
  85. if (empty($motDePasseActuel)) {
  86. $badResult = true;
  87. $msg = "Le mot de passe actuel est vide.";
  88. }
  89. //Nouveau mot de passe actuel vide
  90. if (!$badResult && empty($motDePasseNouveau)) {
  91. $badResult = true;
  92. $msg = "Le nouveau mot de passe est vide.";
  93. }
  94. //confirmation actuel vide
  95. if (!$badResult && empty($confirmation)) {
  96. $badResult = true;
  97. $msg = "La confirmation est vide.";
  98. }
  99. // Le même que celui actuellement utilisé ?
  100. if (!$badResult && $motDePasseNouveau === $motDePasseActuel) {
  101. $badResult = true;
  102. $msg = "Le nouveau mot de passe est identique à celui actuellement utilisé.";
  103. }
  104. // Mot de passe différent ?
  105. if (!$badResult && $motDePasseNouveau !== $confirmation) {
  106. $badResult = true;
  107. $msg = "Le nouveau mot de passe diffère de la confirmation.";
  108. }
  109. // Mot de passe presque identique ou similaire
  110. if (!$badResult && levenshtein($motDePasseActuel, $motDePasseNouveau) <= 4) {
  111. $badResult = true;
  112. $msg = "Le nouveau mot de passe est trop similaire au mot de passe actuel.";
  113. }
  114. // Respecte la politique de création des mots de passe ?
  115. //TODO en faire une négation pour validatePassword.
  116. if (!$badResult && $password->validatePassword($motDePasseNouveau)) {
  117. $badResult = true;
  118. $msg = "Le nouveau mot de passe ne respecte pas la politique de création des mots de passe.";
  119. }
  120. // Prévention contre les attaques de dictionnaire
  121. if (!$badResult) {
  122. unset($sqlparam);
  123. $sqlparam["connexion"] = "maitre";
  124. $sqlparam["table"][] = "dictionnaire_mdp";
  125. $sqlparam["where"][] = "entree like '%" . $motDePasseNouveau . "%'";
  126. $result = executerRequeteSql($sqlparam);
  127. if (isset($result) && !empty($result)) {
  128. $badResult = true;
  129. $msg = "Le nouveau mot de passe est sujet à des attaques. Veuillez le changer";
  130. }
  131. if (!$badResult) {
  132. $valeur = preg_replace($pattern, $replacement, $motDePasseNouveau);
  133. unset($sqlparam);
  134. $sqlparam["connexion"] = "maitre";
  135. $sqlparam["table"][] = "dictionnaire_mdp";
  136. $sqlparam["where"][] = "entree like '%" . $valeur . "%'";
  137. $result = executerRequeteSql($sqlparam);
  138. if (isset($result)) {
  139. $badResult = true;
  140. $msg = "Le nouveau mot de passe est sujet à des attaques. Veuillez le changer";
  141. }
  142. }
  143. }
  144. // Le nouveau mot de passe ne peut pas être un mot de passe déjà utilisé
  145. if (!$badResult) {
  146. unset($sqlparam);
  147. $sqlparam["connexion"] = "maitre";
  148. $sqlparam["table"][] = "historique_mdp";
  149. $sqlparam["where"][] = "noutilisateur = " . $_SESSION["noutilisateur"];
  150. $sqlparam["champs"][] = "hashloginmps";
  151. $sqlparam["limite"] = 10;
  152. $result = executerRequeteSql($sqlparam);
  153. if (isset($result)) {
  154. foreach ($result as &$valeur) {
  155. if (password_verify($motDePasseNouveau, $valeur["hashloginmps"])) {
  156. $badResult = true;
  157. $msg = "Vous avez déjà utilisé ce mot de passe auparavant.";
  158. break;
  159. }
  160. }
  161. }
  162. }
  163. // Mise à jour du mot de passe si tout est ok.
  164. if (!$badResult) {
  165. unset($sqlparam);
  166. $sqlparam["connexion"] = "maitre";
  167. $sqlparam["type"] = "UPDATE";
  168. $sqlparam["table"][] = "administration";
  169. $sqlparam["champs"]["hashloginmps"] = sprintf("'%s'", password_hash($motDePasseNouveau, PASSWORD_DEFAULT));
  170. $sqlparam["champs"]["temporaire"] = 0;
  171. $sqlparam["where"][] = "refutilisateur =" . $_SESSION["noutilisateur"];
  172. $result = executerRequeteSql($sqlparam);
  173. if (isset($result)) {
  174. $module["resultatSQL"] = true;
  175. $module["message"] = $module["msg"]["maj_ok"];
  176. unset($sqlparam);
  177. $sqlparam["connexion"] = "maitre";
  178. $sqlparam["table"][] = "administration";
  179. $sqlparam["champs"][] = "hashloginmps";
  180. $sqlparam["where"][] = "refutilisateur =" . $_SESSION["noutilisateur"];
  181. $result = executerRequeteSql($sqlparam);
  182. unset($sqlparam);
  183. $sqlparam["connexion"] = "maitre";
  184. $sqlparam["type"] = "INSERT";
  185. $sqlparam["table"][] = "historique_mdp";
  186. $sqlparam["champs"]["noutilisateur"] = "'" . $_SESSION["noutilisateur"] . "'";
  187. $sqlparam["champs"]["hashloginmps"] = sprintf("'%s'", $result[0]["hashloginmps"]);
  188. executerRequeteSql($sqlparam);
  189. $_SESSION["temporaire"] = 0;
  190. } else {
  191. $module["resultatSQL"] = false;
  192. $module["message"] = $module["msg"]["maj_echec"];
  193. }
  194. } else {
  195. $module["resultatSQL"] = false;
  196. $module["message"] = $msg;
  197. }
  198. } elseif ($action == "Appliquer votre jeton de sécurité") {
  199. if (!empty($jetonsec)) {
  200. $secret = $_SESSION["temporary2fa"];
  201. unset($sqlparam);
  202. $sqlparam["connexion"] = "maitre";
  203. $sqlparam["type"] = "UPDATE";
  204. $sqlparam["table"][] = "administration";
  205. $sqlparam["champs"]["secret2fa"] = "'$secret'";
  206. $sqlparam["where"][] = "refutilisateur =" . $_SESSION["noutilisateur"];
  207. $result = executerRequeteSql($sqlparam);
  208. if (isset($result) && check2fa($secret, $jetonsec)) {
  209. $module["resultatSQL"] = true;
  210. $module["message"] = $module["msg"]["maj_jeton_ok"];
  211. $_SESSION["2fa_set"] = true;
  212. unset($_SESSION["temporary2fa"]);
  213. } else {
  214. //En cas d'erreur, vider le champ
  215. unset($sqlparam);
  216. $sqlparam["connexion"] = "maitre";
  217. $sqlparam["type"] = "UPDATE";
  218. $sqlparam["table"][] = "administration";
  219. $sqlparam["champs"]["secret2fa"] = "NULL";
  220. $sqlparam["where"][] = "refutilisateur =" . $_SESSION["noutilisateur"];
  221. executerRequeteSql($sqlparam);
  222. $module["resultatSQL"] = false;
  223. $module["message"] = $module["msg"]["maj_jeton_echec"];
  224. $_SESSION["2fa_set"] = false;
  225. }
  226. } else {
  227. $module["resultatSQL"] = false;
  228. $module["message"] = "Jeton de sécurité vide";
  229. }
  230. } elseif ($action == "Mettre à jour vos informations personnelles") {
  231. unset($sqlparam);
  232. $sqlparam["connexion"] = "maitre";
  233. $sqlparam["type"] = "UPDATE";
  234. $sqlparam["table"][] = "administration";
  235. $sqlparam["champs"]["cellulaire"] = "'$cellulaire'";
  236. $sqlparam["champs"]["fournisseur"] = "'$fournisseur'";
  237. $sqlparam["where"][] = "refutilisateur =" . $_SESSION["noutilisateur"];
  238. $result = executerRequeteSql($sqlparam);
  239. if (isset($result)) {
  240. $module["resultatSQL"] = true;
  241. $module["message"] = $module["msg"]["maj_info_ok"];
  242. } else {
  243. $module["resultatSQL"] = false;
  244. $module["message"] = $module["msg"]["maj_info_echec"];
  245. }
  246. }
  247. }
  248. //Afficher les symboles permis dans le mot de passe.
  249. function afficheSymboles($ALLOWED_SYMBOLS) {
  250. $symboles="";
  251. foreach ($ALLOWED_SYMBOLS as $clef => $valeur) {
  252. $symboles .= "&nbsp;".$valeur."&nbsp;";
  253. }
  254. return $symboles;
  255. }
  256. // Barre de menu
  257. print preparerModule($module);
  258. ?>
  259. <form Method="Post" Action="index2.php?page=profil" name="formulaire">
  260. <?php
  261. if ($_SESSION["temporaire"] == 0 && $_SESSION["2fa_set"]) {
  262. ?>
  263. <div class="boitelogin2">
  264. <fieldset>
  265. <legend>Zone de contrôle 1</legend>
  266. <div class='mdp'>
  267. <div style='width:200px; float:left;'>
  268. <label for="motDePasseActuel">Mot de passe actuel : </label>
  269. </div>
  270. <input type="text" id="motDePasseActuel" name="motDePasseActuel"/>
  271. </div>
  272. <div style='clear:both;'></div>
  273. <div class='mdp'>
  274. <div style='width:200px; float:left;'>
  275. <label for="motDePasseNouveau">Nouveau mot de passe : </label>
  276. </div>
  277. <input type="text" id="motDePasseNouveau" name="motDePasseNouveau"/>
  278. </div>
  279. <div style='clear:both;'></div>
  280. <div class='mdp'>
  281. <div style='width:200px; float:left;'>
  282. <label for="Confirmation">Confirmation : </label>
  283. </div>
  284. <input type="text" id="confirmation" name="confirmation"/>
  285. </div>
  286. <br />
  287. <input type="submit" class="button2" name="action" value="Changer votre mot de passe"/>
  288. <input type="hidden" name="jeton" value="<?php print $jeton; ?>"/>
  289. </fieldset>
  290. <br />
  291. Règles pour la création d'un mot de passe :<br>
  292. <ul>
  293. <li>Longueur minimale : 12 caractères</LI>
  294. <li>Minimum de 6 lettres</li>
  295. <li>2 nombres présents (0-9)</li>
  296. <li>3 caractères en minuscule</li>
  297. <li>3 caractères en majuscule</li>
  298. <li> Présence de 2 à 3 caractères spéciaux ci-dessous<br>
  299. <pre><?php print afficheSymboles($ALLOWED_SYMBOLS); ?></pre>
  300. </li>
  301. </ul>
  302. Truc : L'emploi d'une phrase facile à retenir est aussi possible.<br/>
  303. <i>Exemple : L'été sera Merveilleux au Chalet en 2030![</i>
  304. </div>
  305. <div class="boitelogin2">
  306. <fieldset>
  307. <legend>Informations personnelles</legend>
  308. <div class='mdp'>
  309. <div style='width:125px; float:left;'>
  310. <label for="cellulaire">Cellulaire : </label>
  311. </div>
  312. <input type="text" id="cellulaire" name="cellulaire" value="<?php print $lcellulaire; ?>" SIZE='14' MAXLENGTH='14'/>
  313. </div>
  314. <div style='clear:both;'></div>
  315. <div class='mdp'>
  316. <div style='width:125px; float:left;'>
  317. <label for="fournisseur">Fournisseur : </label>
  318. </div>
  319. <?php
  320. unset($result2);
  321. $result2 = getFournisseursCellulaire();
  322. unset($param);
  323. $param["form"] = "menu2";
  324. $param["nom"] = "fournisseur";
  325. $param["etq"] = "";
  326. $param["defaut"] = $lfournisseur;
  327. $param["donnees"] = $result2;
  328. $param["menu"] = true;
  329. //TODO Mettre un évènement vide pour contourner le gestionnaire interne. À corriger.
  330. $param["evt"] = "blank";
  331. print formListbox2($param);
  332. ?>
  333. </div>
  334. <div style='clear:both;'></div>
  335. <br />
  336. <input type="submit" class="button2" name="action" value="Mettre à jour vos informations personnelles"/>
  337. <input type="hidden" name="jeton" value="<?php print $jeton; ?>"/>
  338. </fieldset>
  339. </div>
  340. <script type='text/javascript'>
  341. // Mise en place des masques de saisie
  342. $(document).ready(function () {
  343. $("#cellulaire").mask("(999) 999-9999");
  344. });
  345. </script>
  346. <?php
  347. } else if ($_SESSION["temporaire"] == 1 && (!$_SESSION["2fa_set"] || empty($_SESSION["2fa_set"]))) {
  348. ?>
  349. <div class="boitelogin">
  350. <fieldset>
  351. <legend>Zone de contrôle2</legend>
  352. <div class='mdp'>
  353. <div style='width:200px; float:left;'>
  354. <label for="motDePasse">Mot de passe actuel : </label>
  355. </div>
  356. <input type="text" id="motDePasseActuel" name="motDePasseActuel"/>
  357. </div>
  358. <div style='clear:both;'></div>
  359. <div class='mdp'>
  360. <div style='width:200px; float:left;'>
  361. <label for="motDePasseNouveau">Nouveau mot de passe : </label>
  362. </div>
  363. <input type="text" id="motDePasseNouveau" name="motDePasseNouveau"/>
  364. </div>
  365. <div style='clear:both;'></div>
  366. <div class='mdp'>
  367. <div style='width:200px; float:left;'>
  368. <label for="Confirmation">Confirmation : </label>
  369. </div>
  370. <input type="text" id="confirmation" name="confirmation"/>
  371. </div>
  372. <br />
  373. <input type="submit" class="button2" name="action" value="Changer votre mot de passe"/>
  374. <input type="hidden" name="jeton" value="<?php print $jeton; ?>"/>
  375. </fieldset>
  376. <br />
  377. Règles pour la création d'un mot de passe :<br>
  378. <ul>
  379. <li>Longueur minimale : 12 caractères</LI>
  380. <li>Minimum de 6 lettres</li>
  381. <li>Minimum de 2 nombres présents (0-9)</li>
  382. <li>Minimum de 3 lettres en minuscule</li>
  383. <li>Minimum de 3 lettres en majuscule</li>
  384. <li>Présence de 2 à 3 caractères spéciaux <br>
  385. <?php
  386. //(&nbsp;'#',&nbsp;&nbsp;'_',&nbsp;&nbsp;'-',&nbsp;&nbsp;'!',&nbsp;&nbsp;'[',&nbsp;&nbsp;']',&nbsp;&nbsp;'=',&nbsp;'~',&nbsp;&nbsp;'*'&nbsp;)
  387. print afficheSymboles($ALLOWED_SYMBOLS);
  388. ?>
  389. </li>
  390. </ul>
  391. Truc : L'emploi d'une phrase facile à retenir est aussi possible.<br/>
  392. Exemple : L'été sera Merveilleux AU chalet en 2030!*
  393. </div>
  394. <?php
  395. } else if (!$_SESSION["2fa_set"] || empty($_SESSION["2fa_set"])) {
  396. $secret = getUser2faSecret();
  397. $_SESSION["temporary2fa"] = $secret;
  398. ?>
  399. <div class = 'authenticator'>
  400. <center><h1>Nouveau</h1></center>
  401. <p>
  402. Ce nouveau processus renforce la sécurité de connexion à l'application et
  403. s'assure que l'utilisateur est vraiment vous, comme personne.
  404. </p>
  405. <p>
  406. Désormais, l'utilisation d'une application sera requise pour votre sécurité.
  407. Ce processus est simple.
  408. </p>
  409. <ol>
  410. <li>
  411. Installer l'application Microsoft Authenticator sur votre téléphone cellulaire.<br>
  412. <a href="https://www.microsoft.com/fr-ca/security/mobile-authenticator-app">Cliquer ici</a>
  413. <br>
  414. <img id = "authenticator" src = "images/ms_authenticator.png" alt = "Microsoft Authenticator" width = "400" height = "96" />
  415. <br>
  416. <span style="color:blue;font-size:11px;">
  417. <i>Cliquer sur l'image pour l'agrandir.</i>
  418. </span>
  419. </li>
  420. <li>
  421. <p>
  422. Dans l'application Microsoft Authenticator, effectuer ces quelques étapes.
  423. </p>
  424. <ol>
  425. <li>Cliquer sur le symbole «+» pour ajouter un compte</li>
  426. <li>Choisissez le type de compte professionnel ou scolaire</li>
  427. <li>Lire (scanner) le code QR ci-dessous afin
  428. d'ajouter le site web des libérations comme émetteur de jeton de sécurité</li>
  429. </ol>
  430. <p>
  431. <?php
  432. // Voir la librairie lib2fa pour savoir comment créer des code qr avec Microsoft Authenticator.
  433. //TODO changer l'émetteur dans l'adresse de l'image
  434. $data = "otpauth://totp/liberations:" . $_SESSION["adressecourriel"] . "?secret=$secret&issuer=https://liberations.infinityfreeapp.com/";
  435. print "<img src='" . (new QRCode)->render($data) . "' alt='Code QR' />";
  436. ?>
  437. </p>
  438. </li>
  439. <li>
  440. Inscrire un jeton de sécurité provenant de Microsoft Authenticator afin d'activer la sécurité renforcée
  441. et confirmer que la nouvelle sécurité est active.
  442. <div class='mdp'>
  443. <div style='width:200px; float:left;'>
  444. <label for="jeton">Jeton de sécurité : </label>
  445. </div>
  446. <input type="text" id="jetonsec" name="jetonsec"/>
  447. </div>
  448. <br />
  449. <input type="submit" class="button2" name="action" value="Appliquer votre jeton de sécurité"/>
  450. </li>
  451. </ol>
  452. </div>
  453. <div id="image-viewer">
  454. <span class="close">&times;</span>
  455. <img class="modal-content" id="full-image">
  456. </div>
  457. <script type='text/javascript'>
  458. $("#authenticator").click(function () {
  459. $("#full-image").attr("src", $(this).attr("src"));
  460. $('#image-viewer').show();
  461. });
  462. $("#image-viewer .close").click(function () {
  463. $('#image-viewer').hide();
  464. });
  465. </script>
  466. <?php
  467. }
  468. ?>
  469. </form>