forked from extern/egroupware
Feature: to allow admins a) to set an allowed password age, to require all users to change their password regularily; b) force password change for a given user on the users next login; c) better control about the password strength required; Funded by Cricket
This commit is contained in:
parent
1f8e2e93df
commit
3843c0b59b
@ -260,7 +260,7 @@
|
||||
{
|
||||
return False;
|
||||
}
|
||||
|
||||
//error_log(array2string($userData));
|
||||
$accountPrefix = '';
|
||||
if(isset($GLOBALS['egw_info']['server']['account_prefix']))
|
||||
{
|
||||
@ -441,6 +441,7 @@
|
||||
/* stores the userdata */
|
||||
function save_user($_userData)
|
||||
{
|
||||
//error_log(__METHOD__.array2string($_userData));
|
||||
$account =& CreateObject('phpgwapi.accounts',$_userData['account_id'],'u');
|
||||
$account->update_data($_userData);
|
||||
$account->save_repository();
|
||||
@ -458,6 +459,13 @@
|
||||
$GLOBALS['egw']->hooks->process($GLOBALS['hook_values']+array(
|
||||
'location' => 'changepassword'
|
||||
),False,True); // called for every app now, not only enabled ones)
|
||||
if ($_userData['account_lastpwd_change']==0)
|
||||
{
|
||||
// change password sets the shadow_timestamp/account_lastpwd_change timestamp
|
||||
// so we need to reset that to 0 as Admin required the change of password upon next login
|
||||
unset($_userData['account_passwd']);
|
||||
$this->save_user($_userData);
|
||||
}
|
||||
}
|
||||
|
||||
$apps =& CreateObject('phpgwapi.applications',(int)$_userData['account_id']);
|
||||
|
@ -573,6 +573,7 @@
|
||||
'account_groups' => $_POST['account_groups'],
|
||||
'anonymous' => $_POST['anonymous'],
|
||||
'changepassword' => $_POST['changepassword'],
|
||||
'mustchangepassword' => $_POST['mustchangepassword'],
|
||||
'account_permissions' => $_POST['account_permissions'],
|
||||
'homedirectory' => $_POST['homedirectory'],
|
||||
'loginshell' => $_POST['loginshell'],
|
||||
@ -580,7 +581,7 @@
|
||||
'account_email' => $email
|
||||
/* 'file_space' => $_POST['account_file_space_number'] . "-" . $_POST['account_file_space_type'] */
|
||||
);
|
||||
|
||||
if ($userData['mustchangpassword']) $userData['account_lastpwd_change']=0;
|
||||
/* when does the account expire */
|
||||
if ($_POST['expires'] !== '' && !$_POST['never_expires'])
|
||||
{
|
||||
@ -863,6 +864,7 @@
|
||||
'account_primary_group' => $_POST['account_primary_group'],
|
||||
'anonymous' => $_POST['anonymous'],
|
||||
'changepassword' => $_POST['changepassword'],
|
||||
'mustchangepassword' => $_POST['mustchangepassword'],
|
||||
'account_permissions' => $_POST['account_permissions'],
|
||||
'homedirectory' => $_POST['homedirectory'],
|
||||
'loginshell' => $_POST['loginshell'],
|
||||
@ -870,6 +872,21 @@
|
||||
'account_email' => $email,
|
||||
/* 'file_space' => $_POST['account_file_space_number'] . "-" . $_POST['account_file_space_type'] */
|
||||
);
|
||||
if ($userData['mustchangepassword'])
|
||||
{
|
||||
$userData['account_lastpwd_change']=0;
|
||||
}
|
||||
else
|
||||
{
|
||||
$accountid = $account_id;
|
||||
settype($account_id,'integer');
|
||||
$account_id = (int)($_GET['account_id'] ? $_GET['account_id'] : $accountid);
|
||||
|
||||
//echo $account_id.'#<br>';
|
||||
$prevVal = $GLOBALS['egw']->accounts->id2name($account_id,'account_lastpwd_change').'#<br>';
|
||||
//echo $prevVal.'#<br>'; // previous Value was force password change by admin
|
||||
if ($prevVal==0) $userData['account_lastpwd_change']=egw_time::to('now','ts');
|
||||
}
|
||||
if($userData['account_primary_group'] && (!isset($userData['account_groups']) || !in_array($userData['account_primary_group'],$userData['account_groups'])))
|
||||
{
|
||||
$userData['account_groups'][] = (int)$userData['account_primary_group'];
|
||||
@ -965,6 +982,7 @@
|
||||
'lang_groups' => lang('Groups'),
|
||||
'lang_anonymous' => lang('Anonymous user (not shown in list sessions)'),
|
||||
'lang_changepassword'=> lang('Can change password'),
|
||||
'lang_mustchangepassword'=> lang('Must change password upon next login'),
|
||||
'lang_firstname' => lang('First Name'),
|
||||
'lang_lastlogin' => lang('Last login'),
|
||||
'lang_lastloginfrom' => lang('Last login from'),
|
||||
@ -985,6 +1003,7 @@
|
||||
$acl =& CreateObject('phpgwapi.acl',(int)$_GET['account_id']);
|
||||
$var['anonymous'] = $acl->check('anonymous',1,'phpgwapi') ? ' X' : ' ';
|
||||
$var['changepassword'] = !$acl->check('nopasswordchange',1,'preferences') ? ' X' : ' ';
|
||||
$var['mustchangepassword']= $userData['account_lastpwd_change']==0 ? ' X' : ' ';
|
||||
unset($acl);
|
||||
|
||||
if ($userData['status'])
|
||||
@ -1286,6 +1305,7 @@
|
||||
|
||||
function create_edit_user($_account_id,$_userData='',$_errors='')
|
||||
{
|
||||
//_debug_array($_userData);
|
||||
$GLOBALS['egw_info']['flags']['include_xajax'] = true;
|
||||
|
||||
$jscal =& CreateObject('phpgwapi.jscalendar');
|
||||
@ -1341,6 +1361,7 @@
|
||||
$acl->read_repository();
|
||||
$userData['anonymous'] = $acl->check('anonymous',1,'phpgwapi');
|
||||
$userData['changepassword'] = !$acl->check('nopasswordchange',1,'preferences');
|
||||
$userData['mustchangepassword'] = ($userData['account_lastpwd_change']==0?true:false);
|
||||
unset($acl);
|
||||
}
|
||||
else
|
||||
@ -1351,6 +1372,7 @@
|
||||
$userGroups = Array();
|
||||
$userData['anonymous'] = False;
|
||||
$userData['changepassword'] = True;
|
||||
$userData['mustchangepassword'] = false;
|
||||
}
|
||||
$allGroups = $account->get_list('groups');
|
||||
}
|
||||
@ -1380,6 +1402,7 @@
|
||||
'lang_firstname' => lang('First Name'),
|
||||
'lang_anonymous' => lang('Anonymous User (not shown in list sessions)'),
|
||||
'lang_changepassword' => lang('Can change password'),
|
||||
'lang_mustchangepassword'=> lang('Must change password upon next login'),
|
||||
'lang_button' => ($_account_id?lang('Save'):lang('Add')),
|
||||
'lang_passwds_unequal' => lang('The two passwords are not the same'),
|
||||
/* 'lang_file_space' => lang('File Space') */
|
||||
@ -1449,6 +1472,7 @@
|
||||
'loginshell' => $loginshell,
|
||||
'anonymous' => '<input type="checkbox" name="anonymous" value="1"'.($userData['anonymous'] ? ' checked' : '').'>',
|
||||
'changepassword' => '<input type="checkbox" name="changepassword" value="1"'.($userData['changepassword'] ? ' checked' : '').'>',
|
||||
'mustchangepassword' => '<input type="checkbox" name="mustchangepassword" value="1"'.($userData['mustchangepassword'] ? ' checked' : '').'>',
|
||||
'account_status' => '<input type="checkbox" name="account_status" value="A"'.($userData['status']?' checked':'').'>',
|
||||
'account_firstname' => '<input id="firstname" onchange="check_account_email(this.id);" name="account_firstname" maxlength="50" value="' . $userData['firstname'] . '">',
|
||||
'account_lastname' => '<input id="lastname" onchange="check_account_email(this.id);" name="account_lastname" maxlength="50" value="' . $userData['lastname'] . '">',
|
||||
|
@ -61,6 +61,13 @@ function check_password(id)
|
||||
</tr>
|
||||
|
||||
{password_fields}
|
||||
|
||||
<tr class="row_on">
|
||||
<td>{lang_mustchangepassword}</td>
|
||||
<td>{mustchangepassword}</td>
|
||||
<td></td>
|
||||
<td></td>
|
||||
</tr>
|
||||
|
||||
<tr class="row_off">
|
||||
<td>{lang_changepassword}</td>
|
||||
|
@ -231,6 +231,20 @@
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="row_off">
|
||||
<td>{lang_Force_users_to_change_their_password_regularily?(empty_for_no,number_for_after_that_number_of_days}:</td>
|
||||
<td>
|
||||
<input name="newsettings[change_pwd_every_x_days]" value="{value_change_pwd_every_x_days}" size="5">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="row_on">
|
||||
<td>{lang_Force_password_strength_(1-5,_default_empty: no check against rules for a strong password)?}:</td>
|
||||
<td>
|
||||
<input name="newsettings[force_pwd_strength]" value="{value_force_pwd_strength}" size="5">
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="row_off">
|
||||
<td>{lang_Admin_email_addresses_(comma-separated)_to_be_notified_about_the_blocking_(empty_for_no_notify)}:</td>
|
||||
<td>
|
||||
|
@ -19,8 +19,8 @@
|
||||
*/
|
||||
$GLOBALS['egw_info'] = array(
|
||||
'flags' => array(
|
||||
'noheader' => False,
|
||||
'nonavbar' => False,
|
||||
'noheader' => true,//False,
|
||||
'nonavbar' => true,//False,
|
||||
'currentapp' => 'home',
|
||||
'enable_network_class' => False,
|
||||
'enable_contacts_class' => False,
|
||||
@ -30,7 +30,9 @@
|
||||
);
|
||||
|
||||
include('../header.inc.php');
|
||||
|
||||
auth::check_password_age('home','index');
|
||||
$GLOBALS['egw_info']['flags']['nonavbar']=false;
|
||||
common::egw_header();
|
||||
/*
|
||||
** Initializing the template
|
||||
*/
|
||||
@ -288,4 +290,3 @@
|
||||
//$GLOBALS['egw']->common->debug_phpgw_info();
|
||||
//$GLOBALS['egw']->common->debug_list_core_functions();
|
||||
$GLOBALS['egw']->common->egw_footer();
|
||||
?>
|
||||
|
@ -18,7 +18,7 @@ $GLOBALS['egw_info'] = array(
|
||||
)
|
||||
);
|
||||
include('../header.inc.php');
|
||||
|
||||
auth::check_password_age('infolog','index');
|
||||
include_once(EGW_INCLUDE_ROOT.'/infolog/setup/setup.inc.php');
|
||||
if ($setup_info['infolog']['version'] != $GLOBALS['egw_info']['apps']['infolog']['version'])
|
||||
{
|
||||
|
@ -504,7 +504,7 @@ class accounts_ldap
|
||||
// shadowexpire is in days since 1970/01/01 (equivalent to a timestamp (int UTC!) / (24*60*60)
|
||||
'account_status' => isset($data['shadowexpire']) && $data['shadowexpire'][0]*24*3600+$utc_diff < time() ? false : 'A',
|
||||
'account_expires' => isset($data['shadowexpire']) && $data['shadowexpire'][0] ? $data['shadowexpire'][0]*24*3600+$utc_diff : -1, // LDAP date is in UTC
|
||||
'account_lastpasswd_change' => isset($data['shadowlastchange']) ? $data['shadowlastchange'][0]*24*3600+$utc_diff : null,
|
||||
'account_lastpwd_change' => isset($data['shadowlastchange']) ? $data['shadowlastchange'][0]*24*3600+$utc_diff : null,
|
||||
// lastlogin and lastlogin from are not availible via the shadowAccount object class
|
||||
// 'account_lastlogin' => $data['phpgwaccountlastlogin'][0],
|
||||
// 'account_lastloginfrom' => $data['phpgwaccountlastloginfrom'][0],
|
||||
@ -591,7 +591,7 @@ class accounts_ldap
|
||||
unset($to_write['shadowexpire']); // gives protocoll error otherwise
|
||||
}
|
||||
|
||||
if ($data['account_lastpasswd_change']) $to_write['shadowlastchange'] = $data['lastpasswd_change']/(24*3600);
|
||||
if ($data['account_lastpwd_change']) $to_write['shadowlastchange'] = $data['lastpwd_change']/(24*3600);
|
||||
|
||||
// lastlogin and lastlogin from are not availible via the shadowAccount object class
|
||||
// $to_write['phpgwaccountlastlogin'] = $data['lastlogin'];
|
||||
|
@ -55,6 +55,46 @@ class auth
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* check_password_age
|
||||
* check if users are supposed to change their password every x sdays, then check if password is of old age
|
||||
* or the devil-admin reset the users password and forced the user to change his password on next login.
|
||||
*
|
||||
* @param string $app to know where you are/ or where you want to go
|
||||
* @param string $class to know where you are/ or where you want to go
|
||||
* @param string $method to know where you are/ or where you want to go
|
||||
* @return boolean true if check determined, that you passed the test, otherwise void, as we get redirected
|
||||
*/
|
||||
static function check_password_age($app='', $class='', $method='')
|
||||
{
|
||||
//echo egw_time::to('now','ts').'<br>';
|
||||
//echo $GLOBALS['egw_info']['user']['account_lastpwd_change'].'<br>';
|
||||
//echo ($GLOBALS['egw_info']['server']['change_pwd_every_x_days']*86400).'<br>';
|
||||
//echo egw_time::to('now','ts')-($GLOBALS['egw_info']['server']['change_pwd_every_x_days']*86400).'<br>';
|
||||
if (!($app == 'preferences' && $class == 'uipassword' && $method=='change') &&
|
||||
(($GLOBALS['egw_info']['server']['change_pwd_every_x_days'] &&
|
||||
($GLOBALS['egw_info']['user']['apps']['preferences'] || $GLOBALS['egw_info']['user']['apps']['password']) &&
|
||||
egw_time::to('now','ts')-($GLOBALS['egw_info']['server']['change_pwd_every_x_days']*86400)>$GLOBALS['egw_info']['user']['account_lastpwd_change']
|
||||
) || $GLOBALS['egw_info']['user']['account_lastpwd_change']==0)
|
||||
)
|
||||
{
|
||||
error_log(__METHOD__.' Password of '.$GLOBALS['egw_info']['user']['account_lid'].' ('.$GLOBALS['egw_info']['user']['account_fullname'].') is of old age.'.array2string(array(
|
||||
'ts'=>$GLOBALS['egw_info']['user']['account_lastpwd_change'],
|
||||
'date'=>egw_time::to($GLOBALS['egw_info']['user']['account_lastpwd_change']))));
|
||||
if ($GLOBALS['egw_info']['user']['account_lastpwd_change']==0)
|
||||
{
|
||||
$message = lang('an admin required that you must change your password upon login.');
|
||||
}
|
||||
else
|
||||
{
|
||||
$message = lang('it has been more then %1 days since you changed your password',$GLOBALS['egw_info']['server']['change_pwd_every_x_days']);
|
||||
}
|
||||
if ($GLOBALS['egw_info']['user']['apps']['password']) egw::redirect_link('/preferences/password.php',array('message'=>$message));
|
||||
egw::redirect_link('/index.php',array('menuaction'=>'preferences.uipassword.change','message'=>$message));
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* password authentication against password stored in sql datababse
|
||||
*
|
||||
@ -365,29 +405,58 @@ class auth
|
||||
* @author cornelius weiss <egw at von-und-zu-weiss.de>
|
||||
* @return mixed false if password is considered "safe" or a string $message if "unsafe"
|
||||
*/
|
||||
static function crackcheck($passwd)
|
||||
static function crackcheck($passwd,$reqstrength=5)
|
||||
{
|
||||
if (!preg_match('/.{'. ($noc=7). ',}/',$passwd))
|
||||
{
|
||||
$message = lang('Password must have at least %1 characters',$noc). '<br>';
|
||||
$message[] = lang('Password must have at least %1 characters',$noc). '<br>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$strength++;
|
||||
}
|
||||
if(!preg_match('/(.*\d.*){'. ($non=1). ',}/',$passwd))
|
||||
{
|
||||
$message .= lang('Password must contain at least %1 numbers',$non). '<br>';
|
||||
$message[] = lang('Password must contain at least %1 numbers',$non). '<br>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$strength++;
|
||||
}
|
||||
if(!preg_match('/(.*[[:upper:]].*){'. ($nou=1). ',}/',$passwd))
|
||||
{
|
||||
$message .= lang('Password must contain at least %1 uppercase letters',$nou). '<br>';
|
||||
$message[] = lang('Password must contain at least %1 uppercase letters',$nou). '<br>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$strength++;
|
||||
}
|
||||
if(!preg_match('/(.*[[:lower:]].*){'. ($nol=1). ',}/',$passwd))
|
||||
{
|
||||
$message .= lang('Password must contain at least %1 lowercase letters',$nol). '<br>';
|
||||
$message[] = lang('Password must contain at least %1 lowercase letters',$nol). '<br>';
|
||||
}
|
||||
else
|
||||
{
|
||||
$strength++;
|
||||
}
|
||||
if(!preg_match('/(.*[\\!"#$%&\'()*+,-.\/:;<=>?@\[\]\^_ {|}~`].*){'. ($nol=1). ',}/',$passwd))
|
||||
{
|
||||
$message .= lang('Password must contain at least %1 special characters',$nol). '<br>';
|
||||
$message[] = lang('Password must contain at least %1 special characters',$nol). '<br>';
|
||||
}
|
||||
return $message ? $message : false;
|
||||
else
|
||||
{
|
||||
$strength++;
|
||||
}
|
||||
if (count($message)>0 && $reqstrength>$strength)
|
||||
{
|
||||
$outmessage = lang('Your Password does not meet the required strength.<br> You must meet %1 criteria. You met only %2 criteria. <br>Your Password failed the following criteria:',$reqstrength,$strength);
|
||||
$outmessage .= '<br>'.implode(' ',$message);
|
||||
}
|
||||
else
|
||||
{
|
||||
$outmessage =false;
|
||||
}
|
||||
return $outmessage ? $outmessage : false;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -362,15 +362,15 @@ abstract class egw_framework
|
||||
$var['quick_add'] = $this->_get_quick_add();
|
||||
|
||||
$var['user_info'] = $this->_user_time_info();
|
||||
|
||||
if($GLOBALS['egw_info']['user']['lastpasswd_change'] == 0)
|
||||
|
||||
if($GLOBALS['egw_info']['user']['account_lastpwd_change'] == 0)
|
||||
{
|
||||
$api_messages = lang('You are required to change your password during your first login').'<br />'.
|
||||
lang('Click this image on the navbar: %1','<img src="'.common::image('preferences','navbar.gif').'">');
|
||||
}
|
||||
elseif($GLOBALS['egw_info']['user']['lastpasswd_change'] < time() - (86400*30))
|
||||
elseif($GLOBALS['egw_info']['server']['change_pwd_every_x_days'] && $GLOBALS['egw_info']['user']['account_lastpwd_change'] < time() - (86400*$GLOBALS['egw_info']['server']['change_pwd_every_x_days']))
|
||||
{
|
||||
$api_messages = lang('it has been more then %1 days since you changed your password',30);
|
||||
$api_messages = lang('it has been more then %1 days since you changed your password',$GLOBALS['egw_info']['server']['change_pwd_every_x_days']);
|
||||
}
|
||||
|
||||
// This is gonna change
|
||||
|
@ -23,6 +23,7 @@ class uipassword
|
||||
|
||||
function change()
|
||||
{
|
||||
//_debug_array($GLOBALS['egw_info']['user']);
|
||||
$n_passwd = $_POST['n_passwd'];
|
||||
$n_passwd_2 = $_POST['n_passwd_2'];
|
||||
$o_passwd_2 = $_POST['o_passwd_2'];
|
||||
@ -72,11 +73,23 @@ class uipassword
|
||||
$errors[] = lang('The two passwords are not the same');
|
||||
}
|
||||
|
||||
if($o_passwd == $n_passwd)
|
||||
{
|
||||
$errors[] = lang('Old password and new password are the same. This is invalid. You must enter a new password');
|
||||
}
|
||||
|
||||
if(!$n_passwd)
|
||||
{
|
||||
$errors[] = lang('You must enter a password');
|
||||
}
|
||||
if($GLOBALS['egw_info']['server']['check_save_passwd'] && $error_msg = $GLOBALS['egw']->auth->crackcheck($n_passwd))
|
||||
$strength = ($GLOBALS['egw_info']['server']['force_pwd_strength']?$GLOBALS['egw_info']['server']['force_pwd_strength']:false);
|
||||
//error_log(__METHOD__.__LINE__.' Strength:'.$strength);
|
||||
|
||||
if ($strength && $strength>5) $strength =5;
|
||||
if ($strength && $strength<0) $strength = false;
|
||||
if($GLOBALS['egw_info']['server']['check_save_passwd'] && $strength==false) $strength=5;//old behavior
|
||||
//error_log(__METHOD__.__LINE__.' Strength:'.$strength);
|
||||
if(($GLOBALS['egw_info']['server']['check_save_passwd'] || $strength) && $error_msg = $GLOBALS['egw']->auth->crackcheck($n_passwd,$strength))
|
||||
{
|
||||
$errors[] = $error_msg;
|
||||
}
|
||||
@ -104,7 +117,10 @@ class uipassword
|
||||
{
|
||||
$GLOBALS['egw']->session->appsession('password','phpgwapi',base64_encode($n_passwd));
|
||||
$GLOBALS['egw_info']['user']['passwd'] = $n_passwd;
|
||||
$GLOBALS['egw_info']['user']['account_lastpwd_change'] = egw_time::to('now','ts');
|
||||
accounts::cache_invalidate($GLOBALS['egw_info']['user']['account_id']);
|
||||
egw::invalidate_session_cache();
|
||||
//_debug_array( $GLOBALS['egw_info']['user']);
|
||||
$GLOBALS['hook_values']['account_id'] = $GLOBALS['egw_info']['user']['account_id'];
|
||||
$GLOBALS['hook_values']['old_passwd'] = $o_passwd;
|
||||
$GLOBALS['hook_values']['new_passwd'] = $n_passwd;
|
||||
|
@ -11,12 +11,16 @@
|
||||
|
||||
$GLOBALS['egw_info'] = array(
|
||||
'flags' => array(
|
||||
'noheader' => true,
|
||||
'novavbar' => true,
|
||||
'currentapp' => 'preferences',
|
||||
'disable_Template_class' => True,
|
||||
),
|
||||
);
|
||||
include('../header.inc.php');
|
||||
|
||||
auth::check_password_age('preferences','index');
|
||||
$GLOBALS['egw_info']['flags']['nonavbar']=false;
|
||||
common::egw_header();
|
||||
$pref_tpl =& CreateObject('phpgwapi.Template',EGW_APP_TPL);
|
||||
$templates = Array(
|
||||
'pref' => 'index.tpl'
|
||||
|
Loading…
Reference in New Issue
Block a user