2011-04-01 15:35:16 +02:00
|
|
|
<?php
|
|
|
|
/**
|
|
|
|
* EGgroupware admin - Reset passwords
|
|
|
|
*
|
|
|
|
* @link http://www.egroupware.org
|
|
|
|
* @author Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
|
|
|
* @package admin
|
2016-03-28 20:51:38 +02:00
|
|
|
* @copyright (c) 2011-16 by Ralf Becker <RalfBecker-AT-outdoor-training.de>
|
2011-04-01 15:35:16 +02:00
|
|
|
* @license http://opensource.org/licenses/gpl-license.php GPL - GNU General Public License
|
|
|
|
* @version $Id$
|
|
|
|
*/
|
|
|
|
|
|
|
|
include_once(EGW_INCLUDE_ROOT.'/setup/inc/hook_config.inc.php'); // functions to return password hashes
|
|
|
|
|
2016-03-28 20:51:38 +02:00
|
|
|
use EGroupware\Api;
|
2020-12-17 21:12:21 +01:00
|
|
|
use EGroupware\Api\Framework;
|
2021-01-11 19:53:04 +01:00
|
|
|
use EGroupware\Api\Mail\Credentials;
|
|
|
|
use EGroupware\OpenID\Repositories\AccessTokenRepository;
|
|
|
|
use EGroupware\WebAuthn\PublicKeyCredentialSourceRepository;
|
2016-03-28 20:51:38 +02:00
|
|
|
|
2011-04-01 15:35:16 +02:00
|
|
|
/**
|
|
|
|
* Reset passwords
|
|
|
|
*/
|
|
|
|
class admin_passwordreset
|
|
|
|
{
|
|
|
|
/**
|
|
|
|
* Which methods of this class can be called as menuation
|
|
|
|
*
|
|
|
|
* @var array
|
|
|
|
*/
|
|
|
|
public $public_functions = array(
|
|
|
|
'index' => true,
|
2020-12-17 21:12:21 +01:00
|
|
|
'ajax_clear_credentials' => true
|
2011-04-01 15:35:16 +02:00
|
|
|
);
|
|
|
|
|
|
|
|
/**
|
|
|
|
* @var array
|
|
|
|
*/
|
2011-04-05 17:02:41 +02:00
|
|
|
var $replacements = array(
|
|
|
|
'lid' => 'LoginID',
|
|
|
|
'firstname' => 'first name',
|
|
|
|
'lastname' => 'last name',
|
|
|
|
'fullname' => 'full name',
|
|
|
|
'email' => 'email',
|
|
|
|
'password' => 'new password',
|
|
|
|
'id' => 'nummeric account ID',
|
|
|
|
);
|
2011-04-01 15:35:16 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Constructor
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
function __construct()
|
|
|
|
{
|
|
|
|
if($GLOBALS['egw']->acl->check('account_access',16,'admin'))
|
|
|
|
{
|
|
|
|
$GLOBALS['egw']->redirect_link('/index.php');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Reset passwords
|
|
|
|
*
|
2014-11-20 14:53:30 +01:00
|
|
|
* @param array $content =null
|
|
|
|
* @param string $msg =''
|
2011-04-01 15:35:16 +02:00
|
|
|
*/
|
|
|
|
function index(array $content=null, $msg='')
|
|
|
|
{
|
2024-06-28 23:16:41 +02:00
|
|
|
|
|
|
|
// Setup if opened as a dialog
|
|
|
|
$content['dialog'] = $_GET['dialog'];
|
|
|
|
if($content['dialog'] && !$content['users'] && isset($_GET['ids']))
|
|
|
|
{
|
|
|
|
$content['users'] = $_GET['ids'];
|
|
|
|
|
|
|
|
$query = Api\Cache::getSession('admin', 'account_list');
|
|
|
|
if($_GET['select_all'] == 'true')
|
|
|
|
{
|
|
|
|
@set_time_limit(0); // switch off the execution time limit, as it's for big selections to small
|
|
|
|
$query['num_rows'] = -1; // all
|
|
|
|
$readonlys = null;
|
|
|
|
$content['users'] = [];
|
|
|
|
$rows = [];
|
|
|
|
admin_ui::get_users($query, $rows);
|
|
|
|
foreach($rows as $row)
|
|
|
|
{
|
|
|
|
$content['users'][] = $row['account_id'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2011-04-01 15:35:16 +02:00
|
|
|
if (!($account_repository = $GLOBALS['egw_info']['server']['account_repository']) &&
|
|
|
|
!($account_repository = $GLOBALS['egw_info']['server']['auth_type']))
|
|
|
|
{
|
|
|
|
$account_repository = 'sql';
|
|
|
|
}
|
|
|
|
if (!($current_hash = $GLOBALS['egw_info']['server'][$account_repository.'_encryption_type']))
|
|
|
|
{
|
|
|
|
$current_hash = 'md5';
|
|
|
|
}
|
|
|
|
if (is_array($content))
|
|
|
|
{
|
2017-02-02 16:45:36 +01:00
|
|
|
// Save message for next time
|
|
|
|
Api\Config::save_value('password_reset_message',array('subject' => $content['subject'], 'body' => $content['body']),'admin');
|
|
|
|
|
2011-04-01 15:35:16 +02:00
|
|
|
if ($content['download_csv'] && $content['changed'])
|
|
|
|
{
|
2016-03-28 20:51:38 +02:00
|
|
|
Api\Header\Content::type('changed.csv', 'text/csv');
|
2015-07-01 20:35:24 +02:00
|
|
|
//echo "account_lid;account_password;account_email;account_firstname;account_lastname\n";
|
2011-04-01 15:35:16 +02:00
|
|
|
foreach($content['changed'] as $account)
|
|
|
|
{
|
2015-07-01 20:35:24 +02:00
|
|
|
echo "$account[account_lid];$account[account_password];$account[account_email];$account[account_firstname];$account[account_lastname]\n";
|
2011-04-01 15:35:16 +02:00
|
|
|
}
|
2016-03-28 20:51:38 +02:00
|
|
|
exit;
|
2011-04-01 15:35:16 +02:00
|
|
|
}
|
|
|
|
if (!$content['users'])
|
|
|
|
{
|
|
|
|
$msg = lang('You need to select some users first!');
|
|
|
|
}
|
2011-04-01 20:49:42 +02:00
|
|
|
elseif (!$content['random_pw'] && !$content['hash'] && !$content['notify'] &&
|
2013-04-05 15:08:08 +02:00
|
|
|
(string)$content['changepassword'] === '' && (string)$content['mustchangepassword'] === '' &&
|
2013-04-05 16:13:41 +02:00
|
|
|
(string)$content['mail']['activate'] === '' && (string)$content['mail']['quota'] === '' &&
|
|
|
|
strpos($content['mail']['domain'], '.') === false)
|
2011-04-01 15:35:16 +02:00
|
|
|
{
|
2011-04-01 20:49:42 +02:00
|
|
|
$msg = lang('You need to select as least one action!');
|
2011-04-01 15:35:16 +02:00
|
|
|
}
|
|
|
|
elseif(!$content['random_pw'] && $content['hash'] && $content['hash'] != $current_hash && $current_hash != 'plain')
|
|
|
|
{
|
|
|
|
$msg = lang('You can only change the hash, if you set a random password or currently use plaintext passwords!');
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
if ($content['hash'] && $content['hash'] != $current_hash)
|
|
|
|
{
|
2016-03-28 20:51:38 +02:00
|
|
|
Api\Config::save_value($account_repository.'_encryption_type',$content['hash'],'phpgwapi');
|
2011-04-01 15:35:16 +02:00
|
|
|
$msg = lang('Changed password hash for %1 to %2.',strtoupper($account_repository),$content['hash'])."\n";
|
|
|
|
$GLOBALS['egw_info']['server'][$account_repository.'_encryption_type'] = $content['hash'];
|
|
|
|
}
|
2011-04-01 20:49:42 +02:00
|
|
|
$change_pw = $content['random_pw'] || $content['hash'] && $content['hash'] != $current_hash;
|
2011-04-01 15:35:16 +02:00
|
|
|
$changed = array();
|
2014-11-20 14:53:30 +01:00
|
|
|
$emailadmin = null;
|
2011-04-01 15:35:16 +02:00
|
|
|
foreach($content['users'] as $account_id)
|
|
|
|
{
|
2024-06-27 23:24:46 +02:00
|
|
|
$result = $this->resetAccount($account_id, $content, $change_pw, $current_hash, $msg, $changed);
|
|
|
|
if($result === false)
|
2011-04-01 15:35:16 +02:00
|
|
|
{
|
2024-06-27 23:24:46 +02:00
|
|
|
break;
|
2011-04-01 15:35:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if ($changed)
|
|
|
|
{
|
2011-04-01 20:49:42 +02:00
|
|
|
$msg .= lang('Passwords and/or attributes of %1 accounts changed',count($changed));
|
2011-04-01 15:35:16 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$content['msg'] = $msg;
|
|
|
|
$content['account_repository'] = $account_repository;
|
2011-04-01 20:49:42 +02:00
|
|
|
$content['current_hash'] = $content['hash'] ? $content['hash'] : $current_hash;
|
2011-04-01 15:35:16 +02:00
|
|
|
$sel_options['hash'] = $account_repository == 'sql' ?
|
|
|
|
sql_passwdhashes($GLOBALS['egw_info']['server'],true) :
|
|
|
|
passwdhashes($GLOBALS['egw_info']['server'],true);
|
2013-04-05 16:13:41 +02:00
|
|
|
$sel_options['activate'] = array('Deactivate','Activate');
|
2011-04-01 20:49:42 +02:00
|
|
|
|
2017-02-02 16:45:36 +01:00
|
|
|
// Start with same message as last time
|
|
|
|
$config = Api\Config::read('admin');
|
|
|
|
$message = $config['password_reset_message'];
|
|
|
|
$content['subject'] = $message['subject'];
|
|
|
|
$content['body'] = $message['body'];
|
|
|
|
|
2011-04-01 15:35:16 +02:00
|
|
|
$content['replacements'] = array();
|
|
|
|
foreach($this->replacements as $name => $label)
|
|
|
|
{
|
|
|
|
$content['replacements'][] = array(
|
|
|
|
'name' => '$$'.$name.'$$',
|
|
|
|
'label' => $label,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
$readonlys['download_csv'] = !$changed;
|
|
|
|
|
2011-04-01 20:49:42 +02:00
|
|
|
$GLOBALS['egw_info']['flags']['app_header'] = lang('Bulk password reset');
|
2011-04-01 15:35:16 +02:00
|
|
|
|
2016-03-28 20:51:38 +02:00
|
|
|
$tmpl = new Api\Etemplate('admin.passwordreset');
|
2011-04-01 15:35:16 +02:00
|
|
|
$tmpl->exec('admin.admin_passwordreset.index',$content,$sel_options,$readonlys,array(
|
|
|
|
'changed' => $changed,
|
|
|
|
));
|
|
|
|
}
|
2020-12-17 21:12:21 +01:00
|
|
|
|
2024-06-27 23:24:46 +02:00
|
|
|
protected function resetAccount($account_id, $content, $change_pw, $current_hash, &$msg, &$changed)
|
|
|
|
{
|
|
|
|
if(($account = $GLOBALS['egw']->accounts->read($account_id)))
|
|
|
|
{
|
|
|
|
if($content['random_pw'])
|
|
|
|
{
|
|
|
|
if(($minlength = $GLOBALS['egw_info']['server']['force_pwd_length']) < 8)
|
|
|
|
{
|
|
|
|
$minlength = 8;
|
|
|
|
}
|
|
|
|
$n = 0;
|
|
|
|
do
|
|
|
|
{
|
|
|
|
$password = Api\Auth::randomstring($minlength,
|
|
|
|
$GLOBALS['egw_info']['server']['force_pwd_strength'] >= 4
|
|
|
|
);
|
|
|
|
error_log(__METHOD__ . "() minlength=$minlength, n=$n, password=$password");
|
|
|
|
}
|
|
|
|
while(++$n < 100 && Api\Auth::crackcheck($password, null, null, null, $account));
|
|
|
|
$old_password = null;
|
|
|
|
}
|
|
|
|
elseif($change_pw && !preg_match('/^{plain}/i', $account['account_pwd']) &&
|
|
|
|
($current_hash != 'plain' || $current_hash == 'plain' && $account['account_pwd'][0] == '{'))
|
|
|
|
{
|
|
|
|
$msg .= lang('Account "%1" has NO plaintext password!', $account['account_lid']) . "\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$old_password = $password = preg_replace('/^{plain}/i', '', $account['account_pwd']);
|
|
|
|
}
|
|
|
|
// change password, if requested
|
|
|
|
try
|
|
|
|
{
|
|
|
|
if($change_pw && !$GLOBALS['egw']->auth->change_password($old_password, $password, $account_id))
|
|
|
|
{
|
|
|
|
$msg .= lang('Failed to change password for account "%1"!', $account['account_lid']) . "\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
catch (Exception $e)
|
|
|
|
{
|
|
|
|
$msg .= lang('Failed to change password for account "%1"!', $account['account_lid']) . ' ' . $e->getMessage() . "\n";
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
// force password change on next login
|
|
|
|
if((string)$content['mustchangepassword'] !== '' && !(!$content['mustchangepassword'] && $change_pw))
|
|
|
|
{
|
|
|
|
// dont use password here, as the use of passwords indicates the usage of the functionality in usermode
|
|
|
|
$GLOBALS['egw']->auth->setLastPwdChange($account_id, null, $content['mustchangepassword'] ? 0 : time());
|
|
|
|
}
|
|
|
|
// allow or forbid to change password, if requested
|
|
|
|
if((string)$content['changepassword'] !== '')
|
|
|
|
{
|
|
|
|
if(!$content['changepassword'])
|
|
|
|
{
|
|
|
|
$GLOBALS['egw']->acl->add_repository('preferences', 'nopasswordchange', $account_id, 1);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$GLOBALS['egw']->acl->delete_repository('preferences', 'nopasswordchange', $account_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$account['account_password'] = $password;
|
|
|
|
|
|
|
|
if((string)$content['mail']['activate'] !== '' || (string)$content['mail']['quota'] !== '' ||
|
|
|
|
strpos($content['mail']['domain'], '.') !== false)
|
|
|
|
{
|
|
|
|
if(!isset($emailadmin))
|
|
|
|
{
|
|
|
|
$emailadmin = Api\Mail\Account::get_default();
|
|
|
|
if(!Api\Mail\Account::is_multiple($emailadmin))
|
|
|
|
{
|
|
|
|
$msg = lang('No default account found!');
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if(($userData = $emailadmin->getUserData($account_id)))
|
|
|
|
{
|
|
|
|
if((string)$content['mail']['activate'] !== '')
|
|
|
|
{
|
|
|
|
$userData['accountStatus'] = $content['mail']['activate'] ? 'active' : '';
|
|
|
|
}
|
|
|
|
if((string)$content['mail']['quota'] !== '')
|
|
|
|
{
|
|
|
|
$userData['quotaLimit'] = $content['mail']['quota'];
|
|
|
|
}
|
|
|
|
if(strpos($content['mail']['domain'], '.') !== false)
|
|
|
|
{
|
|
|
|
$userData['mailLocalAddress'] = preg_replace('/@' . preg_quote($emailadmin->acc_domain) . '$/', '@' . $content['mail']['domain'], $userData['mailLocalAddress']);
|
|
|
|
|
|
|
|
foreach($userData['mailAlternateAddress'] as &$alias)
|
|
|
|
{
|
|
|
|
$alias = preg_replace('/@' . preg_quote($emailadmin->acc_domain) . '$/', '@' . $content['mail']['domain'], $alias);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$emailadmin->saveUserData($account_id, $userData);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
$msg .= lang('No profile defined for user %1', '#' . $account_id . ' ' . $account['account_fullname'] . "\n");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$changed[] = $account;
|
|
|
|
|
|
|
|
if($content['notify'])
|
|
|
|
{
|
|
|
|
if(strpos($account['account_email'], '@') === false)
|
|
|
|
{
|
|
|
|
$msg .= lang('Account "%1" has no email address --> not notified!', $account['account_lid']);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
$send = new Api\Mailer();
|
|
|
|
$send->AddAddress($account['account_email'], $account['account_fullname']);
|
|
|
|
$replacements = array();
|
|
|
|
foreach($this->replacements as $name => $label)
|
|
|
|
{
|
|
|
|
$replacements['$$' . $name . '$$'] = $account['account_' . $name];
|
|
|
|
}
|
|
|
|
$send->addHeader('Subject', strtr($content['subject'], $replacements));
|
|
|
|
$send->setBody(strtr($content['body'], $replacements));
|
|
|
|
if(!empty($GLOBALS['egw_info']['user']['account_email']))
|
|
|
|
{
|
|
|
|
$send->addHeader('From', Api\Mailer::add_personal(
|
|
|
|
$GLOBALS['egw_info']['user']['account_email'],
|
|
|
|
$GLOBALS['egw_info']['user']['account_fullname']
|
|
|
|
)
|
|
|
|
);
|
|
|
|
}
|
|
|
|
try
|
|
|
|
{
|
|
|
|
$send->Send();
|
|
|
|
}
|
|
|
|
catch (Exception $e)
|
|
|
|
{
|
|
|
|
$msg .= lang('Notifying account "%1" %2 failed!', $account['account_lid'], $account['account_email']) .
|
|
|
|
': ' . strip_tags(str_replace('<p>', "\n", $e->getMessage())) . "\n";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-11 19:53:04 +01:00
|
|
|
public function ajax_clear_credentials($action_id, $account_ids)
|
2020-12-17 21:12:21 +01:00
|
|
|
{
|
|
|
|
$msg = [];
|
|
|
|
|
2021-01-11 19:53:04 +01:00
|
|
|
if($action_id == 'clear_mail')
|
2020-12-17 21:12:21 +01:00
|
|
|
{
|
2021-01-15 21:54:40 +01:00
|
|
|
$count = Api\Mail\Credentials::delete(0,$account_ids, Credentials::IMAP|Credentials::SMTP|Credentials::SMIME);
|
2020-12-17 21:12:21 +01:00
|
|
|
$msg[] = lang("%1 mail credentials deleted", $count);
|
|
|
|
}
|
|
|
|
|
|
|
|
$action['action'] = 'delete';
|
|
|
|
$action['selected'] = $account_ids;
|
2021-01-11 19:53:04 +01:00
|
|
|
$hook_data = array();
|
2020-12-17 21:12:21 +01:00
|
|
|
|
2021-01-11 19:53:04 +01:00
|
|
|
if($action_id == 'clear_2fa')
|
|
|
|
{
|
2021-01-15 21:54:40 +01:00
|
|
|
if (Credentials::delete(0, $account_ids, Credentials::TWOFA))
|
2021-01-11 19:53:04 +01:00
|
|
|
{
|
|
|
|
$msg[] = lang('Secret deleted, two factor authentication disabled.');
|
|
|
|
}
|
|
|
|
$hook_data = Api\Hooks::process(array('location' => 'preferences_security'), ['openid'], true);
|
|
|
|
}
|
2020-12-17 21:12:21 +01:00
|
|
|
foreach($hook_data as $extra_tab)
|
|
|
|
{
|
|
|
|
if($extra_tab['delete'])
|
|
|
|
{
|
|
|
|
$msg[] = call_user_func_array($extra_tab['delete'], [$account_ids]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-01-11 19:53:04 +01:00
|
|
|
switch ($extra_tab['name'])
|
2020-12-17 21:12:21 +01:00
|
|
|
{
|
2021-01-11 19:53:04 +01:00
|
|
|
case 'openid.access_tokens':
|
|
|
|
// We need to get all access tokens, no easy way to delete by account
|
|
|
|
$token_repo = new AccessTokenRepository();
|
|
|
|
$token_repo->revokeAccessToken(['account_id' => $action['selected']]);
|
|
|
|
$count = $GLOBALS['egw']->db->affected_rows();
|
|
|
|
$msg[] = ($count > 1 ? $count.' ' : '') . lang('Access Token revoked.');
|
|
|
|
break;
|
|
|
|
case 'webauthn.tokens':
|
|
|
|
$token_repo = new PublicKeyCredentialSourceRepository();
|
|
|
|
$count = $token_repo->delete(['account_id' => $action['selected']]);
|
|
|
|
$msg[] = ($count > 1 ? $count.' ' : '') . lang($extra_tab['label']) . ' ' . lang('deleted');
|
2021-01-15 21:54:40 +01:00
|
|
|
break;
|
2021-01-11 19:53:04 +01:00
|
|
|
default:
|
|
|
|
// Each credential / security option can have its nm as a different ID
|
|
|
|
$content['tabs'] = $extra_tab['name'];
|
|
|
|
foreach($extra_tab['data'] as $id => $datum)
|
|
|
|
{
|
|
|
|
if(is_array($datum) && array_key_exists('get_rows',$datum))
|
|
|
|
{
|
|
|
|
$content[$id] = $action;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$msg[] = call_user_func_array($extra_tab['save_callback'], [$content]);
|
2020-12-17 21:12:21 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Framework::message(implode("\n",$msg), 'success');
|
|
|
|
Framework::redirect_link('/index.php', 'menuaction=admin.admin_ui.index','admin');
|
|
|
|
}
|
2024-06-27 23:24:46 +02:00
|
|
|
|
|
|
|
public function ajax_reset($content)
|
|
|
|
{
|
|
|
|
$msg = '';
|
|
|
|
$changed = [];
|
|
|
|
if(!($account_repository = $GLOBALS['egw_info']['server']['account_repository']) &&
|
|
|
|
!($account_repository = $GLOBALS['egw_info']['server']['auth_type']))
|
|
|
|
{
|
|
|
|
$account_repository = 'sql';
|
|
|
|
}
|
|
|
|
if(!($current_hash = $GLOBALS['egw_info']['server'][$account_repository . '_encryption_type']))
|
|
|
|
{
|
|
|
|
$current_hash = 'md5';
|
|
|
|
}
|
|
|
|
$change_pw = $content['random_pw'] || $content['hash'] && $content['hash'] != $current_hash;
|
|
|
|
|
|
|
|
$result = $this->resetAccount($content['user'], $content['values'], $change_pw, $current_hash, $msg, $changed);
|
|
|
|
|
|
|
|
if(count($changed) == 1)
|
|
|
|
{
|
|
|
|
Api\Json\Response::get()->data($msg !== "" ? $msg : $GLOBALS['egw']->accounts->id2name($content['user']));
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
Api\Json\Response::get()->error($msg);
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2011-04-01 15:35:16 +02:00
|
|
|
}
|