From 851752e64286fe8362b25afb544e36812a253136 Mon Sep 17 00:00:00 2001 From: David Coutadeur Date: Mon, 9 Sep 2024 19:20:54 +0200 Subject: [PATCH] move ppolicy functions into a dedicated class (#36) --- src/Ltb/Password.php | 229 ---------------------------------- tests/Ltb/PasswordTest.php | 249 ------------------------------------- 2 files changed, 478 deletions(-) diff --git a/src/Ltb/Password.php b/src/Ltb/Password.php index 67dc7fe..108c5f4 100644 --- a/src/Ltb/Password.php +++ b/src/Ltb/Password.php @@ -1,8 +1,5 @@ 0 ) { $complex++; } - if ( $digit > 0 ) { $complex++; } - if ( $lower > 0 ) { $complex++; } - if ( $upper > 0 ) { $complex++; } - if ( $complex < $pwd_complexity ) { $result="notcomplex"; } - } - - # Minimal length - if ( $pwd_min_length and $length < $pwd_min_length ) { $result="tooshort"; } - - # Maximal length - if ( $pwd_max_length and $length > $pwd_max_length ) { $result="toobig"; } - - # Minimal lower chars - if ( $pwd_min_lower and $lower < $pwd_min_lower ) { $result="minlower"; } - - # Minimal upper chars - if ( $pwd_min_upper and $upper < $pwd_min_upper ) { $result="minupper"; } - - # Minimal digit chars - if ( $pwd_min_digit and $digit < $pwd_min_digit ) { $result="mindigit"; } - - # Minimal special chars - if ( $pwd_min_special and $special < $pwd_min_special ) { $result="minspecial"; } - - # Forbidden chars - if ( $forbidden > 0 ) { $result="forbiddenchars"; } - - # Special chars at beginning or end - if ( $special_at_ends > 0 && $special == 1 ) { $result="specialatends"; } - - # Same as old password? - if ( $pwd_no_reuse and $password === $oldpassword ) { $result="sameasold"; } - - # Same as login? - if ( $pwd_diff_login and $password === $login ) { $result="sameaslogin"; } - - if ( $pwd_diff_last_min_chars > 0 and strlen($oldpassword) > 0 ) { - $similarities = similar_text($oldpassword, $password); - $check_len = strlen($oldpassword) < strlen($password) ? - strlen($oldpassword) : - strlen($password); - $new_chars = $check_len - $similarities; - if ($new_chars <= $pwd_diff_last_min_chars) { $result = "diffminchars"; } - } - - # Contains forbidden words? - if ( !empty($pwd_forbidden_words) ) { - foreach( $pwd_forbidden_words as $disallowed ) { - if( stripos($password, $disallowed) !== false ) { - $result="forbiddenwords"; - break; - } - } - } - - # Contains values from forbidden ldap fields? - if ( !empty($pwd_forbidden_ldap_fields) ) { - foreach ( $pwd_forbidden_ldap_fields as $field ) { - # if entry does not hold requested attribute, continue - if ( array_key_exists($field,$entry_array) ) - { - $values = $entry_array[$field]; - if (!is_array($values)) { - $values = array($values); - } - foreach ($values as $key => $value) { - if ($key === 'count') { - continue; - } - if (stripos($password, $value) !== false) { - $result = "forbiddenldapfields"; - break 2; - } - } - } - } - } - - # ensure that the new password is different from any other custom password field marked as unique - foreach ( $change_custompwdfield as $custompwdfield) { - if (isset($custompwdfield['pwd_policy_config']['pwd_unique_across_custom_password_fields']) && - $custompwdfield['pwd_policy_config']['pwd_unique_across_custom_password_fields']) { - if (array_key_exists($custompwdfield['attribute'], $entry_array)) { - if ($custompwdfield['hash'] == 'auto') { - $matches = []; - if ( preg_match( '/^\{(\w+)\}/', - $entry_array[$custompwdfield['attribute']][0], - $matches ) ) - { - $hash_for_custom_pwd = strtoupper($matches[1]); - } - } else { - $hash_for_custom_pwd = $custompwdfield['hash']; - } - if ( \Ltb\Password::check_password($password, - $entry_array[$custompwdfield['attribute']][0], - $hash_for_custom_pwd) ) - { - $result = "sameascustompwd"; - } - } - } - } - - # pwned? - if ($use_pwnedpasswords and version_compare(PHP_VERSION, '7.2.5') >= 0) { - $pwned_passwords = new PwnedPasswords; - $insecure = $pwned_passwords->isPwned($password); - if ($insecure) { $result="pwned"; } - } - - - # check entropy - $zxcvbn = new Zxcvbn(); - if( isset($pwd_check_entropy) && $pwd_check_entropy == true ) - { - if( isset($pwd_min_entropy) && is_int($pwd_min_entropy) ) - { - // force encoding to utf8, as iso-8859-1 is not supported by zxcvbn - //$password = mb_convert_encoding($p, 'UTF-8', 'ISO-8859-1'); - error_log("checkEntropy: password taken directly"); - $entropy = $zxcvbn->passwordStrength("$password"); - $entropy_level = intval($entropy["score"]); - $entropy_message = $entropy['feedback']['warning'] ? strval($entropy['feedback']['warning']) : ""; - error_log( "checkEntropy: level $entropy_level msg: $entropy_message" ); - if( is_int($entropy_level) && $entropy_level >= $pwd_min_entropy ) - { - ; // password entropy check ok - } - else - { - error_log("checkEntropy: insufficient entropy: level = $entropy_level but minimal required = $pwd_min_entropy"); - $result="insufficiententropy"; - } - } - else - { - error_log("checkEntropy: missing required parameter pwd_min_entropy"); - $result="insufficiententropy"; - } - - } - - return $result; - } - - /* Check user password against zxcvbn library - Input : new user base64-encoded password - Output: JSON response: { "level" => int, "message" => "msg" } */ - - static function checkEntropyJSON($password_base64) - { - $response_params = array(); - $zxcvbn = new Zxcvbn(); - - if( ! isset($password_base64) || empty($password_base64)) - { - error_log("checkEntropy: missing parameter password"); - $response_params["level"] = "-1"; - $response_params["message"] = "missing parameter password"; - return json_encode($response_params); - } - - $p = base64_decode($password_base64); - // force encoding to utf8, as iso-8859-1 is not supported by zxcvbn - $password = mb_convert_encoding($p, 'UTF-8', 'ISO-8859-1'); - - $entropy = $zxcvbn->passwordStrength("$password"); - - $response_params["level"] = strval($entropy["score"]); - $response_params["message"] = $entropy['feedback']['warning'] ? strval($entropy['feedback']['warning']) : ""; - - return json_encode($response_params); - } - } diff --git a/tests/Ltb/PasswordTest.php b/tests/Ltb/PasswordTest.php index f3bf709..ea0836e 100644 --- a/tests/Ltb/PasswordTest.php +++ b/tests/Ltb/PasswordTest.php @@ -162,253 +162,4 @@ function test_set_shadow_data() { } - - - public function testCheckPasswordStrength() - { - - # Password policy - $pwd_policy_config = array( - "pwd_show_policy" => true, - "pwd_min_length" => 6, - "pwd_max_length" => 12, - "pwd_min_lower" => 1, - "pwd_min_upper" => 1, - "pwd_min_digit" => 1, - "pwd_min_special" => 1, - "pwd_special_chars" => "^a-zA-Z0-9", - "pwd_forbidden_chars" => "@", - "pwd_no_reuse" => true, - "pwd_diff_last_min_chars" => 0, - "pwd_diff_login" => true, - "pwd_complexity" => 0, - "use_pwnedpasswords" => false, - "pwd_no_special_at_ends" => false, - "pwd_forbidden_words" => array(), - "pwd_forbidden_ldap_fields"=> array(), - ); - - $login = "coudot"; - $oldpassword = "secret"; - $entry_array = array('cn' => array('common name'), 'sn' => array('surname'), 'customPasswordField' => array("{SSHA}7JWaNGUygodHyWt+DwPpOuYMDdKYJQQX")); - $change_custompwdfield = array( - array( - 'pwd_policy_config' => array( - 'pwd_no_reuse' => true, - 'pwd_unique_across_custom_password_fields' => true - ), - 'attribute' => 'customPasswordField', - 'hash' => "auto" - ) - ); - $change_custompwdfield2 = array( - array( - 'pwd_policy_config' => array( - 'pwd_no_reuse' => true, - 'pwd_unique_across_custom_password_fields' => true - ), - 'attribute' => 'customPasswordField', - 'hash' => "SSHA" - ) - ); - - $this->assertEquals("sameaslogin", \Ltb\Password::check_password_strength( "coudot", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("sameasold", \Ltb\Password::check_password_strength( "secret", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("forbiddenchars", \Ltb\Password::check_password_strength( "p@ssword", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("minspecial", \Ltb\Password::check_password_strength( "password", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("mindigit", \Ltb\Password::check_password_strength( "!password", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("minupper", \Ltb\Password::check_password_strength( "!1password", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("minlower", \Ltb\Password::check_password_strength( "!1PASSWORD", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("toobig", \Ltb\Password::check_password_strength( "!1verylongPassword", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("tooshort", \Ltb\Password::check_password_strength( "!1Pa", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("sameascustompwd", \Ltb\Password::check_password_strength( "!TestMe123!", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("sameascustompwd", \Ltb\Password::check_password_strength( "!TestMe123!", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield2 ) ); - - - $pwd_policy_config = array( - "pwd_show_policy" => true, - "pwd_min_length" => 6, - "pwd_max_length" => 12, - "pwd_min_lower" => 0, - "pwd_min_upper" => 0, - "pwd_min_digit" => 0, - "pwd_min_special" => 0, - "pwd_special_chars" => "^a-zA-Z0-9", - "pwd_forbidden_chars" => "@", - "pwd_no_reuse" => true, - "pwd_diff_last_min_chars" => 3, - "pwd_diff_login" => true, - "pwd_complexity" => 3, - "use_pwnedpasswords" => false, - "pwd_no_special_at_ends" => true, - "pwd_forbidden_words" => array('companyname', 'trademark'), - "pwd_forbidden_ldap_fields"=> array('cn', 'sn'), - ); - - $this->assertEquals("notcomplex", \Ltb\Password::check_password_strength( "simple", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("specialatends", \Ltb\Password::check_password_strength( "!simple", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("specialatends", \Ltb\Password::check_password_strength( "simple?", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("forbiddenwords", \Ltb\Password::check_password_strength( "companyname", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("forbiddenwords", \Ltb\Password::check_password_strength( "trademark", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("forbiddenwords", \Ltb\Password::check_password_strength( "working at companyname is fun", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("forbiddenldapfields", \Ltb\Password::check_password_strength( "common name", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("forbiddenldapfields", \Ltb\Password::check_password_strength( "my surname", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("diffminchars", \Ltb\Password::check_password_strength( "C0mplex", "C0mplexC0mplex", $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("", \Ltb\Password::check_password_strength( "C0mplex", "", $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("", \Ltb\Password::check_password_strength( "C0mplex", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("", \Ltb\Password::check_password_strength( "C0!mplex", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("", \Ltb\Password::check_password_strength( "%C0!mplex", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - } - - /** - * Test check_password_strength function with pwned passwords - */ - public function testCheckPasswordStrengthPwnedPasswords() - { - - $login = "coudot"; - $oldpassword = "secret"; - - if ( version_compare(PHP_VERSION, '7.2.5') >= 0 ) { - $pwd_policy_config = array( - "pwd_show_policy" => true, - "pwd_min_length" => 6, - "pwd_max_length" => 12, - "pwd_min_lower" => 1, - "pwd_min_upper" => 1, - "pwd_min_digit" => 1, - "pwd_min_special" => 1, - "pwd_special_chars" => "^a-zA-Z0-9", - "pwd_forbidden_chars" => "@", - "pwd_no_reuse" => true, - "pwd_diff_last_min_chars" => 0, - "pwd_diff_login" => true, - "pwd_complexity" => 0, - "use_pwnedpasswords" => true, - "pwd_no_special_at_ends" => false, - "pwd_forbidden_words" => array(), - "pwd_forbidden_ldap_fields"=> array(), - ); - - $this->assertEquals("pwned", \Ltb\Password::check_password_strength( "!1Password", $oldpassword, $pwd_policy_config, $login, array(), array() ) ); - } - - } - - /** - * Test check_password_strength function with weak entropy password - */ - public function testCheckPasswordStrengthWeakEntropy() - { - - $login = "johnsmith"; - $oldpassword = "secret"; - - if ( version_compare(PHP_VERSION, '7.2.5') >= 0 ) { - $pwd_policy_config = array( - "pwd_show_policy" => true, - "pwd_min_length" => 6, - "pwd_max_length" => 0, - "pwd_min_lower" => 0, - "pwd_min_upper" => 0, - "pwd_min_digit" => 0, - "pwd_min_special" => 0, - "pwd_special_chars" => "^a-zA-Z0-9", - "pwd_forbidden_chars" => "", - "pwd_no_reuse" => false, - "pwd_diff_last_min_chars" => 0, - "pwd_diff_login" => false, - "pwd_complexity" => 0, - "use_pwnedpasswords" => false, - "pwd_no_special_at_ends" => false, - "pwd_forbidden_words" => array(), - "pwd_forbidden_ldap_fields"=> array(), - "pwd_display_entropy" => true, - "pwd_check_entropy" => true, - "pwd_min_entropy" => 3 - ); - - $this->assertEquals("insufficiententropy", \Ltb\Password::check_password_strength( "secret", $oldpassword, $pwd_policy_config, $login, array(), array() ) ); - } - - } - - /** - * Test check_password_strength function with strong entropy password - */ - public function testCheckPasswordStrengthStrongEntropy() - { - - $login = "johnsmith"; - $oldpassword = "secret"; - - if ( version_compare(PHP_VERSION, '7.2.5') >= 0 ) { - $pwd_policy_config = array( - "pwd_show_policy" => true, - "pwd_min_length" => 6, - "pwd_max_length" => 0, - "pwd_min_lower" => 0, - "pwd_min_upper" => 0, - "pwd_min_digit" => 0, - "pwd_min_special" => 0, - "pwd_special_chars" => "^a-zA-Z0-9", - "pwd_forbidden_chars" => "", - "pwd_no_reuse" => false, - "pwd_diff_last_min_chars" => 0, - "pwd_diff_login" => false, - "pwd_complexity" => 0, - "use_pwnedpasswords" => false, - "pwd_no_special_at_ends" => false, - "pwd_forbidden_words" => array(), - "pwd_forbidden_ldap_fields"=> array(), - "pwd_display_entropy" => true, - "pwd_check_entropy" => true, - "pwd_min_entropy" => 3 - ); - - $this->assertEquals("", \Ltb\Password::check_password_strength( "Th!Sis@Str0ngP@ss0rd", $oldpassword, $pwd_policy_config, $login, array(), array() ) ); - } - - } - - /** - * Test checkEntropyJSON function - */ - public function testCheckEntropyJSON() - { - - $password_weak = "secret"; - $password_weak_base64 = base64_encode($password_weak); - - $password_strong = "jtK8hEhNgT3wwGiDY_z7XmI92fUbnemQ"; - $password_strong_base64 = base64_encode($password_strong); - - $result_error = json_encode( - array( - "level" => "-1", - "message" => "missing parameter password" - ) - ); - - $result_weak = json_encode( - array( - "level" => "0", - "message" => "This is a top-100 common password" - ) - ); - - $result_strong = json_encode( - array( - "level" => "4", - "message" => "" - ) - ); - - $this->assertEquals($result_error, \Ltb\Password::checkEntropyJSON( "" ) ); - $this->assertEquals($result_weak, \Ltb\Password::checkEntropyJSON( $password_weak_base64 ) ); - $this->assertEquals($result_strong, \Ltb\Password::checkEntropyJSON( $password_strong_base64 ) ); - - } - - }