profil.inc.php 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517
  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. $replacement = '';
  133. $valeur = preg_replace($pattern, $replacement, $motDePasseNouveau);
  134. unset($sqlparam);
  135. $sqlparam["connexion"] = "maitre";
  136. $sqlparam["table"][] = "dictionnaire_mdp";
  137. $sqlparam["where"][] = "entree like '%" . $valeur . "%'";
  138. $result = executerRequeteSql($sqlparam);
  139. if (isset($result) && count($result) > 0) {
  140. $badResult = true;
  141. $msg = "Le nouveau mot de passe est sujet à des attaques. Veuillez le changer";
  142. }
  143. }
  144. }
  145. // Le nouveau mot de passe ne peut pas être un mot de passe déjà utilisé
  146. if (!$badResult) {
  147. unset($sqlparam);
  148. $sqlparam["connexion"] = "maitre";
  149. $sqlparam["table"][] = "historique_mdp";
  150. $sqlparam["where"][] = "noutilisateur = " . $_SESSION["noutilisateur"];
  151. $sqlparam["champs"][] = "hashloginmps";
  152. $sqlparam["limite"] = 10;
  153. $result = executerRequeteSql($sqlparam);
  154. if (isset($result)) {
  155. foreach ($result as &$valeur) {
  156. if (password_verify($motDePasseNouveau, $valeur["hashloginmps"])) {
  157. $badResult = true;
  158. $msg = "Vous avez déjà utilisé ce mot de passe auparavant.";
  159. break;
  160. }
  161. }
  162. }
  163. }
  164. // Mise à jour du mot de passe si tout est ok.
  165. if (!$badResult) {
  166. unset($sqlparam);
  167. $sqlparam["connexion"] = "maitre";
  168. $sqlparam["type"] = "UPDATE";
  169. $sqlparam["table"][] = "administration";
  170. $sqlparam["champs"]["motdepasse"] = "'$motDePasseNouveau'";
  171. $sqlparam["champs"]["hashloginmps"] = sprintf("'%s'", password_hash($motDePasseNouveau, PASSWORD_DEFAULT));
  172. $sqlparam["champs"]["temporaire"] = 0;
  173. $sqlparam["where"][] = "refutilisateur =" . $_SESSION["noutilisateur"];
  174. $result = executerRequeteSql($sqlparam);
  175. if (isset($result)) {
  176. $module["resultatSQL"] = true;
  177. $module["message"] = $module["msg"]["maj_ok"];
  178. unset($sqlparam);
  179. $sqlparam["connexion"] = "maitre";
  180. $sqlparam["table"][] = "administration";
  181. $sqlparam["champs"][] = "hashloginmps";
  182. $sqlparam["where"][] = "refutilisateur =" . $_SESSION["noutilisateur"];
  183. $result = executerRequeteSql($sqlparam);
  184. unset($sqlparam);
  185. $sqlparam["connexion"] = "maitre";
  186. $sqlparam["type"] = "INSERT";
  187. $sqlparam["table"][] = "historique_mdp";
  188. $sqlparam["champs"]["noutilisateur"] = "'" . $_SESSION["noutilisateur"] . "'";
  189. $sqlparam["champs"]["hashloginmps"] = sprintf("'%s'", $result[0]["hashloginmps"]);
  190. executerRequeteSql($sqlparam);
  191. $_SESSION["temporaire"] = 0;
  192. } else {
  193. $module["resultatSQL"] = false;
  194. $module["message"] = $module["msg"]["maj_echec"];
  195. }
  196. } else {
  197. $module["resultatSQL"] = false;
  198. $module["message"] = $msg;
  199. }
  200. } elseif ($action == "Appliquer votre jeton de sécurité") {
  201. if (!empty($jetonsec)) {
  202. $secret = $_SESSION["temporary2fa"];
  203. unset($sqlparam);
  204. $sqlparam["connexion"] = "maitre";
  205. $sqlparam["type"] = "UPDATE";
  206. $sqlparam["table"][] = "administration";
  207. $sqlparam["champs"]["secret2fa"] = "'$secret'";
  208. $sqlparam["where"][] = "refutilisateur =" . $_SESSION["noutilisateur"];
  209. $result = executerRequeteSql($sqlparam);
  210. if (isset($result) && check2fa($secret, $jetonsec)) {
  211. $module["resultatSQL"] = true;
  212. $module["message"] = $module["msg"]["maj_jeton_ok"];
  213. $_SESSION["2fa_set"] = true;
  214. unset($_SESSION["temporary2fa"]);
  215. } else {
  216. //En cas d'erreur, vider le champ
  217. unset($sqlparam);
  218. $sqlparam["connexion"] = "maitre";
  219. $sqlparam["type"] = "UPDATE";
  220. $sqlparam["table"][] = "administration";
  221. $sqlparam["champs"]["secret2fa"] = "NULL";
  222. $sqlparam["where"][] = "refutilisateur =" . $_SESSION["noutilisateur"];
  223. executerRequeteSql($sqlparam);
  224. $module["resultatSQL"] = false;
  225. $module["message"] = $module["msg"]["maj_jeton_echec"];
  226. $_SESSION["2fa_set"] = false;
  227. }
  228. } else {
  229. $module["resultatSQL"] = false;
  230. $module["message"] = "Jeton de sécurité vide";
  231. }
  232. } elseif ($action == "Mettre à jour vos informations personnelles") {
  233. unset($sqlparam);
  234. $sqlparam["connexion"] = "maitre";
  235. $sqlparam["type"] = "UPDATE";
  236. $sqlparam["table"][] = "administration";
  237. $sqlparam["champs"]["cellulaire"] = "'$cellulaire'";
  238. $sqlparam["champs"]["fournisseur"] = "'$fournisseur'";
  239. $sqlparam["where"][] = "refutilisateur =" . $_SESSION["noutilisateur"];
  240. $result = executerRequeteSql($sqlparam);
  241. if (isset($result)) {
  242. $module["resultatSQL"] = true;
  243. $module["message"] = $module["msg"]["maj_info_ok"];
  244. } else {
  245. $module["resultatSQL"] = false;
  246. $module["message"] = $module["msg"]["maj_info_echec"];
  247. }
  248. }
  249. }
  250. //Afficher les symboles permis dans le mot de passe.
  251. function afficheSymboles($ALLOWED_SYMBOLS) {
  252. $symboles = "";
  253. foreach ($ALLOWED_SYMBOLS as $clef => $valeur) {
  254. $symboles .= "&nbsp;" . $valeur . "&nbsp;";
  255. }
  256. return $symboles;
  257. }
  258. // Barre de menu
  259. print preparerModule($module);
  260. ?>
  261. <form Method="Post" Action="index2.php?page=profil" name="formulaire">
  262. <?php
  263. if ($_SESSION["temporaire"] == 0 && $_SESSION["2fa_set"]) {
  264. ?>
  265. <div class="boitelogin2">
  266. <fieldset>
  267. <legend>Zone de contrôle 1</legend>
  268. <div class='mdp'>
  269. <div style='width:200px; float:left;'>
  270. <label for="motDePasseActuel">Mot de passe actuel : </label>
  271. </div>
  272. <input type="password" id="motDePasseActuel" name="motDePasseActuel"/>
  273. </div>
  274. <div style='clear:both;'></div>
  275. <div class='mdp'>
  276. <div style='width:200px; float:left;'>
  277. <label for="motDePasseNouveau">Nouveau mot de passe : </label>
  278. </div>
  279. <input type="password" id="motDePasseNouveau" name="motDePasseNouveau"/>
  280. </div>
  281. <div style='clear:both;'></div>
  282. <div class='mdp'>
  283. <div style='width:200px; float:left;'>
  284. <label for="Confirmation">Confirmation : </label>
  285. </div>
  286. <input type="password" id="confirmation" name="confirmation"/>
  287. </div>
  288. <br />
  289. <input type="submit" class="button2" name="action" value="Changer votre mot de passe"/>
  290. <input type="hidden" name="jeton" value="<?php print $jeton; ?>"/>
  291. </fieldset>
  292. <br />
  293. Règles pour la création d'un mot de passe :<br>
  294. <ul>
  295. <li>Longueur minimale : 12 caractères</LI>
  296. <li>Minimum de 6 lettres</li>
  297. <li>2 nombres présents (0-9)</li>
  298. <li>3 caractères en minuscule</li>
  299. <li>3 caractères en majuscule</li>
  300. <li> Présence de 2 à 3 caractères spéciaux ci-dessous<br>
  301. <pre><?php print afficheSymboles($ALLOWED_SYMBOLS); ?></pre>
  302. </li>
  303. </ul>
  304. Truc : L'emploi d'une phrase facile à retenir est aussi possible.<br/>
  305. <i>Exemple : L'été sera Merveilleux au Chalet en 2030![</i>
  306. </div>
  307. <div class="boitelogin2">
  308. <fieldset>
  309. <legend>Informations personnelles</legend>
  310. <div class='mdp'>
  311. <div style='width:125px; float:left;'>
  312. <label for="cellulaire">Cellulaire : </label>
  313. </div>
  314. <input type="text" id="cellulaire" name="cellulaire" value="<?php print $lcellulaire; ?>" SIZE='14' MAXLENGTH='14'/>
  315. </div>
  316. <div style='clear:both;'></div>
  317. <div class='mdp'>
  318. <div style='width:125px; float:left;'>
  319. <label for="fournisseur">Fournisseur : </label>
  320. </div>
  321. <?php
  322. unset($result2);
  323. $result2 = getFournisseursCellulaire();
  324. unset($param);
  325. $param["form"] = "menu2";
  326. $param["nom"] = "fournisseur";
  327. $param["etq"] = "";
  328. $param["defaut"] = $lfournisseur;
  329. $param["donnees"] = $result2;
  330. $param["menu"] = true;
  331. //TODO Mettre un évènement vide pour contourner le gestionnaire interne. À corriger.
  332. $param["evt"] = "blank";
  333. print formListbox2($param);
  334. ?>
  335. </div>
  336. <div style='clear:both;'></div>
  337. <br />
  338. <input type="submit" class="button2" name="action" value="Mettre à jour vos informations personnelles"/>
  339. <input type="hidden" name="jeton" value="<?php print $jeton; ?>"/>
  340. </fieldset>
  341. </div>
  342. <script type='text/javascript'>
  343. // Mise en place des masques de saisie
  344. $(document).ready(function () {
  345. $("#cellulaire").mask("(999) 999-9999");
  346. });
  347. </script>
  348. <?php
  349. } else if ($_SESSION["temporaire"] == 1 && (!$_SESSION["2fa_set"] || empty($_SESSION["2fa_set"]))) {
  350. ?>
  351. <div class="boitelogin">
  352. <fieldset>
  353. <legend>Zone de contrôle2</legend>
  354. <div class='mdp'>
  355. <div style='width:200px; float:left;'>
  356. <label for="motDePasse">Mot de passe actuel : </label>
  357. </div>
  358. <input type="password" id="motDePasseActuel" name="motDePasseActuel"/>
  359. </div>
  360. <div style='clear:both;'></div>
  361. <div class='mdp'>
  362. <div style='width:200px; float:left;'>
  363. <label for="motDePasseNouveau">Nouveau mot de passe : </label>
  364. </div>
  365. <input type="password" id="motDePasseNouveau" name="motDePasseNouveau"/>
  366. </div>
  367. <div style='clear:both;'></div>
  368. <div class='mdp'>
  369. <div style='width:200px; float:left;'>
  370. <label for="Confirmation">Confirmation : </label>
  371. </div>
  372. <input type="password" id="confirmation" name="confirmation"/>
  373. </div>
  374. <br />
  375. <input type="submit" class="button2" name="action" value="Changer votre mot de passe"/>
  376. <input type="hidden" name="jeton" value="<?php print $jeton; ?>"/>
  377. </fieldset>
  378. <br />
  379. Règles pour la création d'un mot de passe :<br>
  380. <ul>
  381. <li>Longueur minimale : 12 caractères</LI>
  382. <li>Minimum de 6 lettres</li>
  383. <li>Minimum de 2 nombres présents (0-9)</li>
  384. <li>Minimum de 3 lettres en minuscule</li>
  385. <li>Minimum de 3 lettres en majuscule</li>
  386. <li>Présence de 2 à 3 caractères spéciaux <br>
  387. <?php
  388. //(&nbsp;'#',&nbsp;&nbsp;'_',&nbsp;&nbsp;'-',&nbsp;&nbsp;'!',&nbsp;&nbsp;'[',&nbsp;&nbsp;']',&nbsp;&nbsp;'=',&nbsp;'~',&nbsp;&nbsp;'*'&nbsp;)
  389. print afficheSymboles($ALLOWED_SYMBOLS);
  390. ?>
  391. </li>
  392. </ul>
  393. Truc : L'emploi d'une phrase facile à retenir est aussi possible.<br/>
  394. Exemple : L'été sera Merveilleux AU chalet en 2030!*
  395. </div>
  396. <?php
  397. } else if (!$_SESSION["2fa_set"] || empty($_SESSION["2fa_set"])) {
  398. $secret = getUser2faSecret();
  399. $_SESSION["temporary2fa"] = $secret;
  400. ?>
  401. <div class = 'authenticator'>
  402. <center><h1>Nouveau</h1></center>
  403. <p>
  404. Ce nouveau processus renforce la sécurité de connexion à l'application et
  405. s'assure que l'utilisateur est vraiment vous, comme personne.
  406. </p>
  407. <p>
  408. Désormais, l'utilisation d'une application sera requise pour votre sécurité.
  409. Ce processus est simple.
  410. </p>
  411. <ol>
  412. <li>
  413. Installer l'application Microsoft Authenticator sur votre téléphone cellulaire.<br>
  414. <a href="https://www.microsoft.com/fr-ca/security/mobile-authenticator-app">Cliquer ici</a>
  415. <br>
  416. <img id = "authenticator" src = "images/ms_authenticator.png" alt = "Microsoft Authenticator" width = "400" height = "96" />
  417. <br>
  418. <span style="color:blue;font-size:11px;">
  419. <i>Cliquer sur l'image pour l'agrandir.</i>
  420. </span>
  421. </li>
  422. <li>
  423. <p>
  424. Dans l'application Microsoft Authenticator, effectuer ces quelques étapes.
  425. </p>
  426. <ol>
  427. <li>Cliquer sur le symbole «+» pour ajouter un compte</li>
  428. <li>Choisissez le type de compte professionnel ou scolaire</li>
  429. <li>Lire (scanner) le code QR ci-dessous afin
  430. d'ajouter le site web des libérations comme émetteur de jeton de sécurité</li>
  431. </ol>
  432. <p>
  433. <?php
  434. // Voir la librairie lib2fa pour savoir comment créer des code qr avec Microsoft Authenticator.
  435. //TODO changer l'émetteur dans l'adresse de l'image
  436. // $data = "otpauth://totp/liberations:" . $_SESSION["adressecourriel"] . "?secret=$secret&issuer=https://liberations.magikweb.ca/";
  437. $data = "otpauth://totp/liberations:voiesdurables@gmail.com?secret=$secret&issuer=https://liberations.magikweb.ca/";
  438. print "<img src='" . (new QRCode)->render($data) . "' alt='Code QR' />";
  439. ?>
  440. </p>
  441. </li>
  442. <li>
  443. Inscrire un jeton de sécurité provenant de Microsoft Authenticator afin d'activer la sécurité renforcée
  444. et confirmer que la nouvelle sécurité est active.
  445. <div class='mdp'>
  446. <div style='width:200px; float:left;'>
  447. <label for="jeton">Jeton de sécurité : </label>
  448. </div>
  449. <input type="text" id="jetonsec" name="jetonsec"/>
  450. </div>
  451. <br />
  452. <input type="submit" class="button2" name="action" value="Appliquer votre jeton de sécurité"/>
  453. </li>
  454. </ol>
  455. </div>
  456. <div id="image-viewer">
  457. <span class="close">&times;</span>
  458. <img class="modal-content" id="full-image">
  459. </div>
  460. <script type='text/javascript'>
  461. $("#authenticator").click(function () {
  462. $("#full-image").attr("src", $(this).attr("src"));
  463. $('#image-viewer').show();
  464. });
  465. $("#image-viewer .close").click(function () {
  466. $('#image-viewer').hide();
  467. });
  468. </script>
  469. <?php
  470. }
  471. ?>
  472. </form>