From a0d1c26eafe015d78bf35e2465f08430c4f50acf Mon Sep 17 00:00:00 2001 From: sparkyx Date: Sat, 2 Dec 2023 18:01:53 +0100 Subject: [PATCH] Task #2309 mot de passe fort --- html/ajax_misc.php | 15 ++++ html/install.php | 14 ++- html/js/noalyss_script.js | 54 ++++++++++- include/ajax/ajax_preference.php | 29 ++++-- include/class/noalyss_user.class.php | 3 +- include/lib/ac_common.php | 115 +++++++++++++++++++++++- include/recover.php | 21 +---- include/user.inc.php | 36 +++++++- include/user_detail.inc.php | 2 + unit-test/include/lib/ac_commonTest.php | 42 +++++++++ 10 files changed, 296 insertions(+), 35 deletions(-) diff --git a/html/ajax_misc.php b/html/ajax_misc.php index 3227b94aa..1ee537040 100644 --- a/html/ajax_misc.php +++ b/html/ajax_misc.php @@ -113,7 +113,22 @@ if ($op == "progressBar") { return; } +//------------------------------------------------------------------------------------------- +// check password +//------------------------------------------------------------------------------------------- +if ($op=='password_chk') { + $cnt = $http->request("pass"); + $result=check_password_strength($cnt)['msg']; + if (count($result) == 0) { + echo json_response(["password" => "ok", "msg" => 0]); + } else { + $str=""; + foreach ($result as $item) {$str.=sprintf("
  • %s
  • ",$item);} + echo json_response(["password" => "nok", "msg" => '
      '.$str.'
    ']); + } + return; +} $html = var_export($_REQUEST, true); set_language(); if ( LOGINPUT) diff --git a/html/install.php b/html/install.php index f4c5a90e9..9ec75f0a9 100644 --- a/html/install.php +++ b/html/install.php @@ -233,7 +233,19 @@ if (isset($_POST['save_config'])) { $err++; } - + // check strenght password admin + $passw_error=check_password_strength($cpassword_admin); + if ( count($passw_error['msg'])>0) { + echo '

    '; + echo _("Mot de passe trop faible"); + echo '

    '; + echo '
      '; + foreach ($passw_error['msg'] as $error) { + echo "
    1. ",$error,"
    2. "; + } + echo '
    '; + $err++; + } // check password and admin not containing quote or double quote // if ( strpos($cpassword_admin,'"') !== false diff --git a/html/js/noalyss_script.js b/html/js/noalyss_script.js index 3e6301e22..e78c118f2 100644 --- a/html/js/noalyss_script.js +++ b/html/js/noalyss_script.js @@ -3786,9 +3786,13 @@ function updatePreference() method: "post", parameters: param, onSuccess: function (req) { - var style = req.responseText.evalJSON(); + var answer = req.responseText.evalJSON(); // $('pagestyle').setAttribute('href', style.style); - removeDiv('preference_div'); + if ( answer['psw']=='NOK') { + smoke.alert(answer['msg']); + } else { + removeDiv('preference_div'); + } } }); } catch (e) @@ -4224,4 +4228,50 @@ function event_display_main(p_dossier) { { alert_box(e.message); } +} + +/** + * @brief check if password is strong or not, update a DIV element + * @param p_pass_domid DOM ID of the INPUT element with the password + * @param p_result_domid DOM ID of the element to update + */ +function check_password_strength(p_pass_domid,p_result_domid,details) +{ + try + { + if ( $(p_pass_domid).value=="") { $(p_result_domid).update("");return;} + var queryString= { + 'op':"password_chk" + ,pass:$(p_pass_domid).value + }; + var action = new Ajax.Request( + "ajax_misc.php" , + { + method:'GET', + parameters:queryString, + onFailure:ajax_misc_failure, + onSuccess:function(req){ + remove_waiting_box(); + if (req.responseText == 'NOCONX') { + return; + } + var answer=req.responseJSON; + console.debug(answer); + if (answer['password']=='nok') { + + $(p_pass_domid).setStyle("background-color:red"); + if ( details) { + $(p_result_domid).update(answer['msg']) + } + return; + } + $(p_pass_domid).setStyle("background-color: lightgreen"); + $(p_result_domid).update("") + } + } + ); + }catch( e) + { + alert_box(e.message); + } } \ No newline at end of file diff --git a/include/ajax/ajax_preference.php b/include/ajax/ajax_preference.php index 99a0bdf45..f66c6cb99 100644 --- a/include/ajax/ajax_preference.php +++ b/include/ajax/ajax_preference.php @@ -84,8 +84,9 @@ if ( $action == 'display_form' ) Mot de passe : - + + @@ -321,20 +322,32 @@ if ($action == 'save') $csv_decimal=$http->post("csv_decimal","number"); $csv_encoding=$http->post("csv_encoding"); $firstday=$http->post("selFirstDay","number"); - + $password="OK"; + $msg =""; if (noalyss_strlentrim($pass_1) != 0 && noalyss_strlentrim($pass_2) != 0) { if ( $g_user->save_password($_POST['pass_1'],$pass_2) ) - { $g_user->password_to_session() ; + { + $g_user->password_to_session() ; } else { /** * password not changed - */ - + */ + $password="NOK"; + $msg=""; + if ( $_POST['pass_1'] !== $pass_2) { + $msg = _("Mot de passe ne correspondent pas"); + $msg .="
    "; + } + $a_pass_error=check_password_strength($_POST['pass_1']); + if ( count($a_pass_error['msg']) != 0 ) { + foreach($a_pass_error['msg'] as $pass_error) { + $msg.=$pass_error."
    "; + } + } + } - - } if ( $inside_dossier) { @@ -366,6 +379,6 @@ if ($action == 'save') { $style = "style-classic7.css"; } - json_response(["style"=>$style]); + json_response(["style"=>$style,'psw'=>$password,'msg'=>$msg]); } diff --git a/include/class/noalyss_user.class.php b/include/class/noalyss_user.class.php index 890f7d886..b9b10e2df 100644 --- a/include/class/noalyss_user.class.php +++ b/include/class/noalyss_user.class.php @@ -1635,11 +1635,12 @@ class Noalyss_User * @brief Save the password of the current user * @param string $p_pass1 password (clear) * @param string $p_pass2 for confirming password (clear) + * @see check_password_strength() * @return true : password successfully changed otherwise false */ function save_password($p_pass1, $p_pass2) { - if ($p_pass1==$p_pass2) + if ($p_pass1==$p_pass2 && count(check_password_strength($p_pass1)['msg'])==0) { $repo=new Database(); $l_pass=md5($p_pass1); diff --git a/include/lib/ac_common.php b/include/lib/ac_common.php index dcab67a9f..82ea4dbac 100644 --- a/include/lib/ac_common.php +++ b/include/lib/ac_common.php @@ -1416,10 +1416,13 @@ function remove_divide_zero($p_formula) * @brief Create randomly a string * @param int $p_length length of the generate string */ -function generate_random_string($p_length) +function generate_random_string($p_length,$special=1) { $string=""; - $chaine="abcdefghijklmnpqrstuvwxyABCDEFGHIJKLMNPQRSTUVWXY0123456789*/+-="; + if ($special == 1) + $chaine="abcdefghijklmnpqrstuvwxyABCDEFGHIJKLMNPQRSTUVWXY0123456789*/+-="; + if ($special == 0) + $chaine="abcdefghijklmnpqrstuvwxyABCDEFGHIJKLMNPQRSTUVWXY0123456789"; $microtime=microtime(true)*microtime(true)*100; srand(0); srand((int)$microtime); @@ -1663,3 +1666,111 @@ function MaintenanceMode($p_file) exit; } } + +/** + * @brief returns an double array with the error found and code , if the count is 0 then the password is very string, 5 means it is + * empty ,4 weak, ... the array contains the errors, [msg]=>array message [code] => array of code + * Codes are + * - 1 : too short + * - 2 : missing digit + * - 3 : missing lowercase letter + * - 4 : missing uppercase letter + * - 5 : too many time same letter or symbol.. + * - 6 : missing special char + * + * If the password is strong returns an empty array + * + * @param $password string + * @code + + $error = check_password_strength($password); + if ( count($error['msg']) > 0 ) { + echo "password to weak"; + foreach ($error['msg'] as $item_error) { + echo "error $item_error"; + } + + } else { + echo "OK password strong"; + } + + * @endcode + */ +function check_password_strength($password) { + $errors=array(); + $error_code=array(); + + $len=strlen($password??""); + if ( $len < 8) { + $errors[] = _("mot de passe de 8 lettres minimum"); + $error_code[]=1; + } + + if (!preg_match("#[0-9]+#", $password)) { + $errors[] = _("mot de passe doit inclure au moins un chiffre"); + $error_code[]=2; + } + + if (!preg_match("#[a-z]+#", $password)) { + $errors[] = _("mot de passe doit inclure au moins une minuscule"); + $error_code[]=3; + } + if (!preg_match("#[A-Z]+#", $password)) { + $errors[] = _("mot de passe doit inclure au moins une majuscule"); + $error_code[]=4; + } + + if ( $len > 0 ) { + $cnt_diff=count(count_chars($password,1)); + $ratio_diff=$len/$cnt_diff; + + if ($ratio_diff > 2) { + $errors[] = _("Trop souvent le(s) même(s) symbole(s)"); + $error_code[]=5; + } + $special_char=preg_replace('/[[:alnum:]]/','',$password); + if ( strlen($special_char??"")==0) + { + $errors[] = _("mot de passe doit inclure au moins un caractére spécial '+-/*[...'"); + $error_code[]=6; + + } + } + + return array( 'msg'=>$errors, 'code'=>$error_code); +} +/** + * @brief generate a strong random password + * @param $car int length of the password, minimum 8 + * + */ +function generate_random_password($car):string +{ + $string=""; + $car=($car < 8 )?8:$car; + $max_loop=20;$loop=0; + do + { + $loop++; + $string=""; + $chaine="abcdefghijklmnpqrstuvwxy"; + // srand( (int)microtime()*1020030); + for ($i=0; $i<$car; $i++) + { + $string .= $chaine[rand()%strlen($chaine)]; + } + $chaine="ABCDEFGHIJKLMNPQRSTUVWXY"; + for ($i=0;$i<2;$i++) { + $string[rand()%$car]=$chaine[rand()%strlen($chaine)];; + } + $chaine="0123456789"; + for ($i=0;$i<2;$i++) { + $string[rand()%$car]=$chaine[rand()%strlen($chaine)];; + } + $special_set="+-/*;,.=:&()[]"; + $special_car=$special_set[rand()%strlen($special_set)]; + $string[rand()%$car]=$special_car; + // echo $string."\n"; + }while ( count(check_password_strength($string)['msg'])> 0 && $loop<$max_loop); + return $string; +} \ No newline at end of file diff --git a/include/recover.php b/include/recover.php index 026863f65..ed8d7ad47 100644 --- a/include/recover.php +++ b/include/recover.php @@ -17,27 +17,14 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ // Copyright (2014) Author Dany De Bontridder +require_once NOALYSS_INCLUDE.'/lib/ac_common.php'; if (!defined('RECOVER')) die('Appel direct ne sont pas permis'); define('SIZE_REQUEST', 70); -/** - * @brief generate a random string of char - * @param $car int length of the string - */ -function generate_random($car) -{ - $string=""; - $chaine="abcdefghijklmnpqrstuvwxyABCDEFGHIJKLMNPQRSTUVWXY0123456789"; - srand((double) microtime()*1020030); - for ($i=0; $i<$car; $i++) - { - $string .= $chaine[rand()%strlen($chaine)]; - } - return $string; -} + $http=new HttpInput(); /** * @file @@ -89,8 +76,8 @@ elseif ($action=="send_email") : if ($valid==true): - $request_id=generate_random(SIZE_REQUEST); - $user_password=generate_random(10); + $request_id=generate_random_string(SIZE_REQUEST,special: 0); + $user_password=generate_random_password(10); // exist a valid request for this user ? $exist_request= $cn->get_array("select request , password from recover_pass where use_id=$1 and created_on > now() - interval '12 hours'",[$user_id]); diff --git a/include/user.inc.php b/include/user.inc.php index c714f83e5..dddccd752 100644 --- a/include/user.inc.php +++ b/include/user.inc.php @@ -35,6 +35,7 @@ echo '
    '; if ( isset ($_POST["ADD"]) ) { $cn=new Database(); + $a_result =check_password_strength($_POST['PASS']); $pass5=md5($_POST['PASS']); $new_user=new Noalyss_user($cn,0); $new_user->first_name=$http->post('FNAME'); @@ -45,11 +46,18 @@ if ( isset ($_POST["ADD"]) ) $login=str_replace(" ","",$login); $login=strtolower($login); $new_user->login=$login; - $new_user->setPassword($pass5); + $new_user->email=$http->post('EMAIL',"string",''); if ( trim($login)=="") { alert(_("Le login ne peut pas être vide")); + }elseif (count($a_result['msg']) > 0){ + // password too weak + $msg=''._("Mot de passe inchangé").''; + foreach ($a_result['msg'] as $result ) { + $msg.="$result
    "; + } + alert($msg); } else { @@ -101,8 +109,18 @@ if ($sbaction == "save") } if ( trim($_POST['password'])<>'') { - $UserChange->setPassword(md5($_POST['password'])); - $UserChange->save(); + $a_result =check_password_strength($_POST['password']); + if (count($a_result['msg']) > 0){ + // password too weak + $msg=''._("Mot de passe inchangé").''; + foreach ($a_result['msg'] as $result ) { + $msg.="$result
    "; + } + alert($msg); + } else { + $UserChange->setPassword(md5($_POST['password'])); + $UserChange->save(); + } } else { @@ -168,9 +186,19 @@ if ( isset($_REQUEST['det']) && $sbaction=="") - + + + + + + + + + id == false) $UserChange->load(); $it_pass=new IText('password'); +$it_pass->javascript='onkeyup="check_password_strength(\'password\',\'password_info\',1)"'; $it_pass->value=""; ?>
    @@ -81,6 +82,7 @@ $it_pass->value=""; input();?> + diff --git a/unit-test/include/lib/ac_commonTest.php b/unit-test/include/lib/ac_commonTest.php index 10483089a..91f2ff39c 100644 --- a/unit-test/include/lib/ac_commonTest.php +++ b/unit-test/include/lib/ac_commonTest.php @@ -424,4 +424,46 @@ EOF; $this->assertEquals(strtoupper($expect),strtoupper(preg_replace("/\s+/",'',faxTo('123'))),); } + + /** + * supply data for user password + * @return array[$password, $weakness] 0 means strong password + */ + public function dataCheck_password_strength() + { + return array( + ["AAAAAAA",5] + ,["123456789",3] + ,["Az123456789",1] + ,["",4] + ,["+",4] + ,["AAAA121212abx",2] + ,["Az&123456789",0] + ,["l5F8Cny=",0] + ); + } + /** + * @testDoc test the check_password_strength function + * @dataProvider dataCheck_password_strength() + */ + public function testCheck_password_strength($p_password,$p_cnt) + { + + $count=count(check_password_strength($p_password)['msg']); + $this->assertTrue($count ==$p_cnt,"error : $p_password weak password $count" ); + + } + + public function testGenerate_strong_password() + { + + for ($i = 0; $i < 100; $i++) + { + $pass=generate_random_password(5); + + $this->assertTrue( count(check_password_strength($pass)['msg'])==0 + ,"error cannot generate strong password get $pass"); + } + } + }