Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add User lockout and user unlock options #2897

Merged
merged 16 commits into from Jan 17, 2017
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions include/utils.php
Expand Up @@ -389,6 +389,10 @@ function get_sugar_config_defaults()
'max_cron_runtime' => 30, // max runtime for cron jobs
'min_cron_interval' => 30, // minimal interval between cron jobs
),
'userlockout' => array(
'maxfailedlogins' => '0',
'automaticunlocktime' => '0',
),
);

if (!is_object($locale)) {
Expand Down
77 changes: 71 additions & 6 deletions modules/Administration/PasswordManager.tpl
Expand Up @@ -3,8 +3,8 @@
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.

* SuiteCRM is an extension to SugarCRM Community Edition developed by Salesagility Ltd.
* Copyright (C) 2011 - 2014 Salesagility Ltd.
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2017 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
Expand Down Expand Up @@ -250,8 +250,41 @@
{/if}
</table>



<table id="userLockId" name="userLockName" width="100%" border="0" cellspacing="1" cellpadding="0" class="edit view">
<tr>
<th align="left" scope="row" colspan="2"><h4>{$MOD.LBL_USER_LOCKOUT}</h4></th>
</tr>
<tr>
<td width="25%" scope="row">{$MOD.LBL_ENABLE_MAX_FAILED_LOGINS}:</td>
<td scope="row" width="25%" >
<input type='checkbox' id="maxFailedLogin" {if $config.userlockout.maxfailedlogins}checked="checked"{/if}>&nbsp;
</td>
<td></td>
<td></td>
</tr>
<tr id="maxFailedLoginsRow">
<td width="25%" scope="row">{$MOD.LBL_MAX_FAILED_LOGINS}:&nbsp{sugar_help text=$MOD.LBL_MAX_FAILED_LOGINS_HELP WIDTH=400}</td>
<td scope="row" width="25%">
<input type='text' maxlength="3" style="width:2em" id='userlockout_maxfailedlogins' name='userlockout_maxfailedlogins' value='{$config.userlockout.maxfailedlogins}'>
</td>
<td></td>
<td></td>
</tr>
<tr>
<td width="25%" scope="row">{$MOD.LBL_AUTO_UNLOCK}:&nbsp{sugar_help text=$MOD.LBL_AUTO_UNLOCK_HELP WIDTH=400}</td>
<td scope="row" width="25%" >
<input type='checkbox' id="autoUnlock" {if $config.userlockout.automaticunlocktime}checked="checked"{/if}>&nbsp;
</td>
<td>&nbsp;</td><td>&nbsp;</td>
</tr>
<tr id="autoUnlockRow">
<td width="25%" scope="row">{$MOD.LBL_AUTO_UNLOCK_TIME}:&nbsp{sugar_help text=$MOD.LBL_AUTO_UNLOCK_TIME_HELP WIDTH=400}</td>
<td scope="row" width="25%" >
<input type='text' maxlength="10" style="width:5em" id='userlockout_automaticunlocktime' name='userlockout_automaticunlocktime' value='{$config.userlockout.automaticunlocktime}'>&nbsp;<span>{$MOD.LBL_AUTO_UNLOCK_TIME_UNITS}</span>
</td>
<td>&nbsp;</td><td>&nbsp;</td>
</tr>
</table>

<table id="emailTemplatesId" name="emailTemplatesName" width="100%" border="0" cellspacing="1" cellpadding="0" class="edit view">
<tr>
Expand Down Expand Up @@ -306,7 +339,7 @@
<tr>
<td width="25%" scope="row" valign='middle'>
{$MOD.LBL_LDAP_ENABLE}{sugar_help text=$MOD.LBL_LDAP_HELP_TXT}
</td><td valign='middle'><input name="system_ldap_enabled" id="system_ldap_enabled" class="checkbox" type="checkbox" {$system_ldap_enabled_checked} onclick='toggleDisplay("ldap_display");enableDisablePasswordTable("system_ldap_enabled");'></td><td>&nbsp;</td><td>&nbsp;</td></tr>
</td><td valign='middle'><input name="system_ldap_enabled" id="system_ldap_enabled" class="checkbox" type="checkbox" {$system_ldap_enabled_checked} onclick='toggleDisplay("ldap_display");filterUserLock();enableDisablePasswordTable("system_ldap_enabled");'></td><td>&nbsp;</td><td>&nbsp;</td></tr>
<tr>
<td colspan='4'>
<table cellspacing='0' cellpadding='1' id='ldap_display' style='display:{$ldap_display}' width='100%'>
Expand Down Expand Up @@ -455,7 +488,7 @@
<input name="authenticationClass" id="system_saml_enabled" class="checkbox"
value="SAMLAuthenticate" type="checkbox"
{if $saml_enabled_checked}checked="1"{/if}
onclick='toggleDisplay("saml_display");enableDisablePasswordTable("system_saml_enabled");'>
onclick='toggleDisplay("saml_display");filterUserLock();enableDisablePasswordTable("system_saml_enabled");'>
</td><td>&nbsp;</td><td>&nbsp;</td></tr>
<tr>
<td colspan='4'>
Expand Down Expand Up @@ -508,6 +541,36 @@ document.getElementById('forgotpassword_checkbox').checked=true;

{literal}
<script>
$(document).ready(function(){
$('#maxFailedLogin').change(function() {
if($('#maxFailedLogin').is(':checked')){
$('#maxFailedLoginsRow').show();
}else{
$('#maxFailedLoginsRow').hide();
$('#userlockout_maxfailedlogins').val(0);
}
});
$('#maxFailedLogin').change();
$('#autoUnlock').change(function() {
if($('#autoUnlock').is(':checked')){
$('#autoUnlockRow').show();
}else{
$('#autoUnlockRow').hide();
$('#userlockout_automaticunlocktime').val(0);
}
});
$('#autoUnlock').change();
filterUserLock();
});

function filterUserLock(){
if($('#system_saml_enabled').is(':checked') || $('#system_ldap_enabled').is(':checked')){
$('#userLockId').hide();
}else{
$('#userLockId').show();
}
}

function addcheck(form){{/literal}
addForm('ConfigurePasswordSettings');

Expand All @@ -520,6 +583,8 @@ function addcheck(form){{/literal}
addToValidate('ConfigurePasswordSettings', 'passwordsetting_systexpirationtime', 'int', form.required_sys_pwd_exp_time.checked,"{$MOD.ERR_PASSWORD_EXPIRE_TIME}" );
addToValidate('ConfigurePasswordSettings', 'passwordsetting_systexpirationlogin', 'int', form.required_sys_pwd_exp_login.checked,"{$MOD.ERR_PASSWORD_EXPIRE_LOGIN}" );
{literal}}{/literal}
addToValidate('ConfigurePasswordSettings', 'userlockout_maxfailedlogins', 'int', false,"{$MOD.ERR_MAX_FAILED_LOGINS} ");
addToValidate('ConfigurePasswordSettings', 'userlockout_automaticunlocktime', 'int', false,"{$MOD.ERR_AUTOMATIC_UNLOCK_TIME} ");


{literal} }
Expand Down
12 changes: 12 additions & 0 deletions modules/Administration/language/en_us.lang.php
Expand Up @@ -1262,5 +1262,17 @@
'LBL_SECURITYGROUPS_SUGAROUTFITTERS_TITLE' => "SugarOutfitters",
'LBL_SECURITYGROUPS_SUGAROUTFITTERS' => "Grab the latest version of SecuritySuite and find other SuiteCRM modules, themes, and integrations along with reviews, docs, support, and community verified versions.",

'LBL_USER_LOCKOUT' => 'User Lockout',
'LBL_MAX_FAILED_LOGINS' => 'Max failed logins',
'LBL_MAX_FAILED_LOGINS_HELP' => 'The number of failed logins before a user will have their account locked.',
'LBL_AUTO_UNLOCK_TIME' => 'Automatic user unlock time',
'LBL_AUTO_UNLOCK_TIME_HELP' => 'The number of minutes it takes for a user account to be automatically unlocked.',
'LBL_AUTO_UNLOCK_TIME_UNITS' => 'minutes',
'ERR_MAX_FAILED_LOGINS' => 'Please specify a valid value for the number of max failed logins',
'ERR_AUTOMATIC_UNLOCK_TIME' => 'Please specify a valid value for the automatic unlock time',
'LBL_ENABLE_MAX_FAILED_LOGINS' => 'Lock users after a number of failed logins',
'LBL_ENABLE_MAX_FAILED_LOGINS_HELP' => '',
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Empty language string!

'LBL_AUTO_UNLOCK' => 'Automatically unlock locked users',
'LBL_AUTO_UNLOCK_HELP' => 'Unlock locked users after a configurable amount of time',
);

@@ -1,11 +1,10 @@
<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
/*********************************************************************************
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.

* SuiteCRM is an extension to SugarCRM Community Edition developed by Salesagility Ltd.
* Copyright (C) 2011 - 2014 Salesagility Ltd.
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2017 SalesAgility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
Expand Down Expand Up @@ -47,6 +46,10 @@
* based on the users validation
*
*/
if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}

class SugarAuthenticate{
var $userAuthenticateClass = 'SugarAuthenticateUser';
var $authenticationDir = 'SugarAuthenticate';
Expand Down Expand Up @@ -83,6 +86,30 @@ public function SugarAuthenticate(){
self::__construct();
}

/**
* Given a user returns true if this user is currently locked out based on the `user_locked_out` and
* on whether the unlock time has passed, if set.
*
* @see SugarAuthenticate::loginAuthenticate()
* @param User $user
*
* @return bool
*/
private function isUserLockedOut(User $user){
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docblocks Comment missing

global $sugar_config;
if(!$user->getPreference('user_locked_out')){
return false;
}
if(empty($sugar_config['userlockout']['automaticunlocktime'])){
return true;
}
$unlockCutoff = time() - ($sugar_config['userlockout']['automaticunlocktime'] * 60);
if($user->getPreference('user_locked_out_time') < $unlockCutoff){
return false;
}
return true;
}

/**
* Authenticates a user based on the username and password
* returns true if the user was authenticated false otherwise
Expand All @@ -92,15 +119,19 @@ public function SugarAuthenticate(){
* @param string $password
* @return boolean
*/
function loginAuthenticate($username, $password, $fallback=false, $PARAMS = array ()){
global $mod_strings;
public function loginAuthenticate($username, $password, $fallback=false, $PARAMS = array ()){
global $mod_strings, $sugar_config;
unset($_SESSION['login_error']);
$usr= new user();
$usr_id=$usr->retrieve_user_id($username);
$usr->retrieve($usr_id);
$_SESSION['login_error']='';
$_SESSION['waiting_error']='';
$_SESSION['hasExpiredPassword']='0';
if ($this->isUserLockedOut($usr)) {
$_SESSION['login_error'] = translate('ERR_USER_IS_LOCKED_OUT', 'Users');
return false;
}
if ($this->userAuthenticate->loadUserOnLogin($username, $password, $fallback, $PARAMS)) {
require_once('modules/Users/password_utils.php');
if(hasPasswordExpired($username)) {
Expand All @@ -109,19 +140,27 @@ function loginAuthenticate($username, $password, $fallback=false, $PARAMS = arra
// now that user is authenticated, reset loginfailed
if ($usr->getPreference('loginfailed') != '' && $usr->getPreference('loginfailed') != 0) {
$usr->setPreference('loginfailed','0');
$usr->setPreference('user_locked_out', false);
$usr->setPreference('user_locked_out_time', '');
$usr->savePreferencesToDB();
}
return $this->postLoginAuthenticate();

}
else
{
//if(!empty($usr_id) && $res['lockoutexpiration'] > 0){
if(!empty($usr_id)){
if (($logout=$usr->getPreference('loginfailed'))=='')
$usr->setPreference('loginfailed','1');
else
if (($logout=$usr->getPreference('loginfailed'))=='') {
$usr->setPreference('loginfailed', '1');
}else{
$usr->setPreference('loginfailed',$logout+1);
}
if (!empty($sugar_config['userlockout']['maxfailedlogins']) &&
($logout + 1) >= $sugar_config['userlockout']['maxfailedlogins']
) {
$usr->setPreference('user_locked_out', true);
$usr->setPreference('user_locked_out_time', time());
}
$usr->savePreferencesToDB();
}
}
Expand Down Expand Up @@ -149,7 +188,7 @@ function loginAuthenticate($username, $password, $fallback=false, $PARAMS = arra
* Once a user is authenticated on login this function will be called. Populate the session with what is needed and log anything that needs to be logged
*
*/
function postLoginAuthenticate(){
public function postLoginAuthenticate(){

global $reset_language_on_default_user, $sugar_config;

Expand Down Expand Up @@ -188,7 +227,7 @@ function postLoginAuthenticate(){
* On every page hit this will be called to ensure a user is authenticated
* @return boolean
*/
function sessionAuthenticate(){
public function sessionAuthenticate(){

global $module, $action, $allowed_actions;
$authenticated = false;
Expand Down Expand Up @@ -234,7 +273,7 @@ function sessionAuthenticate(){
* @return boolean
*/

function postSessionAuthenticate(){
public function postSessionAuthenticate(){

global $action, $allowed_actions, $sugar_config;
$_SESSION['userTime']['last'] = time();
Expand Down Expand Up @@ -263,7 +302,7 @@ function postSessionAuthenticate(){
* Make sure a user isn't stealing sessions so check the ip to ensure that the ip address hasn't dramatically changed
*
*/
function validateIP() {
public function validateIP() {
global $sugar_config;
// grab client ip address
$clientIP = query_client_ip();
Expand Down Expand Up @@ -310,7 +349,7 @@ function validateIP() {
* Called when a user requests to logout
*
*/
function logout(){
public function logout(){
session_start();
session_destroy();
ob_clean();
Expand All @@ -325,22 +364,22 @@ function logout(){
* @param STRING $password
* @return STRING $encoded_password
*/
static function encodePassword($password){
public static function encodePassword($password){
return strtolower(md5($password));
}

/**
* If a user may change there password through the Sugar UI
*
*/
function canChangePassword(){
public function canChangePassword(){
return true;
}
/**
* If a user may change there user name through the Sugar UI
*
*/
function canChangeUserName(){
public function canChangeUserName(){
return true;
}

Expand All @@ -350,7 +389,7 @@ function canChangeUserName(){
*
* This function allows the SugarAuthenticate subclasses to perform some pre login initialization as needed
*/
function pre_login()
public function pre_login()
{
if (isset($_SESSION['authenticated_user_id']))
{
Expand Down
24 changes: 21 additions & 3 deletions modules/Users/controller.php
@@ -1,11 +1,10 @@
<?php
if(!defined('sugarEntry') || !sugarEntry) die('Not A Valid Entry Point');
/*********************************************************************************
* SugarCRM Community Edition is a customer relationship management program developed by
* SugarCRM, Inc. Copyright (C) 2004-2013 SugarCRM Inc.

* SuiteCRM is an extension to SugarCRM Community Edition developed by Salesagility Ltd.
* Copyright (C) 2011 - 2014 Salesagility Ltd.
* SuiteCRM is an extension to SugarCRM Community Edition developed by SalesAgility Ltd.
* Copyright (C) 2011 - 2017 Salesagility Ltd.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU Affero General Public License version 3 as published by the
Expand Down Expand Up @@ -44,6 +43,11 @@
* All Rights Reserved.
* Contributor(s): ______________________________________..
********************************************************************************/

if (!defined('sugarEntry') || !sugarEntry) {
die('Not A Valid Entry Point');
}

require_once("include/OutboundEmail/OutboundEmail.php");

class UsersController extends SugarController
Expand Down Expand Up @@ -130,5 +134,19 @@ public function action_save()
{
require 'modules/Users/Save.php';
}

public function action_unlockuser(){
global $current_user;
if (!is_admin($current_user)) {
SugarApplication::redirect("index.php?module=Users&record=" . $_REQUEST['record'] . "&action=DetailView");
return;
}
$this->bean->setPreference('user_locked_out', false);
$this->bean->setPreference('user_locked_out_time', '');
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

needs to also reset the loginfailed preference

$this->bean->setPreference('loginfailed', '0');
$this->bean->savePreferencesToDB();
SugarApplication::appendErrorMessage(translate('LBL_USER_UNLOCKED_MSG', 'Users'));
SugarApplication::redirect("index.php?module=Users&record=" . $_REQUEST['record'] . "&action=DetailView");
}
}

2 changes: 2 additions & 0 deletions modules/Users/language/en_us.lang.php
Expand Up @@ -599,6 +599,8 @@
'LBL_LIST_NONINHERITABLE' => "Not Inheritable",
'LBL_PRIMARY_GROUP' => "Primary Group",
'LBL_PASSWORD_MIS_MATCH' => 'mis-match',
'LBL_UNLOCK_USER' => 'Unlock user',
'LBL_USER_UNLOCKED_MSG' => 'User unlocked',
); // END STRINGS DEFS

?>