From 6773a8829b308927162c112f6c1b1f7dedaad81d Mon Sep 17 00:00:00 2001 From: Julio Montoya Date: Fri, 9 Aug 2013 13:12:50 +0200 Subject: [PATCH] Adding account blocked due number of wrong attempts see BT#6486 --- main/inc/lib/main_api.lib.php | 39 ++++++++++++++ main/inc/lib/userportal.lib.php | 53 ++++++++++-------- main/inc/local.inc.php | 95 ++++++++++++++++++++++++--------- 3 files changed, 141 insertions(+), 46 deletions(-) diff --git a/main/inc/lib/main_api.lib.php b/main/inc/lib/main_api.lib.php index 0904aaeed6..e9155e5118 100644 --- a/main/inc/lib/main_api.lib.php +++ b/main/inc/lib/main_api.lib.php @@ -6510,3 +6510,42 @@ function api_get_easy_password_list() } return $passwordList; } + +/** + * +* create an user extra field called 'captcha_blocked_until_date' + */ +function api_block_account_captcha($username) +{ + $userInfo = api_get_user_info_from_username($username); + if (empty($userInfo)) { + return false; + } + global $_configuration; + $minutesToBlock = isset($_configuration['captcha_time_to_block']) ? $_configuration['captcha_time_to_block'] : 10; + $time = time() + $minutesToBlock*60; + Usermanager::update_extra_field_value($userInfo['user_id'], 'captcha_blocked_until_date', api_get_utc_datetime($time)); +} + +function api_clean_account_captcha($username) +{ + $userInfo = api_get_user_info_from_username($username); + if (empty($userInfo)) { + return false; + } + Session::erase('loginFailedCount'); + Usermanager::update_extra_field_value($userInfo['user_id'], 'captcha_blocked_until_date', null); +} + +function api_get_user_blocked_by_captcha($username) +{ + $userInfo = api_get_user_info_from_username($username); + if (empty($userInfo)) { + return false; + } + $data = Usermanager::get_extra_user_data_by_field($userInfo['user_id'], 'captcha_blocked_until_date'); + if (isset($data)) { + return $data['captcha_blocked_until_date']; + } + return false; +} diff --git a/main/inc/lib/userportal.lib.php b/main/inc/lib/userportal.lib.php index 334d01da30..9a46cf5ad7 100644 --- a/main/inc/lib/userportal.lib.php +++ b/main/inc/lib/userportal.lib.php @@ -335,6 +335,9 @@ class IndexManager { case 'wrong_captcha': $message = get_lang('TheTextYouEnteredDoesNotMatchThePicture'); break; + case 'blocked_by_captcha': + $message = get_lang('AccountBlockedByCaptcha'); + break; case 'unrecognize_sso_origin': //$message = get_lang('SSOError'); break; @@ -646,37 +649,43 @@ class IndexManager { $form = new FormValidator('formLogin', 'POST', null, null, array('class'=>'form-vertical')); $form->addElement('text', 'login', get_lang('UserName'), array('class' => 'span2 autocapitalize_off', 'autofocus' => 'autofocus')); $form->addElement('password', 'password', get_lang('Pass'), array('class' => 'span2')); + global $_configuration; // Captcha + $allowCaptcha = isset($_configuration['allow_captcha']) ? $_configuration['allow_captcha'] : false; + + if ($allowCaptcha) { - $useCaptcha = isset($_SESSION['loginFailed']) ? $_SESSION['loginFailed'] : null; + $useCaptcha = isset($_SESSION['loginFailed']) ? $_SESSION['loginFailed'] : null; - if ($useCaptcha) { + if ($useCaptcha) { - $form->addElement('text', 'captcha', 'Enter the letters you see'); - $form->addRule('captcha', 'Enter the characters you read in the image', 'required', null, 'client'); + $ajax = api_get_path(WEB_AJAX_PATH).'form.ajax.php?a=get_captcha'; - $ajax = api_get_path(WEB_AJAX_PATH).'form.ajax.php?a=get_captcha'; + $options = array( + 'width' => 250, + 'height' => 90, + 'callback' => $ajax.'&var='.basename(__FILE__, '.php'), + 'sessionVar' => basename(__FILE__, '.php'), + 'imageOptions' => array( + 'font_size' => 20, + 'font_path' => api_get_path(LIBRARY_PATH).'pchart/fonts/', + 'font_file' => 'tahoma.ttf', + //'output' => 'gif' + ) + ); - $options = array( - 'width' => 250, - 'height' => 90, - 'callback' => $ajax.'&var='.basename(__FILE__, '.php'), - 'sessionVar' => basename(__FILE__, '.php'), - 'imageOptions' => array( - 'font_size' => 20, - 'font_path' => api_get_path(LIBRARY_PATH).'pchart/fonts/', - 'font_file' => 'tahoma.ttf', - //'output' => 'gif' - ) - ); + // Minimum options using all defaults (including defaults for Image_Text): + //$options = array('callback' => 'qfcaptcha_image.php'); - // Minimum options using all defaults (including defaults for Image_Text): - //$options = array('callback' => 'qfcaptcha_image.php'); + $captcha_question = $form->addElement('CAPTCHA_Image', 'captcha_question', '', $options); + $form->addElement('static', null, null, get_lang('ClickOnTheImageForANewOne')); - $captcha_question = $form->addElement('CAPTCHA_Image', 'captcha_question', 'Verification', $options); - $form->addElement('static', null, null, 'Click on the image for a new one'); - $form->addRule('captcha', 'What you entered didn\'t match the picture', 'CAPTCHA', $captcha_question); + $form->addElement('text', 'captcha', get_lang('EnterTheLettersYouSee')); + $form->addRule('captcha', get_lang('EnterTheCharactersYouReadInTheImage'), 'required', null, 'client'); + + $form->addRule('captcha', 'What you entered didn\'t match the picture', 'CAPTCHA', $captcha_question); + } } $form->addElement('style_submit_button','submitAuth', get_lang('LoginEnter'), array('class' => 'btn')); diff --git a/main/inc/local.inc.php b/main/inc/local.inc.php index dbc9e172b4..b9c6f4f5b0 100644 --- a/main/inc/local.inc.php +++ b/main/inc/local.inc.php @@ -237,44 +237,69 @@ if (!empty($_SESSION['_user']['user_id']) && !($login || $logout)) { WHERE username = '".Database::escape_string($login)."'"; $result = Database::query($sql); - $catpchaValidated = true; + $captchaValidated = true; + $allowCaptcha = isset($_configuration['allow_captcha']) ? $_configuration['allow_captcha'] : false; if (Database::num_rows($result) > 0) { $uData = Database::fetch_array($result); - if (isset($_POST['captcha'])) { - // Check captcha - $captchaText = $_POST['captcha']; - /** @var Text_CAPTCHA $obj */ - $obj = isset($_SESSION['userportal.lib']) ? $_SESSION['userportal.lib'] : null; - if ($obj) { - $obj->getPhrase(); - if ($obj->getPhrase() != $captchaText) { - $catpchaValidated = false; - } else { - $catpchaValidated = true; + if ($allowCaptcha) { + + // Checking captcha + if (isset($_POST['captcha'])) { + // Check captcha + $captchaText = $_POST['captcha']; + /** @var Text_CAPTCHA $obj */ + $obj = isset($_SESSION['userportal.lib']) ? $_SESSION['userportal.lib'] : null; + if ($obj) { + $obj->getPhrase(); + if ($obj->getPhrase() != $captchaText) { + $captchaValidated = false; + } else { + $captchaValidated = true; + } + } + if (isset($_SESSION['captcha_question'])) { + $captcha_question = $_SESSION['captcha_question']; + $captcha_question->destroy(); } } - if (isset($_SESSION['captcha_question'])) { - $captcha_question = $_SESSION['captcha_question']; - $captcha_question->destroy(); + + // Redirect to login page + if ($captchaValidated == false) { + $loginFailed = true; + Session::erase('_uid'); + Session::write('loginFailed', '1'); + + header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=wrong_captcha'); + exit; } - } - if ($catpchaValidated == false) { - $loginFailed = true; - Session::erase('_uid'); - Session::write('loginFailed', '1'); - header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=wrong_captcha'); - exit; + // Check if account is blocked by captcha user extra field see function api_block_account_captcha() + $blockedUntilDate = api_get_user_blocked_by_captcha($login); + + if (isset($blockedUntilDate) && !empty($blockedUntilDate)) { + if (time() > api_strtotime($blockedUntilDate, 'UTC')) { + api_clean_account_captcha($login); + + } else { + $loginFailed = true; + Session::erase('_uid'); + Session::write('loginFailed', '1'); + + header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=blocked_by_captcha'); + exit; + } + } } + if ($uData['auth_source'] == PLATFORM_AUTH_SOURCE || $uData['auth_source'] == CAS_AUTH_SOURCE) { //The authentification of this user is managed by Chamilo itself $password = api_get_encrypted_password(trim(stripslashes($password))); // Check the user's password - if (($password == $uData['password'] OR $cas_login) AND (trim($login) == $uData['username'])) { + if (($password == $uData['password'] OR $cas_login) AND (trim($login) == $uData['username'])) { $update_type = UserManager::get_extra_user_data_by_field($uData['user_id'], 'update_type'); $update_type= $update_type['update_type']; if (!empty($extAuthSource[$update_type]['updateUser']) && file_exists($extAuthSource[$update_type]['updateUser'])) { @@ -377,6 +402,24 @@ if (!empty($_SESSION['_user']['user_id']) && !($login || $logout)) { $loginFailed = true; Session::erase('_uid'); Session::write('loginFailed', '1'); + + if ($allowCaptcha) { + + if (isset($_SESSION['loginFailedCount'])) { + $_SESSION['loginFailedCount']++; + } else { + $_SESSION['loginFailedCount'] = 1; + } + + $numberMistakesToBlockAccount = isset($_configuration['captcha_number_mistakes_to_block_account']) ? $_configuration['captcha_number_mistakes_to_block_account'] : 10; + + if (isset($_SESSION['loginFailedCount'])) { + if ($_SESSION['loginFailedCount'] >= $numberMistakesToBlockAccount) { + api_block_account_captcha($login); + } + } + } + header('Location: '.api_get_path(WEB_PATH).'index.php?loginFailed=1&error=user_password_incorrect'); exit; } @@ -618,8 +661,12 @@ if (isset($uidReset) && $uidReset) { // session data refresh requested $is_allowedCreateCourse = false; if (isset($_user['user_id']) && $_user['user_id'] && ! api_is_anonymous()) { - // a uid is given (log in succeeded) + // a uid is given (log in succeeded) + $_SESSION['loginFailed'] = false; + unset($_SESSION['loginFailedCount']); + unset($_SESSION['loginToBlock']); + $user_table = Database::get_main_table(TABLE_MAIN_USER); $admin_table = Database::get_main_table(TABLE_MAIN_ADMIN); $track_e_login = Database::get_statistic_table(TABLE_STATISTIC_TRACK_E_LOGIN);