diff --git a/main/admin/user_add.php b/main/admin/user_add.php index da2ecbda51..a72250e569 100644 --- a/main/admin/user_add.php +++ b/main/admin/user_add.php @@ -23,12 +23,33 @@ api_protect_admin_script(true); $is_platform_admin = api_is_platform_admin() ? 1 : 0; $message = null; - +$htmlHeadXtra[] = api_get_password_checker_js('#password'); $htmlHeadXtra[] = ''; $htmlHeadXtra[] = ''; + +if (isset($_configuration['allow_strength_pass_checker']) && $_configuration['allow_strength_pass_checker']) { + $htmlHeadXtra[] = ' + '; +} + $htmlHeadXtra[] = ' '; if (!empty($_GET['message'])) { @@ -157,9 +179,14 @@ if (count($extAuthSource) > 0) { } $group[] = $form->createElement('radio', 'password_auto', get_lang('Password'), get_lang('AutoGeneratePassword').'
', 1); $group[] = $form->createElement('radio', 'password_auto', 'id="radio_user_password"', null, 0); -$group[] = $form->createElement('password', 'password', null, array('onkeydown' => 'javascript: password_switch_radio_button();')); +$group[] = $form->createElement('password', 'password', null, array('id'=> 'password', 'onkeydown' => 'javascript: password_switch_radio_button();')); + $form->addGroup($group, 'password', get_lang('Password'), ''); +if (isset($_configuration['allow_strength_pass_checker']) && $_configuration['allow_strength_pass_checker']) { + $form->addElement('label', null, ''); +} + // Status $status = array(); $status[COURSEMANAGER] = get_lang('Teacher'); @@ -337,4 +364,4 @@ $tpl = new Template($tool_name); //$tpl->assign('actions', $actions); $tpl->assign('message', $message); $tpl->assign('content', $content); -$tpl->display_one_col_template(); \ No newline at end of file +$tpl->display_one_col_template(); diff --git a/main/auth/inscription.php b/main/auth/inscription.php index e34bae3f76..8d27aea151 100644 --- a/main/auth/inscription.php +++ b/main/auth/inscription.php @@ -17,6 +17,8 @@ require_once '../inc/global.inc.php'; require_once api_get_path(CONFIGURATION_PATH).'profile.conf.php'; require_once api_get_path(LIBRARY_PATH).'mail.lib.inc.php'; +$htmlHeadXtra[] = api_get_password_checker_js('#pass1'); + if (api_get_setting('allow_registration') === 'false') { api_not_allowed(true); } @@ -51,10 +53,10 @@ if ($user_already_registered_show_terms == false) { if (api_is_western_name_order()) { // FIRST NAME and LAST NAME $form->addElement('text', 'firstname', get_lang('FirstName'), array('size' => 40)); - $form->addElement('text', 'lastname', get_lang('LastName'), array('size' => 40)); + $form->addElement('text', 'lastname', get_lang('LastName'), array('size' => 40)); } else { // LAST NAME and FIRST NAME - $form->addElement('text', 'lastname', get_lang('LastName'), array('size' => 40)); + $form->addElement('text', 'lastname', get_lang('LastName'), array('size' => 40)); $form->addElement('text', 'firstname', get_lang('FirstName'), array('size' => 40)); } $form->applyFilter(array('lastname', 'firstname'), 'trim'); @@ -68,7 +70,7 @@ if ($user_already_registered_show_terms == false) { } if (api_get_setting('login_is_email') == 'true') { - $form->applyFilter('email','trim'); + $form->applyFilter('email', 'trim'); if (api_get_setting('registration', 'email') != 'true') { $form->addRule('email', get_lang('ThisFieldIsRequired'), 'required'); } @@ -99,8 +101,13 @@ if ($user_already_registered_show_terms == false) { } // PASSWORD - $form->addElement('password', 'pass1', get_lang('Pass'), array('size' => 20, 'autocomplete' => 'off')); - $form->addElement('password', 'pass2', get_lang('Confirmation'), array('size' => 20, 'autocomplete' => 'off')); + $form->addElement('password', 'pass1', get_lang('Pass'), array('id' => 'pass1', 'size' => 20, 'autocomplete' => 'off')); + + if (isset($_configuration['allow_strength_pass_checker']) && $_configuration['allow_strength_pass_checker']) { + $form->addElement('label', null, '
'); + } + + $form->addElement('password', 'pass2', get_lang('Confirmation'), array('id' => 'pass2', 'size' => 20, 'autocomplete' => 'off')); $form->addRule('pass1', get_lang('ThisFieldIsRequired'), 'required'); $form->addRule('pass2', get_lang('ThisFieldIsRequired'), 'required'); $form->addRule(array('pass1', 'pass2'), get_lang('PassTwo'), 'compare'); diff --git a/main/auth/profile.php b/main/auth/profile.php index fa05dac6ca..0cd137e755 100644 --- a/main/auth/profile.php +++ b/main/auth/profile.php @@ -23,6 +23,9 @@ if (api_get_setting('allow_social_tool') == 'true') { $this_section = SECTION_MYPROFILE; } + +$htmlHeadXtra[] = api_get_password_checker_js('#password1'); + $_SESSION['this_section'] = $this_section; if (!(isset($_user['user_id']) && $_user['user_id']) || api_is_anonymous($_user['user_id'], true)) { @@ -257,7 +260,11 @@ if (api_get_setting('extended_profile') == 'true') { // PASSWORD, if auth_source is platform if (is_platform_authentication() && is_profile_editable() && api_get_setting('profile', 'password') == 'true') { $form->addElement('password', 'password0', array(get_lang('Pass'), get_lang('Enter2passToChange')), array('size' => 40)); - $form->addElement('password', 'password1', get_lang('NewPass'), array('size' => 40)); + $form->addElement('password', 'password1', get_lang('NewPass'), array('id'=> 'password1', 'size' => 40)); + + if (isset($_configuration['allow_strength_pass_checker']) && $_configuration['allow_strength_pass_checker']) { + $form->addElement('label', null, '
'); + } $form->addElement('password', 'password2', get_lang('Confirmation'), array('size' => 40)); // user must enter identical password twice so we can prevent some user errors $form->addRule(array('password1', 'password2'), get_lang('PassTwo'), 'compare'); diff --git a/main/css/base.css b/main/css/base.css index 9e072a9296..88534f0806 100644 --- a/main/css/base.css +++ b/main/css/base.css @@ -27,7 +27,7 @@ legend { #header_right { text-align: right; float: right; - + } @@ -175,9 +175,9 @@ header #logo { #main .nav .dropdown { width:150px; text-align: right; - -} - + +} + #main .nav .dropdown:nth-child(n) { width:auto; } @@ -198,7 +198,7 @@ header { .breadcrumb li img { width: 0%; } - + .breadcrumb li img:nth-child(n) { width: auto; } @@ -434,7 +434,7 @@ footer #footer_left { vertical-align:middle; } .actions .actions-pagination { - float:right; + float:right; margin-top:2px; } .actions .actions-pagination * { @@ -4875,3 +4875,9 @@ i.size-32.icon-new-work{ background-color: #e6e6e6; } +.password-verdict { + padding-left:5px; +} +#password_progress { + width: 220px; +} diff --git a/main/inc/lib/javascript/strength/strength.js b/main/inc/lib/javascript/strength/strength.js new file mode 100644 index 0000000000..730f8917d4 --- /dev/null +++ b/main/inc/lib/javascript/strength/strength.js @@ -0,0 +1,315 @@ +/*jslint vars: false, browser: true, nomen: true, regexp: true */ +/*global jQuery */ + +/* +* jQuery Password Strength plugin for Twitter Bootstrap +* +* Copyright (c) 2008-2013 Tane Piper +* Copyright (c) 2013 Alejandro Blanco +* Dual licensed under the MIT and GPL licenses. +* +*/ + +(function ($) { + "use strict"; + + var options = { + errors: [], + // Options + minChar: 8, + errorMessages: { + password_to_short: "The Password is too short", + same_as_username: "Your password cannot be the same as your username" + }, + scores: [17, 26, 40, 50], + verdicts: ["Weak", "Normal", "Medium", "Strong", "Very Strong"], + showVerdicts: true, + raisePower: 1.4, + usernameField: "#username", + onLoad: undefined, + onKeyUp: undefined, + viewports: { + progress: undefined, + verdict: undefined, + errors: undefined + }, + // Rules stuff + ruleScores: { + wordNotEmail: -100, + wordLength: -100, + wordSimilarToUsername: -100, + wordLowercase: 1, + wordUppercase: 3, + wordOneNumber: 3, + wordThreeNumbers: 5, + wordOneSpecialChar: 3, + wordTwoSpecialChar: 5, + wordUpperLowerCombo: 2, + wordLetterNumberCombo: 2, + wordLetterNumberCharCombo: 2 + }, + rules: { + wordNotEmail: true, + wordLength: true, + wordSimilarToUsername: true, + wordLowercase: true, + wordUppercase: true, + wordOneNumber: true, + wordThreeNumbers: true, + wordOneSpecialChar: true, + wordTwoSpecialChar: true, + wordUpperLowerCombo: true, + wordLetterNumberCombo: true, + wordLetterNumberCharCombo: true + }, + validationRules: { + wordNotEmail: function (options, word, score) { + return word.match(/^([\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+\.)*[\w\!\#$\%\&\'\*\+\-\/\=\?\^\`{\|\}\~]+@((((([a-z0-9]{1}[a-z0-9\-]{0,62}[a-z0-9]{1})|[a-z])\.)+[a-z]{2,6})|(\d{1,3}\.){3}\d{1,3}(\:\d{1,5})?)$/i) && score; + }, + wordLength: function (options, word, score) { + var wordlen = word.length, + lenScore = Math.pow(wordlen, options.raisePower); + if (wordlen < options.minChar) { + lenScore = (lenScore + score); + options.errors.push(options.errorMessages.password_to_short); + } + return lenScore; + }, + wordSimilarToUsername: function (options, word, score) { + var username = $(options.usernameField).val(); + if (username && word.toLowerCase().match(username.toLowerCase())) { + options.errors.push(options.errorMessages.same_as_username); + return score; + } + return true; + }, + wordLowercase: function (options, word, score) { + return word.match(/[a-z]/) && score; + }, + wordUppercase: function (options, word, score) { + return word.match(/[A-Z]/) && score; + }, + wordOneNumber : function (options, word, score) { + return word.match(/\d+/) && score; + }, + wordThreeNumbers : function (options, word, score) { + return word.match(/(.*[0-9].*[0-9].*[0-9])/) && score; + }, + wordOneSpecialChar : function (options, word, score) { + return word.match(/.[!,@,#,$,%,\^,&,*,?,_,~]/) && score; + }, + wordTwoSpecialChar : function (options, word, score) { + return word.match(/(.*[!,@,#,$,%,\^,&,*,?,_,~].*[!,@,#,$,%,\^,&,*,?,_,~])/) && score; + }, + wordUpperLowerCombo : function (options, word, score) { + return word.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/) && score; + }, + wordLetterNumberCombo : function (options, word, score) { + return word.match(/([a-zA-Z])/) && word.match(/([0-9])/) && score; + }, + wordLetterNumberCharCombo : function (options, word, score) { + return word.match(/([a-zA-Z0-9].*[!,@,#,$,%,\^,&,*,?,_,~])|([!,@,#,$,%,\^,&,*,?,_,~].*[a-zA-Z0-9])/) && score; + } + } + }, + + setProgressBar = function ($el, score) { + var options = $el.data("pwstrength"), + progressbar = options.progressbar, + $verdict; + + if (options.showVerdicts) { + if (options.viewports.verdict) { + $verdict = $(options.viewports.verdict).find(".password-verdict"); + } else { + $verdict = $el.parent().find(".password-verdict"); + if ($verdict.length === 0) { + $verdict = $(''); + $verdict.insertAfter($el); + } + } + } + + if (score < options.scores[0]) { + progressbar.addClass("progress-danger").removeClass("progress-warning").removeClass("progress-success"); + progressbar.find(".bar").css("width", "5%"); + if (options.showVerdicts) { + $verdict.text(options.verdicts[0]); + } + } else if (score >= options.scores[0] && score < options.scores[1]) { + progressbar.addClass("progress-danger").removeClass("progress-warning").removeClass("progress-success"); + progressbar.find(".bar").css("width", "25%"); + if (options.showVerdicts) { + $verdict.text(options.verdicts[1]); + } + } else if (score >= options.scores[1] && score < options.scores[2]) { + progressbar.addClass("progress-warning").removeClass("progress-danger").removeClass("progress-success"); + progressbar.find(".bar").css("width", "50%"); + if (options.showVerdicts) { + $verdict.text(options.verdicts[2]); + } + } else if (score >= options.scores[2] && score < options.scores[3]) { + progressbar.addClass("progress-warning").removeClass("progress-danger").removeClass("progress-success"); + progressbar.find(".bar").css("width", "75%"); + if (options.showVerdicts) { + $verdict.text(options.verdicts[3]); + } + } else if (score >= options.scores[3]) { + progressbar.addClass("progress-success").removeClass("progress-warning").removeClass("progress-danger"); + progressbar.find(".bar").css("width", "100%"); + if (options.showVerdicts) { + $verdict.text(options.verdicts[4]); + } + } + }, + + calculateScore = function ($el) { + var self = this, + word = $el.val(), + totalScore = 0, + options = $el.data("pwstrength"); + + $.each(options.rules, function (rule, active) { + if (active === true) { + var score = options.ruleScores[rule], + result = options.validationRules[rule](options, word, score); + if (result) { + totalScore += result; + } + } + }); + setProgressBar($el, totalScore); + return totalScore; + }, + + progressWidget = function () { + return '
'; + }, + + methods = { + init: function (settings) { + var self = this, + allOptions = $.extend(options, settings); + + return this.each(function (idx, el) { + var $el = $(el), + progressbar, + verdict; + + $el.data("pwstrength", allOptions); + + $el.on("keyup", function (event) { + var options = $el.data("pwstrength"); + options.errors = []; + calculateScore.call(self, $el); + if ($.isFunction(options.onKeyUp)) { + options.onKeyUp(event); + } + }); + + progressbar = $(progressWidget()); + if (allOptions.viewports.progress) { + $(allOptions.viewports.progress).append(progressbar); + } else { + progressbar.insertAfter($el); + } + progressbar.find(".bar").css("width", "0%"); + $el.data("pwstrength").progressbar = progressbar; + + if (allOptions.showVerdicts) { + verdict = $('' + allOptions.verdicts[0] + ''); + if (allOptions.viewports.verdict) { + $(allOptions.viewports.verdict).append(verdict); + } else { + verdict.insertAfter($el); + } + } + + if ($.isFunction(allOptions.onLoad)) { + allOptions.onLoad(); + } + }); + }, + + destroy: function () { + this.each(function (idx, el) { + var $el = $(el); + $el.parent().find("span.password-verdict").remove(); + $el.parent().find("div.progress").remove(); + $el.parent().find("ul.error-list").remove(); + $el.removeData("pwstrength"); + }); + }, + + forceUpdate: function () { + var self = this; + this.each(function (idx, el) { + var $el = $(el), + options = $el.data("pwstrength"); + options.errors = []; + calculateScore.call(self, $el); + }); + }, + + outputErrorList: function () { + this.each(function (idx, el) { + var output = ''; + if (viewports.errors) { + $(viewports.errors).html(output); + } else { + output = $(output); + verdict = $el.parent().find("span.password-verdict"); + if (verdict.length > 0) { + el = verdict; + } + output.insertAfter(el); + } + } + }); + }, + + addRule: function (name, method, score, active) { + this.each(function (idx, el) { + var options = $(el).data("pwstrength"); + options.rules[name] = active; + options.ruleScores[name] = score; + options.validationRules[name] = method; + }); + }, + + changeScore: function (rule, score) { + this.each(function (idx, el) { + $(el).data("pwstrength").ruleScores[rule] = score; + }); + }, + + ruleActive: function (rule, active) { + this.each(function (idx, el) { + $(el).data("pwstrength").rules[rule] = active; + }); + } + }; + + $.fn.pwstrength = function (method) { + var result; + if (methods[method]) { + result = methods[method].apply(this, Array.prototype.slice.call(arguments, 1)); + } else if (typeof method === "object" || !method) { + result = methods.init.apply(this, arguments); + } else { + $.error("Method " + method + " does not exist on jQuery.pwstrength"); + } + return result; + }; +}(jQuery)); diff --git a/main/inc/lib/main_api.lib.php b/main/inc/lib/main_api.lib.php index 4413ff35b6..ed02c3829c 100644 --- a/main/inc/lib/main_api.lib.php +++ b/main/inc/lib/main_api.lib.php @@ -6451,3 +6451,44 @@ function api_get_user_info_from_official_code($official_code = '') { } return false; } + +function api_get_password_checker_js($inputId) +{ + global $_configuration; + $useStrengthPassChecker = isset($_configuration['allow_strength_pass_checker']) ? $_configuration['allow_strength_pass_checker'] : false; + + if ($useStrengthPassChecker == false) { + return null; + } + + $verdicts = array(get_lang('Weak'), get_lang('Normal'), get_lang('Medium'), get_lang('Strong'), get_lang('VeryStrong')); + $js = api_get_js('strength/strength.js'); + $js .= ""; + return $js; + +}