diff --git a/classes/MaintenanceAccess.php b/classes/MaintenanceAccess.php index 11e0f758d..2f21ad736 100644 --- a/classes/MaintenanceAccess.php +++ b/classes/MaintenanceAccess.php @@ -19,23 +19,15 @@ * @license https://store.webkul.com/license.html */ -class MaintenanceAccessCore extends ObjectModel +class MaintenanceAccessCore extends ObjectModel { - public $id_maintenance_access; public $ip_address; public $email; public $date_add; - const USERNAME_ATTEMPTS_PER_QUARTER_HOUR = 3; - const IP_ATTEMPTS_QUARTER_HOUR = 12; - const USERNAME_ATTEMPTS_PER_HOUR = 6; - const IP_ATTEMPTS_PER_HOUR = 24; - const QUARTER_HOUR = 60*15; - const HOUR = 60*60; - /** - * @see ObjectModel::$definition - */ + const LOGIN_ATTEMPTS_WINDOW = 30; /* in minutes */ + public static $definition = array( 'table' => 'maintenance_access', 'primary' => 'id_maintenance_access', @@ -46,51 +38,32 @@ class MaintenanceAccessCore extends ObjectModel ), ); - public function cleanData() + public function removeFailedAttempts($email, $ipAddress) { - $time = (time() - (self::HOUR*2)); Db::getInstance()->execute( - 'DELETE FROM `' . _DB_PREFIX_ .'maintenance_access` - WHERE `date_add` < "' . pSQL(date("Y-m-d H:i:s", $time)) . '"' + 'DELETE FROM `' . _DB_PREFIX_ .'maintenance_access` ma + WHERE (ma.`email` = "'.$email.'" OR ma.`ip_address` = "'.$ipAddress.'") + AND ma.`date_add` > "'.date('Y-m-d H:i:s', strtotime('-'.MaintenanceAccess::LOGIN_ATTEMPTS_WINDOW.' minutes')).'"' ); } - public function getUserFailedCount($time, $email=false, $ip_address=false, $attempts) + public function getAttemptsCount($email, $ipAddress) { - $sql = "SELECT COUNT(`id_maintenance_access`) FROM `". _DB_PREFIX_ ."maintenance_access` WHERE `date_add` > '" - . pSQL(date('Y-m-d H:i:s', $time)) ."'"; - - if ($email) { - $sql .= " AND `email` = '".pSQL($email)."'"; - } - - if ($ip_address) { - $sql .= " AND `ip_address` = '".pSQL($ip_address)."'"; - } - - return Db::getInstance()->getValue($sql) >= $attempts; + return Db::getInstance()->getValue( + 'SELECT COUNT(ma.`id_maintenance_access`) + FROM `'._DB_PREFIX_.'maintenance_access` ma + WHERE (ma.`email` = "'.$email.'" OR ma.`ip_address` = "'.$ipAddress.'") + AND ma.`date_add` > "'.date('Y-m-d H:i:s', strtotime('-'.MaintenanceAccess::LOGIN_ATTEMPTS_WINDOW.' minutes')).'"' + ); } - public function checkLimit($email) + public function getLastAttempt($email, $ipAddress) { - $has_error = false; - $minutes_ago = time() - self::QUARTER_HOUR; - $hours_ago = time() - self::HOUR; - $ip_address = Tools::getRemoteAddr(); - - $has_error |= $this->getUserFailedCount( - $minutes_ago, $email, false, self::USERNAME_ATTEMPTS_PER_QUARTER_HOUR - )? true : $has_error; - $has_error |= $this->getUserFailedCount( - $minutes_ago, false, $ip_address, self::IP_ATTEMPTS_QUARTER_HOUR - )? true : $has_error; - $has_error |= $this->getUserFailedCount( - $hours_ago, $email, false, self::USERNAME_ATTEMPTS_PER_HOUR - )? true : $has_error; - $has_error |= $this->getUserFailedCount( - $hours_ago, false, $ip_address, self::IP_ATTEMPTS_PER_HOUR - )? true : $has_error; - - return $has_error; + return Db::getInstance()->getRow( + 'SELECT * + FROM `'._DB_PREFIX_.'maintenance_access` ma + WHERE (ma.`email` = "'.$email.'" OR ma.`ip_address` = "'.$ipAddress.'") + ORDER BY ma.`date_add` DESC' + ); } -} \ No newline at end of file +} diff --git a/classes/controller/FrontController.php b/classes/controller/FrontController.php index 6378c8570..d8ccd0fd9 100644 --- a/classes/controller/FrontController.php +++ b/classes/controller/FrontController.php @@ -776,6 +776,7 @@ class FrontControllerCore extends Controller } if (Tools::isSubmit('SubmitLogin') + && !Configuration::get('PS_SHOP_ENABLE') && Configuration::get('PS_ALLOW_EMP') && !Context::getContext()->cookie->enable_maintenance_view ) { @@ -812,24 +813,31 @@ class FrontControllerCore extends Controller protected function processLogin($email, $passwd) { - $result = false; + $remoteIpAddress = Tools::getRemoteAddr(); $objMaintenanceAccess = new MaintenanceAccess(); + $attemptsCount = $objMaintenanceAccess->getAttemptsCount($email, $remoteIpAddress); // does not include the current one + $maxAttempts = Configuration::get('PS_ALLOW_EMP_MAX_ATTEMPTS'); - /* Check fields validity */ if (empty($email)) { - $this->errors[] = Tools::displayError('Email is empty.'); + $this->errors[] = Tools::displayError('The Email address field is blank.'); } elseif (!Validate::isEmail($email)) { $this->errors[] = Tools::displayError('Invalid email address.'); } if (empty($passwd)) { - $this->errors[] = Tools::displayError('The password field is blank.'); + $this->errors[] = Tools::displayError('The Password field is blank.'); } elseif (!Validate::isPasswd($passwd)) { $this->errors[] = Tools::displayError('Invalid password.'); } - if ($objMaintenanceAccess->checkLimit($email)) { - $this->errors[] =Tools::displayError('You have exceeded the limit of login attempts, please try after some time'); + if (!count($this->errors)) { + if ($attemptsCount >= $maxAttempts) { + if ($lastAttempt = $objMaintenanceAccess->getLastAttempt($email, $remoteIpAddress)) { + $minutes = round((time() - strtotime($lastAttempt['date_add'])) / 60); + + $this->errors[] = Tools::displayError(sprintf('You have exceeded the limit of login attempts, please try after %d minutes.', MaintenanceAccess::LOGIN_ATTEMPTS_WINDOW - $minutes)); + } + } } if (!count($this->errors)) { @@ -837,6 +845,7 @@ class FrontControllerCore extends Controller $this->context->employee = new Employee(); $is_employee_loaded = $this->context->employee->getByEmail($email, $passwd); $employee_associated_shop = $this->context->employee->getAssociatedShops(); + if (!$is_employee_loaded) { $this->errors[] = Tools::displayError('The Employee does not exist, or the password provided is incorrect.'); $this->context->employee->logout(); @@ -847,21 +856,40 @@ class FrontControllerCore extends Controller // Update cookie $cookie = Context::getContext()->cookie; $cookie->enable_maintenance_view = $this->context->employee->id; - $cookie->remote_addr = ip2long(Tools::getRemoteAddr()); + $cookie->remote_addr = ip2long($remoteIpAddress); $cookie->write(); + + // Reset attempts count on successful login + $objMaintenanceAccess->removeFailedAttempts($email, $remoteIpAddress); } + if (count($this->errors)) { - $this->context->smarty->assign('errors', $this->errors); + $objMaintenanceAccess = new MaintenanceAccess(); $objMaintenanceAccess->email = $email; - $objMaintenanceAccess->ip_address = Tools::getRemoteAddr(); + $objMaintenanceAccess->ip_address = $remoteIpAddress; $objMaintenanceAccess->save(); + + if ($attemptsCount < $maxAttempts) { + $attemptsLeft = $maxAttempts - $attemptsCount - 1; + + if ($attemptsLeft > 0) { + if ($attemptsLeft > 1) { + $this->errors[] = Tools::displayError(sprintf('%d attempts left.', ($maxAttempts - $attemptsCount - 1))); + } else { + $this->errors[] = Tools::displayError(sprintf('%d attempt left.', ($maxAttempts - $attemptsCount - 1))); + } + } else { + $this->errors[] = Tools::displayError(sprintf('You have reached the limit of login attempts, please try after %d minutes.', MaintenanceAccess::LOGIN_ATTEMPTS_WINDOW)); + } + } + + $this->context->smarty->assign('errors', $this->errors); } else { Tools::redirect('index.php'); } } else { $this->context->smarty->assign('errors', $this->errors); } - return $result; } /** diff --git a/controllers/admin/AdminMaintenanceController.php b/controllers/admin/AdminMaintenanceController.php index d6911d4da..b8aa7c845 100644 --- a/controllers/admin/AdminMaintenanceController.php +++ b/controllers/admin/AdminMaintenanceController.php @@ -54,7 +54,16 @@ class AdminMaintenanceControllerCore extends AdminController 'validation' => 'isBool', 'cast' => 'intval', 'type' => 'bool', - 'class' => "hello" + 'form_group_class' => (Tools::getValue('PS_SHOP_ENABLE', Configuration::get('PS_SHOP_ENABLE'))) ? 'collapse' : '', + ), + 'PS_ALLOW_EMP_MAX_ATTEMPTS' => array( + 'title' => $this->l('Maximum Login Attempts'), + 'validation' => 'isUnsignedInt', + 'cast' => 'intval', + 'type' => 'text', + 'class' => 'fixed-width-xl', + 'desc' => $this->l('Set the number of maximum login attempts allowed in 30 minutes.'), + 'form_group_class' => ((Tools::getValue('PS_SHOP_ENABLE', Configuration::get('PS_SHOP_ENABLE'))) || !(Tools::getValue('PS_ALLOW_EMP', Configuration::get('PS_ALLOW_EMP')))) ? ' collapse' : '', ), 'PS_MAINTENANCE_IP' => array( 'title' => $this->l('Maintenance IP'), @@ -69,6 +78,41 @@ class AdminMaintenanceControllerCore extends AdminController ); } + public function postProcess() + { + if (Tools::isSubmit('submitOptionsconfiguration')) { + $shopEnable = Tools::getValue('PS_SHOP_ENABLE'); + $allowEmp = Tools::getValue('PS_ALLOW_EMP'); + $allowEmpMaxTries = trim(Tools::getValue('PS_ALLOW_EMP_MAX_ATTEMPTS')); + $maintenanceIp = trim(Tools::getValue('PS_MAINTENANCE_IP')); + + // validations + if (!$shopEnable && $allowEmp) { + if (!$allowEmpMaxTries) { + $this->errors[] = $this->l('Maximum Tries is a required field.'); + } elseif (!Validate::isUnsignedInt($allowEmpMaxTries)) { + $this->errors[] = $this->l('Maximum Tries is invalid.'); + } + } + + // update values + if (!count($this->errors)) { + Configuration::updateValue('PS_SHOP_ENABLE', $shopEnable); + Configuration::updateValue('PS_MAINTENANCE_IP', $maintenanceIp); + + if (!$shopEnable) { + Configuration::updateValue('PS_ALLOW_EMP', $allowEmp); + + if ($allowEmp) { + Configuration::updateValue('PS_ALLOW_EMP_MAX_ATTEMPTS', $allowEmpMaxTries); + } + } + + Tools::redirectAdmin(self::$currentIndex.'&token='.$this->token.'&conf=6'); + } + } + } + public function setMedia() { parent::setMedia(); diff --git a/install/data/xml/configuration.xml b/install/data/xml/configuration.xml index 557f554f6..be03b27c3 100644 --- a/install/data/xml/configuration.xml +++ b/install/data/xml/configuration.xml @@ -92,6 +92,12 @@ 1 + + 0 + + + 3 + 20 diff --git a/js/maintenance.js b/js/maintenance.js index a2685698b..43611d9d3 100644 --- a/js/maintenance.js +++ b/js/maintenance.js @@ -1,22 +1,52 @@ -$(document).ready(function(){ +/** +* Since 2010 Webkul. +* +* NOTICE OF LICENSE +* +* All right is reserved, +* Please go through this link for complete license : https://store.webkul.com/license.html +* +* DISCLAIMER +* +* Do not edit or add to this file if you wish to upgrade this module to newer +* versions in the future. If you wish to customize this module for your +* needs please refer to https://store.webkul.com/customisation-guidelines/ for more information. +* +* @author Webkul IN +* @copyright Since 2010 Webkul IN +* @license https://store.webkul.com/license.html +*/ - if ($("#PS_SHOP_ENABLE_on").is(':checked')) { - $('#conf_id_PS_ALLOW_EMP').hide(); - } - if ($("#PS_SHOP_ENABLE_off").is(':checked')) { - $("#conf_id_PS_ALLOW_EMP").show(); +function manageFieldMaximumAttempts(state) { + if (state) { + $('[name="PS_ALLOW_EMP_MAX_ATTEMPTS"]').closest('.form-group').show(200); + } else { + $('[name="PS_ALLOW_EMP_MAX_ATTEMPTS"]').closest('.form-group').hide(200); } +} - $(".clicker.blue").on('click', function(){ - $('.hiddendiv').show('slow'); +$(document).ready(function() { + // manage fields visibility + $(document).on('change', '[name="PS_SHOP_ENABLE"]', function () { + if (parseInt($('[name="PS_SHOP_ENABLE"]:checked').val())) { + $('[name="PS_ALLOW_EMP"]').closest('.form-group').hide(200); + manageFieldMaximumAttempts(false); + } else { + $('[name="PS_ALLOW_EMP"]').closest('.form-group').show(200); + manageFieldMaximumAttempts(parseInt($('[name="PS_ALLOW_EMP"]:checked').val())); + } }); - $("#cancelLogin").on('click', function(){ - $('.hiddendiv').hide('slow'); + $(document).on('change', '[name="PS_ALLOW_EMP"]', function () { + manageFieldMaximumAttempts(parseInt($('[name="PS_ALLOW_EMP"]:checked').val())); }); - $("input[type='radio'][name='PS_SHOP_ENABLE']").change(function() { - $("#conf_id_PS_ALLOW_EMP").toggle('slow'); + $('.clicker.blue').on('click', function() { + $('.login-form-wrap').toggle(200); + }); + + $('#cancelLogin').on('click', function() { + $('.login-form-wrap').hide(200); }); // manage dropdowns diff --git a/themes/hotel-reservation-theme/css/maintenance.css b/themes/hotel-reservation-theme/css/maintenance.css index da78fb1d8..5bcf093c9 100644 --- a/themes/hotel-reservation-theme/css/maintenance.css +++ b/themes/hotel-reservation-theme/css/maintenance.css @@ -39,8 +39,7 @@ p { cursor: pointer; } -.hiddendiv { - display: none; +.login-form-wrap { width: 300px; } diff --git a/themes/hotel-reservation-theme/maintenance.tpl b/themes/hotel-reservation-theme/maintenance.tpl index a347a4079..3fd9d2a5f 100644 --- a/themes/hotel-reservation-theme/maintenance.tpl +++ b/themes/hotel-reservation-theme/maintenance.tpl @@ -98,37 +98,33 @@

{l s='Thanks for your patience!'}

{if isset($allowEmployee) && $allowEmployee}
-

{l s='Are you member?'}

-
-
-
-
-
- -
- -
-
- -
- -
- - +

{l s='Are you a member?'}

+ +
{/if}