diff --git a/.gitignore b/.gitignore index ab2cab23..923fe182 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,6 @@ /htdocs/vendor/ composer.lock tests/.phpunit.result.cache +htdocs/js/ppolicy.js +htdocs/css/ppolicy.css +templates/policy.tpl diff --git a/composer.json b/composer.json index 6968a43f..4a58b82c 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,6 @@ { "require": { - "ltb-project/ltb-common": "dev-main", + "ltb-project/ltb-common": "dev-36-add-password-policy", "bjeavons/zxcvbn-php": "^1.0", "twbs/bootstrap": "v5.3.3", "defuse/php-encryption": "2.4.0", @@ -22,7 +22,10 @@ "rm -rf htdocs/vendor/font-awesome", "cp -R vendor/fortawesome/font-awesome htdocs/vendor/font-awesome", "rm -rf htdocs/vendor/font-awesome/js htdocs/vendor/font-awesome/js-packages htdocs/vendor/font-awesome/less htdocs/vendor/font-awesome/metadata htdocs/vendor/font-awesome/otfs htdocs/vendor/font-awesome/scss htdocs/vendor/font-awesome/sprites htdocs/vendor/font-awesome/svgs", - "rm -rf vendor/fortawesome/font-awesome" + "rm -rf vendor/fortawesome/font-awesome", + "cp -f vendor/ltb-project/ltb-common/src/ppolicy/html/policy.tpl templates/policy.tpl", + "cp -f vendor/ltb-project/ltb-common/src/ppolicy/js/ppolicy.js htdocs/js/ppolicy.js", + "cp -f vendor/ltb-project/ltb-common/src/ppolicy/css/ppolicy.css htdocs/css/ppolicy.css" ] }, "require-dev": { diff --git a/htdocs/change.php b/htdocs/change.php index 6f2c518b..b7c92514 100644 --- a/htdocs/change.php +++ b/htdocs/change.php @@ -159,7 +159,7 @@ # Check password strength #============================================================================== if ( !$result ) { - $result = check_password_strength( $newpassword, $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ); + $result = \Ltb\Ppolicy::check_password_strength( $newpassword, $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ); } #============================================================================== diff --git a/htdocs/changecustompwdfield.php b/htdocs/changecustompwdfield.php index 0de0718a..2e038a66 100644 --- a/htdocs/changecustompwdfield.php +++ b/htdocs/changecustompwdfield.php @@ -217,7 +217,7 @@ function set_default_value(&$variable, $defaultValue) # Check password strength #============================================================================== if ( !$result ) { - $result = check_password_strength( $newcustompwd, $password, $custompwdfield['pwd_policy_config'], $login, $entry_array, $change_custompwdfield ); + $result = \Ltb\Ppolicy::check_password_strength( $newcustompwd, $password, $custompwdfield['pwd_policy_config'], $login, $entry_array, $change_custompwdfield ); } #============================================================================== diff --git a/htdocs/checkentropy.php b/htdocs/checkentropy.php index 58cdcc61..cf237f0f 100644 --- a/htdocs/checkentropy.php +++ b/htdocs/checkentropy.php @@ -1,67 +1,15 @@ int, "message" => "msg" } */ - -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"; - print json_encode($response_params); - exit(1); - } - - $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'); - error_log("checkEntropy: password taken from submitted form"); - - $entropy = $zxcvbn->passwordStrength("$password"); - - $response_params["level"] = strval($entropy["score"]); - $response_params["message"] = $entropy['feedback']['warning'] ? strval($entropy['feedback']['warning']) : ""; - - error_log("checkEntropy: level " . $response_params["level"] . " msg: " . $response_params["message"]); - - print json_encode($response_params); - exit(0); -} // new password sent in the url, base64 encoded $newpass = htmlspecialchars($_POST["password"]); -checkEntropyJSON($newpass); +$entropy_response = \Ltb\Ppolicy::checkEntropyJSON($newpass); +if ($debug) { + error_log("checkEntropy: ".$entropy_response); +} + +print $entropy_response; +exit(0); ?> diff --git a/htdocs/css/self-service-password.css b/htdocs/css/self-service-password.css index e5cbeb75..79bbffb1 100644 --- a/htdocs/css/self-service-password.css +++ b/htdocs/css/self-service-password.css @@ -65,38 +65,3 @@ textarea#sshkey { } } - -/* password entropy customization*/ -#entropybar>div { - width: 0%; - /* Adjust with JavaScript */ -} - -#entropybar>div.levelErr { - width: 0%; -} - -#entropybar>div.level0 { - width: 20%; -} - -#entropybar>div.level1 { - width: 40%; -} - -#entropybar>div.level2 { - width: 60%; -} - -#entropybar>div.level3 { - width: 80%; -} - -#entropybar>div.level4 { - width: 100%; -} - -.entropyHidden { - display: none; -} - diff --git a/htdocs/index.php b/htdocs/index.php index cfbd40d2..13e72282 100644 --- a/htdocs/index.php +++ b/htdocs/index.php @@ -315,64 +315,13 @@ if (isset($login)) { $smarty->assign('login', $login); } if (isset($token)) { $smarty->assign('token', $token); } if (isset($use_captcha)) { $smarty->assign('use_captcha', $use_captcha); } -// TODO : Make it clean function show_policy - START -if (isset($pwd_show_policy_pos)) { - $smarty->assign('pwd_show_policy_pos', $pwd_show_policy_pos); - $smarty->assign('pwd_show_policy', $pwd_show_policy); - $smarty->assign('pwd_show_policy_onerror', true); - if ( $pwd_show_policy === "onerror" ) { - if ( !preg_match( "/tooshort|toobig|minlower|minupper|mindigit|minspecial|forbiddenchars|sameasold|notcomplex|sameaslogin|pwned|specialatends/" , $result) ) { - $smarty->assign('pwd_show_policy_onerror', false); - } else { - $smarty->assign('pwd_show_policy_onerror', true); - } - } - if (isset($pwd_min_length)) { $smarty->assign('pwd_min_length', $pwd_min_length); } - if (isset($pwd_max_length)) { $smarty->assign('pwd_max_length', $pwd_max_length); } - if (isset($pwd_min_lower)) { $smarty->assign('pwd_min_lower', $pwd_min_lower); } - if (isset($pwd_min_upper)) { $smarty->assign('pwd_min_upper', $pwd_min_upper); } - if (isset($pwd_min_digit)) { $smarty->assign('pwd_min_digit', $pwd_min_digit); } - if (isset($pwd_min_special)) { $smarty->assign('pwd_min_special', $pwd_min_special); } - if (isset($pwd_complexity)) { $smarty->assign('pwd_complexity', $pwd_complexity); } - if (isset($pwd_diff_last_min_chars)) { $smarty->assign('pwd_diff_last_min_chars', $pwd_diff_last_min_chars); } - if (isset($pwd_forbidden_chars)) { $smarty->assign('pwd_forbidden_chars', $pwd_forbidden_chars); } - if (isset($pwd_no_reuse)) { $smarty->assign('pwd_no_reuse', $pwd_no_reuse); } - if (isset($pwd_diff_login)) { $smarty->assign('pwd_diff_login', $pwd_diff_login); } - if (isset($pwd_display_entropy)) { $smarty->assign('pwd_display_entropy', $pwd_display_entropy); } - if (isset($pwd_check_entropy)) { $smarty->assign('pwd_check_entropy', $pwd_check_entropy); } - if (isset($pwd_min_entropy)) { $smarty->assign('pwd_min_entropy', $pwd_min_entropy); } - if (isset($use_pwnedpasswords)) { $smarty->assign('use_pwnedpasswords', $use_pwnedpasswords); } - if (isset($pwd_no_special_at_ends)) { $smarty->assign('pwd_no_special_at_ends', $pwd_no_special_at_ends); } - - // send policy to a JSON object usable in javascript (window.policy.[parameter]) - $smarty->assign('json_policy', base64_encode(json_encode( - array( - "pwd_min_length" => $pwd_min_length, - "pwd_max_length" => $pwd_max_length, - "pwd_min_lower" => $pwd_min_lower, - "pwd_min_upper" => $pwd_min_upper, - "pwd_min_digit" => $pwd_min_digit, - "pwd_min_special" => $pwd_min_special, - "pwd_complexity" => $pwd_complexity, - "pwd_diff_last_min_chars" => $pwd_diff_last_min_chars, - "pwd_forbidden_chars" => $pwd_forbidden_chars, - "pwd_no_reuse" => $pwd_no_reuse, - "pwd_diff_login" => $pwd_diff_login, - "pwd_display_entropy" => $pwd_display_entropy, - "pwd_check_entropy" => $pwd_check_entropy, - "pwd_min_entropy" => $pwd_min_entropy, - "use_pwnedpasswords" => $use_pwnedpasswords, - "pwd_no_special_at_ends" => $pwd_no_special_at_ends, - "pwd_special_chars" => $pwd_special_chars - ) - ))); -} + +\Ltb\Ppolicy::smarty_assign_ppolicy($smarty, $pwd_show_policy_pos, $pwd_show_policy, $result, $pwd_policy_config); if (isset($custompwdindex)) { $smarty->assign('custompwdindex', $custompwdindex); if (isset($change_custompwdfield[$custompwdindex]['msg_passwordchangedextramessage'])) { $smarty->assign('msg_passwordchangedextramessage', $change_custompwdfield[$custompwdindex]['msg_passwordchangedextramessage']); } } -// TODO : Make it clean function show_policy - END if (isset($smsdisplay)) { $smarty->assign('smsdisplay', $smsdisplay); } // TODO : Make it clean $prehook_return/$posthook_return - START if (isset($prehook_return)) { diff --git a/htdocs/js/ppolicy.js b/htdocs/js/ppolicy.js deleted file mode 100644 index aaabc367..00000000 --- a/htdocs/js/ppolicy.js +++ /dev/null @@ -1,376 +0,0 @@ -(function() { - var barWidth, bootstrapClasses, displayEntropyBar, displayEntropyBarMsg, ppolicyResults; - - ppolicyResults = {}; - - bootstrapClasses = new Map([["Err", "bg-danger"], ["0", "bg-danger"], ["1", "bg-warning"], ["2", "bg-info"], ["3", "bg-primary"], ["4", "bg-success"]]); - - barWidth = new Map([["Err", "0"], ["0", "20"], ["1", "40"], ["2", "60"], ["3", "80"], ["4", "100"]]); - - json_policy = $("#json-policy").data('policy'); - var local_policy = JSON.parse(atob(json_policy)); - - displayEntropyBar = function(level) { - $("#entropybar div").removeClass(); - $("#entropybar div").addClass('progress-bar'); - $("#entropybar div").width(barWidth.get(level) + '%'); - $("#entropybar div").addClass(bootstrapClasses.get(level)); - return $("#entropybar div").html(barWidth.get(level) + '%'); - }; - - displayEntropyBarMsg = function(msg) { - $("#entropybar-msg").html(msg); - if (msg.length === 0) { - return $("#entropybar-msg").addClass("entropyHidden"); - } else { - return $("#entropybar-msg").removeClass("entropyHidden"); - } - }; - - setResult = function(field, result) { - var ref, ref1; - ppolicyResults[field] = result; - $("#" + field).removeClass('fa-times fa-check fa-spinner fa-pulse fa-info-circle fa-question-circle text-danger text-success text-info text-secondary'); - $("#" + field).attr('role', 'status'); - switch (result) { - case "good": - $("#" + field).addClass('fa-check text-success'); - break; - case "bad": - $("#" + field).addClass('fa-times text-danger'); - $("#" + field).attr('role', 'alert'); - break; - case "unknown": - $("#" + field).addClass('fa-question-circle text-secondary'); - break; - case "waiting": - $("#" + field).addClass('fa-spinner fa-pulse text-secondary'); - break; - case "info": - $("#" + field).addClass('fa-info-circle text-info'); - } - if (Object.values(ppolicyResults).every((function(_this) { - return function(value) { - return value === "good" || value === "info"; - }; - })(this))) { - $('.ppolicy').removeClass('border-danger').addClass('border-success'); - return (ref = $('#newpassword').get(0)) != null ? ref.setCustomValidity('') : void 0; - } else { - $('.ppolicy').removeClass('border-success').addClass('border-danger'); - return (ref1 = $('#newpassword').get(0)) != null ? ref1.setCustomValidity("Insufficient quality") : void 0; - } - }; - - similar_text = function(first, second, percent) { - // discuss at: https://locutus.io/php/similar_text/ - // original by: RafaƂ Kukawski (https://blog.kukawski.pl) - // bugfixed by: Chris McMacken - // bugfixed by: Jarkko Rantavuori original by findings in stackoverflow (https://stackoverflow.com/questions/14136349/how-does-similar-text-work) - // improved by: Markus Padourek (taken from https://www.kevinhq.com/2012/06/php-similartext-function-in-javascript_16.html) - // MIT licenses - // example 1: similar_text('Hello World!', 'Hello locutus!') - // returns 1: 8 - // example 2: similar_text('Hello World!', null) - // returns 2: 0 - - if (first === null || - second === null || - typeof first === 'undefined' || - typeof second === 'undefined') { - return 0 - } - - first += '' - second += '' - - let pos1 = 0 - let pos2 = 0 - let max = 0 - const firstLength = first.length - const secondLength = second.length - let p - let q - let l - let sum - - for (p = 0; p < firstLength; p++) { - for (q = 0; q < secondLength; q++) { - for (l = 0; (p + l < firstLength) && (q + l < secondLength) && (first.charAt(p + l) === second.charAt(q + l)); l++) { - // @todo: ^-- break up this crazy for loop and put the logic in its body - } - if (l > max) { - max = l - pos1 = p - pos2 = q - } - } - } - - sum = max - - if (sum) { - if (pos1 && pos2) { - sum += similar_text(first.substr(0, pos1), second.substr(0, pos2)) - } - - if ((pos1 + max < firstLength) && (pos2 + max < secondLength)) { - sum += similar_text( - first.substr(pos1 + max, firstLength - pos1 - max), - second.substr(pos2 + max, - secondLength - pos2 - max)) - } - } - - if (!percent) { - return sum - } - - return (sum * 200) / (firstLength + secondLength) - } - - - // Generic feature for checkpassword action - // check all local policy criteria one by one and display an appropriate button for each - $(document).on('checkpassword', function(event, context) { - var digit, evType, hasforbidden, i, len, lower, numspechar, password, report, setResult, upper; - password = context.password; - evType = context.evType; - setResult = context.setResult; - report = function(result, id) { - if (result) { - return setResult(id, "good"); - } else { - return setResult(id, "bad"); - } - }; - - removePPolicyCriteria = function(criteria, feedback) { - // first consider the criteria as fullfilled - report( true , feedback); - // remove criteria from the list of ppolicy checks - delete local_policy[criteria]; - // remove the
  • tag parent to given feedback - $( "#" + feedback ).parent().remove(); - }; - - - // Criteria checks - if (local_policy.pwd_min_length > 0) { - report(password.length >= local_policy.pwd_min_length, 'ppolicy-pwd_min_length-feedback'); - } - - if (local_policy.pwd_max_length > 0) { - report(password.length <= local_policy.pwd_max_length, 'ppolicy-pwd_max_length-feedback'); - } - - if (local_policy.pwd_min_upper > 0) { - upper = password.match(/[A-Z]/g); - report(upper && upper.length >= local_policy.pwd_min_upper, 'ppolicy-pwd_min_upper-feedback'); - } - - if (local_policy.pwd_min_lower > 0) { - lower = password.match(/[a-z]/g); - report(lower && lower.length >= local_policy.pwd_min_lower, 'ppolicy-pwd_min_lower-feedback'); - } - - if (local_policy.pwd_min_digit > 0) { - digit = password.match(/[0-9]/g); - report(digit && digit.length >= local_policy.pwd_min_digit, 'ppolicy-pwd_min_digit-feedback'); - } - - if (local_policy.pwd_no_reuse && local_policy.pwd_no_reuse == true) { - if( $( "#oldpassword" ).length ) - { - oldpassword = $( "#oldpassword" ).val(); - report( password != oldpassword , 'ppolicy-pwd_no_reuse-feedback'); - } - else - { - removePPolicyCriteria("pwd_no_reuse", 'ppolicy-pwd_no_reuse-feedback'); - } - } - - if (local_policy.pwd_diff_login && local_policy.pwd_diff_login == true) { - if( $( "#login" ).length ) - { - login = $( "#login" ).val(); - report( password != login, 'ppolicy-pwd_diff_login-feedback'); - } - else - { - report( true , 'ppolicy-pwd_diff_login-feedback'); - } - } - - if (local_policy.pwd_diff_last_min_chars > 0) { - if( $( "#oldpassword" ).length ) - { - minDiffChars = local_policy.pwd_diff_last_min_chars; - oldpassword = $( "#oldpassword" ).val(); - - similarities = similar_text(oldpassword, password); - check_len = oldpassword.length < password.length ? oldpassword.length : password.length; - new_chars = check_len - similarities; - report( new_chars > minDiffChars , 'ppolicy-pwd_diff_last_min_chars-feedback'); - } - else - { - removePPolicyCriteria("pwd_diff_last_min_chars", 'ppolicy-pwd_diff_last_min_chars-feedback'); - } - } - - if (local_policy.pwd_forbidden_chars) { - forbiddenChars = local_policy.pwd_forbidden_chars; - forbidden = false; - i = 0; - while (i < password.length) { - if (forbiddenChars.indexOf(password.charAt(i)) != -1) { - forbidden = true; - } - i++; - } - report( !forbidden, 'ppolicy-pwd_forbidden_chars-feedback' ); - } - - if (local_policy.pwd_min_special > 0 && local_policy.pwd_special_chars) { - numspechar = 0; - var re = new RegExp("["+local_policy.pwd_special_chars+"]",""); - i = 0; - while (i < password.length) { - if (password.charAt(i).match(re)) { - numspechar++; - } - i++; - } - report(numspechar >= local_policy.pwd_min_special, 'ppolicy-pwd_min_special-feedback'); - } - - if ( local_policy.pwd_no_special_at_ends && - local_policy.pwd_no_special_at_ends == true && - local_policy.pwd_special_chars ) { - var re_start = new RegExp("^["+local_policy.pwd_special_chars+"]",""); - var re_end = new RegExp("["+local_policy.pwd_special_chars+"]$",""); - report( ( !password.match(re_start) && !password.match(re_end) ) , 'ppolicy-pwd_no_special_at_ends-feedback'); - } - - if ( local_policy.pwd_complexity) { - complexity = 0; - if (local_policy.pwd_special_chars) { - var re = new RegExp("["+local_policy.pwd_special_chars+"]",""); - if( password.match(re) ){ - complexity++; - } - } - if( password.match(/[A-Z]/g) ){ - complexity++; - } - if( password.match(/[a-z]/g) ){ - complexity++; - } - if( password.match(/[0-9]/g) ){ - complexity++; - } - report( complexity >= local_policy.pwd_complexity, 'ppolicy-pwd_complexity-feedback'); - } - - - if ( local_policy.use_pwnedpasswords) { - setResult('ppolicy-use_pwnedpasswords-feedback', "info"); - } - - }); - - - - // Specific feature for checkentropy action - $(document).on('checkpassword', function(event, context) { - var entropyrequired, entropyrequiredlevel, evType, newpasswordVal, password, setResult; - password = context.password; - evType = context.evType; - setResult = context.setResult; - if ($('#ppolicy-checkentropy-feedback').length > 0) { - newpasswordVal = $("#newpassword").val(); - entropyrequired = $("span[trspan='checkentropyLabel']").attr("data-checkentropy_required"); - entropyrequiredlevel = $("span[trspan='checkentropyLabel']").attr("data-checkentropy_required_level"); - if (newpasswordVal.length === 0) { - displayEntropyBar("Err"); - displayEntropyBarMsg(""); - setResult('ppolicy-checkentropy-feedback', "unknown"); - } - if (newpasswordVal.length > 0) { - return $.ajax({ - dataType: "json", - url: location.pathname + "?action=checkentropy", - method: "POST", - data: { "password": btoa(newpasswordVal) }, - context: document.body, - success: function(data) { - var level, msg; - level = data.level; - msg = data.message; - if (level !== void 0) { - if (parseInt(level) >= 0 && parseInt(level) <= 4) { - displayEntropyBar(level); - displayEntropyBarMsg(msg); - if (entropyrequired === "1" && entropyrequiredlevel.length > 0) { - if (parseInt(level) >= parseInt(entropyrequiredlevel)) { - setResult('ppolicy-checkentropy-feedback', "good"); - } else { - setResult('ppolicy-checkentropy-feedback', "bad"); - } - } - if (entropyrequired !== "1") { - return setResult('ppolicy-checkentropy-feedback', "good"); - } - } else if (parseInt(level) === -1) { - displayEntropyBar(level); - displayEntropyBarMsg(msg); - return setResult('ppolicy-checkentropy-feedback', "bad"); - } else { - displayEntropyBar(level); - displayEntropyBarMsg(msg); - return setResult('ppolicy-checkentropy-feedback', "unknown"); - } - } - }, - error: function(j, status, err) { - var res; - if (err) { - console.log('checkentropy: frontend error: ', err); - } - if (j) { - res = JSON.parse(j.responseText); - } - if (res && res.error) { - return console.log('checkentropy: returned error: ', res); - } - } - }); - } - } - }); - - - - checkpassword = function(password, evType) { - var e, info; - e = jQuery.Event("checkpassword"); - info = { - password: password, - evType: evType, - setResult: setResult - }; - return $(document).trigger(e, info); - }; - if ( (local_policy != null) && $('#newpassword').length) { - checkpassword(''); - $('#newpassword').keyup(function(e) { - checkpassword(e.target.value); - }); - $('#newpassword').focusout(function(e) { - checkpassword(e.target.value, "focusout"); - }); - } - -}).call(this); diff --git a/htdocs/resetbyquestions.php b/htdocs/resetbyquestions.php index ddfcd4a0..92fcc1c1 100644 --- a/htdocs/resetbyquestions.php +++ b/htdocs/resetbyquestions.php @@ -220,7 +220,7 @@ # Check password strength if ( !$result ) { - $result = check_password_strength( $newpassword, "", $pwd_policy_config, $login, $entry_array, $change_custompwdfield ); + $result = \Ltb\Ppolicy::check_password_strength( $newpassword, "", $pwd_policy_config, $login, $entry_array, $change_custompwdfield ); } # Change password diff --git a/htdocs/resetbytoken.php b/htdocs/resetbytoken.php index fe50a6ed..7910844d 100644 --- a/htdocs/resetbytoken.php +++ b/htdocs/resetbytoken.php @@ -152,7 +152,7 @@ # Check password strength if ( !$result ) { $entry_array = ldap_get_attributes($ldap, $entry); - $result = check_password_strength( $newpassword, "", $pwd_policy_config, $login, $entry_array, $change_custompwdfield ); + $result = \Ltb\Ppolicy::check_password_strength( $newpassword, "", $pwd_policy_config, $login, $entry_array, $change_custompwdfield ); } # Change password diff --git a/lib/functions.inc.php b/lib/functions.inc.php index e430c822..c1778ccc 100644 --- a/lib/functions.inc.php +++ b/lib/functions.inc.php @@ -33,16 +33,6 @@ */ require_once __DIR__ . '/../vendor/autoload.php'; -use ZxcvbnPhp\Zxcvbn; - -try{ - $zxcvbn = new Zxcvbn(); - error_log("Module Zxcvbn successfully loaded"); -} -catch(Throwable $e){ - error_log("Could not load Zxcvbn module: ".$e); - exit(1); -} # Generate SMS token function generate_sms_token( $sms_token_length ) { @@ -82,180 +72,6 @@ function get_fa_class( $msg) { } -# Check password strength -# @param array entry_array ldap entry ( ie not resource or LDAP\Result ) -# @return result code -function check_password_strength( $password, $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) { - extract( $pwd_policy_config ); - - $result = ""; - - $length = mb_strlen($password, mb_detect_encoding($password)); - preg_match_all("/[a-z]/", $password, $lower_res); - $lower = count( $lower_res[0] ); - preg_match_all("/[A-Z]/", $password, $upper_res); - $upper = count( $upper_res[0] ); - preg_match_all("/[0-9]/", $password, $digit_res); - $digit = count( $digit_res[0] ); - - $special = 0; - $special_at_ends = false; - if ( isset($pwd_special_chars) && !empty($pwd_special_chars) ) { - preg_match_all("/[$pwd_special_chars]/", $password, $special_res); - $special = count( $special_res[0] ); - if ( $pwd_no_special_at_ends ) { - $special_at_ends = preg_match("/(^[$pwd_special_chars]|[$pwd_special_chars]$)/", $password, $special_res); - } - } - - $forbidden = 0; - if ( isset($pwd_forbidden_chars) && !empty($pwd_forbidden_chars) ) { - preg_match_all("/[$pwd_forbidden_chars]/", $password, $forbidden_res); - $forbidden = count( $forbidden_res[0] ); - } - - # Complexity: checks for lower, upper, special, digits - if ( $pwd_complexity ) { - $complex = 0; - if ( $special > 0 ) { $complex++; } - if ( $digit > 0 ) { $complex++; } - if ( $lower > 0 ) { $complex++; } - if ( $upper > 0 ) { $complex++; } - if ( $complex < $pwd_complexity ) { $result="notcomplex"; } - } - - # Minimal lenght - if ( $pwd_min_length and $length < $pwd_min_length ) { $result="tooshort"; } - - # Maximal lenght - 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\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 etropy 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; -} - # Change password # @return result code function change_password( $ldapInstance, $dn, $password, $ad_mode, $ad_options, $samba_mode, $samba_options, $shadow_options, $hash, $hash_options, $who_change_password, $oldpassword, $use_exop_passwd, $use_ppolicy_control, $custom_pwd_field_mode, $custom_pwd_attribute ) { diff --git a/templates/footer.tpl b/templates/footer.tpl index 9af0a929..76d764fe 100644 --- a/templates/footer.tpl +++ b/templates/footer.tpl @@ -1,8 +1,8 @@ {if $display_footer} -
    {/if} + diff --git a/templates/header.tpl b/templates/header.tpl index 294ef3c8..d52adcd7 100644 --- a/templates/header.tpl +++ b/templates/header.tpl @@ -11,6 +11,7 @@ + {if $custom_css} {/if} diff --git a/templates/policy.tpl b/templates/policy.tpl deleted file mode 100644 index ebb484ba..00000000 --- a/templates/policy.tpl +++ /dev/null @@ -1,129 +0,0 @@ -{if $pwd_show_policy === "onerror" and !$pwd_show_policy_onerror } -{else} -
    - {$msg_policy|unescape: "html" nofilter} - -
    -{/if} diff --git a/tests/CheckPasswordTest.php b/tests/CheckPasswordTest.php deleted file mode 100644 index c7909cf8..00000000 --- a/tests/CheckPasswordTest.php +++ /dev/null @@ -1,229 +0,0 @@ - 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", check_password_strength( "coudot", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("sameasold", check_password_strength( "secret", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("forbiddenchars", check_password_strength( "p@ssword", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("minspecial", check_password_strength( "password", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("mindigit", check_password_strength( "!password", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("minupper", check_password_strength( "!1password", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("minlower", check_password_strength( "!1PASSWORD", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("toobig", check_password_strength( "!1verylongPassword", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("tooshort", check_password_strength( "!1Pa", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("sameascustompwd", check_password_strength( "!TestMe123!", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("sameascustompwd", 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", check_password_strength( "simple", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("specialatends", check_password_strength( "!simple", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("specialatends", check_password_strength( "simple?", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("forbiddenwords", check_password_strength( "companyname", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("forbiddenwords", check_password_strength( "trademark", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("forbiddenwords", check_password_strength( "working at companyname is fun", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("forbiddenldapfields", check_password_strength( "common name", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("forbiddenldapfields", check_password_strength( "my surname", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("diffminchars", check_password_strength( "C0mplex", "C0mplexC0mplex", $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("", check_password_strength( "C0mplex", "", $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("", check_password_strength( "C0mplex", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("", check_password_strength( "C0!mplex", $oldpassword, $pwd_policy_config, $login, $entry_array, $change_custompwdfield ) ); - $this->assertEquals("", 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() - { - - # Load functions - require_once __DIR__ . '/../lib/functions.inc.php'; - - $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", check_password_strength( "!1Password", $oldpassword, $pwd_policy_config, $login, array(), array() ) ); - } - - } - - /** - * Test check_password_strength function with weak entropy password - */ - public function testCheckPasswordStrengthWeakEntropy() - { - - # Load functions - require_once __DIR__ . '/../lib/functions.inc.php'; - - $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", check_password_strength( "secret", $oldpassword, $pwd_policy_config, $login, array(), array() ) ); - } - - } - - /** - * Test check_password_strength function with strong entropy password - */ - public function testCheckPasswordStrengthStrongEntropy() - { - - # Load functions - require_once __DIR__ . '/../lib/functions.inc.php'; - - $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("", check_password_strength( "Th!Sis@Str0ngP@ss0rd", $oldpassword, $pwd_policy_config, $login, array(), array() ) ); - } - - } - -}