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:
Klaus Leithoff 2010-09-22 09:48:27 +00:00
parent 1f8e2e93df
commit 3843c0b59b
11 changed files with 165 additions and 22 deletions

View File

@ -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']);

View File

@ -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') ? '&nbsp;&nbsp;X' : '&nbsp;';
$var['changepassword'] = !$acl->check('nopasswordchange',1,'preferences') ? '&nbsp;&nbsp;X' : '&nbsp;';
$var['mustchangepassword']= $userData['account_lastpwd_change']==0 ? '&nbsp;&nbsp;X' : '&nbsp;';
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'] . '">',

View File

@ -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>

View File

@ -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>

View File

@ -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();
?>

View File

@ -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'])
{

View File

@ -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'];

View File

@ -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;
}
/**

View File

@ -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

View File

@ -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;

View File

@ -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'