aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarc Alexander <admin@m-a-styles.de>2019-08-10 17:18:39 +0200
committerMarc Alexander <admin@m-a-styles.de>2019-08-10 17:18:39 +0200
commitcefdf8bf19d764b7fef3d04383a41ed856af5503 (patch)
tree6a5233f23e3fecd7bfbfa220304317f9f561a709
parenteee18f3747592eb17ea1a16184c19e5cda1f500e (diff)
downloadforums-cefdf8bf19d764b7fef3d04383a41ed856af5503.tar
forums-cefdf8bf19d764b7fef3d04383a41ed856af5503.tar.gz
forums-cefdf8bf19d764b7fef3d04383a41ed856af5503.tar.bz2
forums-cefdf8bf19d764b7fef3d04383a41ed856af5503.tar.xz
forums-cefdf8bf19d764b7fef3d04383a41ed856af5503.zip
[ticket/11327] Finish up initial version of password reset system
PHPBB3-11327
-rw-r--r--phpBB/config/default/container/services_ucp.yml1
-rw-r--r--phpBB/language/en/ucp.php5
-rw-r--r--phpBB/phpbb/ucp/controller/reset_password.php130
-rw-r--r--phpBB/styles/prosilver/template/ucp_reset_password.html38
4 files changed, 111 insertions, 63 deletions
diff --git a/phpBB/config/default/container/services_ucp.yml b/phpBB/config/default/container/services_ucp.yml
index 923f35033c..44e97cb546 100644
--- a/phpBB/config/default/container/services_ucp.yml
+++ b/phpBB/config/default/container/services_ucp.yml
@@ -7,6 +7,7 @@ services:
- '@dispatcher'
- '@controller.helper'
- '@language'
+ - '@log'
- '@passwords.manager'
- '@request'
- '@template'
diff --git a/phpBB/language/en/ucp.php b/phpBB/language/en/ucp.php
index 0a82e150ef..b0df497625 100644
--- a/phpBB/language/en/ucp.php
+++ b/phpBB/language/en/ucp.php
@@ -416,7 +416,8 @@ $lang = array_merge($lang, array(
'PASS_TYPE_SYMBOL_EXPLAIN' => 'Password must be between %1$s and %2$s long, must contain letters in mixed case, must contain numbers and must contain symbols.',
'PASSWORD' => 'Password',
'PASSWORD_ACTIVATED' => 'Your new password has been activated.',
- 'PASSWORD_UPDATED_IF_EXISTED' => 'If your account exists, a new password was sent to your registered email address. If you do not receive an email, it may be because you are banned, your account is not activated, or you are not allowed to change your password. Contact admin if any of those reasons apply. Also, check your spam filter.',
+ 'PASSWORD_RESET' => 'Your password has been successfully reset.',
+ 'PASSWORD_RESET_LINK_SENT' => 'If your account exists, a password reset link was sent to your registered email address. If you do not receive an email, it may be because you are banned, your account is not activated, you have requested multiple password resets within a short time frame, or you are not allowed to change your password. Contact an admin if any of those reasons apply. Also, please check your spam filter.',
'PERMISSIONS_RESTORED' => 'Successfully restored original permissions.',
'PERMISSIONS_TRANSFERRED' => 'Successfully transferred permissions from <strong>%s</strong>, you are now able to browse the board with this user’s permissions.<br />Please note that admin permissions were not transferred. You are able to revert to your permission set at any time.',
'PM_DISABLED' => 'Private messaging has been disabled on this board.',
@@ -464,6 +465,7 @@ $lang = array_merge($lang, array(
'REPLIED_MESSAGE' => 'Replied to message',
'REPLY_TO_ALL' => 'Reply to sender and all recipients.',
'REPORT_PM' => 'Report private message',
+ 'RESET_PASSWORD' => 'Reset password',
'RESET_TOKEN_EXPIRED_OR_INVALID' => 'The password reset token you supplied is invalid or has expired.',
'RESIGN_SELECTED' => 'Resign selected',
'RETURN_FOLDER' => '%1$sReturn to previous folder%2$s',
@@ -480,7 +482,6 @@ $lang = array_merge($lang, array(
'SAME_PASSWORD_ERROR' => 'The new password you entered is the same as your current password.',
'SEARCH_YOUR_POSTS' => 'Show your posts',
- 'SEND_PASSWORD' => 'Send password',
'SENT_AT' => 'Sent', // Used before dates in private messages
'SHOW_EMAIL' => 'Users can contact me by email',
'SIGNATURE_EXPLAIN' => 'This is a block of text that can be added to posts you make. There is a %d character limit.',
diff --git a/phpBB/phpbb/ucp/controller/reset_password.php b/phpBB/phpbb/ucp/controller/reset_password.php
index 3d34c4740b..c686f198c5 100644
--- a/phpBB/phpbb/ucp/controller/reset_password.php
+++ b/phpBB/phpbb/ucp/controller/reset_password.php
@@ -18,6 +18,7 @@ use phpbb\controller\helper;
use phpbb\db\driver\driver_interface;
use phpbb\event\dispatcher;
use phpbb\language\language;
+use phpbb\log\log_interface;
use phpbb\passwords\manager;
use phpbb\request\request_interface;
use phpbb\template\template;
@@ -45,6 +46,9 @@ class reset_password
/** @var language */
protected $language;
+ /** @var log_interface */
+ protected $log;
+
/** @var manager */
protected $passwords_manager;
@@ -74,6 +78,7 @@ class reset_password
* @param dispatcher $dispatcher
* @param helper $helper
* @param language $language
+ * @param log_interface $log
* @param manager $passwords_manager
* @param request_interface $request
* @param template $template
@@ -83,14 +88,15 @@ class reset_password
* @param $php_ext
*/
public function __construct(config $config, driver_interface $db, dispatcher $dispatcher, helper $helper,
- language $language, manager $passwords_manager, request_interface $request,
- template $template, user $user, $tables, $root_path, $php_ext)
+ language $language, log_interface $log, manager $passwords_manager,
+ request_interface $request, template $template, user $user, $tables, $root_path, $php_ext)
{
$this->config = $config;
$this->db = $db;
$this->dispatcher = $dispatcher;
$this->helper = $helper;
$this->language = $language;
+ $this->log = $log;
$this->passwords_manager = $passwords_manager;
$this->request = $request;
$this->template = $template;
@@ -109,11 +115,29 @@ class reset_password
if (!$this->config['allow_password_reset'])
{
- $this->helper->message($this->language->lang('UCP_PASSWORD_RESET_DISABLED', '<a href="mailto:' . htmlspecialchars($this->config['board_contact']) . '">', '</a>'));
+ trigger_error($this->language->lang('UCP_PASSWORD_RESET_DISABLED', '<a href="mailto:' . htmlspecialchars($this->config['board_contact']) . '">', '</a>'));
}
}
/**
+ * Remove reset token for specified user
+ *
+ * @param int $user_id User ID
+ */
+ protected function remove_reset_token(int $user_id)
+ {
+ $sql_ary = [
+ 'reset_token' => '',
+ 'reset_token_expiration' => 0,
+ ];
+
+ $sql = 'UPDATE ' . $this->tables['users'] . '
+ SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . '
+ WHERE user_id = ' . $user_id;
+ $this->db->sql_query($sql);
+ }
+
+ /**
* Handle password reset request
*
* @return Response
@@ -180,7 +204,7 @@ class reset_password
}
else
{
- $message = $this->language->lang('PASSWORD_UPDATED_IF_EXISTED') . '<br /><br />' . $this->language->lang('RETURN_INDEX', '<a href="' . append_sid("{$this->root_path}index.{$this->php_ext}") . '">', '</a>');
+ $message = $this->language->lang('PASSWORD_RESET_LINK_SENT') . '<br /><br />' . $this->language->lang('RETURN_INDEX', '<a href="' . append_sid("{$this->root_path}index.{$this->php_ext}") . '">', '</a>');
$user_row = empty($rowset) ? [] : $rowset[0];
$this->db->sql_freeresult($result);
@@ -254,7 +278,7 @@ class reset_password
'S_PROFILE_ACTION' => $this->helper->route('phpbb_ucp_forgot_password_controller'),
]);
- return $this->helper->render('ucp_remind.html', $this->language->lang('UCP_REMIND'));
+ return $this->helper->render('ucp_reset_password.html', $this->language->lang('UCP_REMIND'));
}
/**
@@ -272,12 +296,12 @@ class reset_password
if (empty($reset_token))
{
- $this->helper->message('NO_RESET_TOKEN');
+ return $this->helper->message('NO_RESET_TOKEN');
}
if (!$user_id)
{
- $this->helper->message('NO_USER');
+ return $this->helper->message('NO_USER');
}
add_form_key('ucp_remind');
@@ -314,31 +338,33 @@ class reset_password
if (empty($user_row))
{
- $this->helper->message($message);
+ return $this->helper->message($message);
}
if (!hash_equals($reset_token, $user_row['reset_token']))
{
- $this->helper->message($message);
+ return $this->helper->message($message);
}
if ($user_row['reset_token_expiration'] < time())
{
- $this->helper->message($message);
+ $this->remove_reset_token($user_id);
+
+ return $this->helper->message($message);
}
+ $error = [];
+
if ($submit)
{
if (!check_form_key('ucp_remind'))
{
- trigger_error('FORM_INVALID');
+ return $this->helper->message('FORM_INVALID');
}
- $message = $this->language->lang('PASSWORD_UPDATED_IF_EXISTED') . '<br /><br />' . $this->language->lang('RETURN_INDEX', '<a href="' . append_sid("{$this->root_path}index.{$this->php_ext}") . '">', '</a>');
-
if ($user_row['user_type'] == USER_IGNORE || $user_row['user_type'] == USER_INACTIVE)
{
- trigger_error($message);
+ return $this->helper->message($message);
}
// Check users permissions
@@ -347,46 +373,54 @@ class reset_password
if (!$auth2->acl_get('u_chgpasswd'))
{
- trigger_error($message);
+ return $this->helper->message($message);
}
- $server_url = generate_board_url();
-
- // Make password at least 8 characters long, make it longer if admin wants to.
- // gen_rand_string() however has a limit of 12 or 13.
- $user_password = gen_rand_string_friendly(max(8, mt_rand((int) $this->config['min_pass_chars'], (int) $this->config['max_pass_chars'])));
-
- // For the activation key a random length between 6 and 10 will do.
- $user_actkey = gen_rand_string(mt_rand(6, 10));
-
- $sql = 'UPDATE ' . USERS_TABLE . "
- SET user_newpasswd = '" . $this->db->sql_escape($this->passwords_manager->hash($user_password)) . "', user_actkey = '" . $this->db->sql_escape($user_actkey) . "'
- WHERE user_id = " . $user_row['user_id'];
- $this->db->sql_query($sql);
-
- include_once($this->root_path . 'includes/functions_messenger.' . $this->php_ext);
-
- $messenger = new messenger(false);
-
- $messenger->template('user_activate_passwd', $user_row['user_lang']);
-
- $messenger->set_addresses($user_row);
-
- $messenger->anti_abuse_headers($this->config, $this->user);
-
- $messenger->assign_vars([
- 'USERNAME' => htmlspecialchars_decode($user_row['username']),
- 'PASSWORD' => htmlspecialchars_decode($user_password),
- 'U_ACTIVATE' => "$server_url/ucp.{$this->php_ext}?mode=activate&u={$user_row['user_id']}&k=$user_actkey"
- ]);
-
- $messenger->send($user_row['user_notify_type']);
+ if (!function_exists('validate_data'))
+ {
+ include($this->root_path . 'includes/functions_user.' . $this->php_ext);
+ }
- trigger_error($message);
+ $data = [
+ 'new_password' => $this->request->untrimmed_variable('new_password', '', true),
+ 'password_confirm' => $this->request->untrimmed_variable('new_password_confirm', '', true),
+ ];
+ $check_data = [
+ 'new_password' => [
+ ['string', false, $this->config['min_pass_chars'], $this->config['max_pass_chars']],
+ ['password'],
+ ],
+ 'password_confirm' => ['string', true, $this->config['min_pass_chars'], $this->config['max_pass_chars']],
+ ];
+ $error = array_merge($error, validate_data($data, $check_data));
+ if (strcmp($data['new_password'], $data['password_confirm']) !== 0)
+ {
+ $error[] = ($data['password_confirm']) ? 'NEW_PASSWORD_ERROR' : 'NEW_PASSWORD_CONFIRM_EMPTY';
+ }
+ if (empty($error))
+ {
+ $sql_ary = [
+ 'user_password' => $this->passwords_manager->hash($data['new_password']),
+ 'user_login_attempts' => 0,
+ 'reset_token' => '',
+ 'reset_token_expiration' => 0,
+ ];
+ $sql = 'UPDATE ' . $this->tables['users'] . '
+ SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . '
+ WHERE user_id = ' . (int) $user_row['user_id'];
+ $this->db->sql_query($sql);
+ $this->log->add('user', $user_row['user_id'], $this->user->ip, 'LOG_USER_NEW_PASSWORD', false, [
+ 'reportee_id' => $user_row['user_id'],
+ $user_row['username']
+ ]);
+ meta_refresh(3, append_sid("{$this->root_path}index.{$this->php_ext}"));
+ trigger_error($this->language->lang('PASSWORD_RESET'));
+ }
}
$this->template->assign_vars([
'S_IS_PASSWORD_RESET' => true,
+ 'ERROR' => !empty($error) ? implode('<br />', array_map([$this->language, 'lang'], $error)) : '',
'S_PROFILE_ACTION' => $this->helper->route('phpbb_ucp_reset_password_controller'),
'S_HIDDEN_FIELDS' => build_hidden_fields([
'u' => $user_id,
@@ -394,6 +428,6 @@ class reset_password
]),
]);
- return $this->helper->render('ucp_remind.html', $this->language->lang('UCP_REMIND'));
+ return $this->helper->render('ucp_reset_password.html', $this->language->lang('UCP_REMIND'));
}
}
diff --git a/phpBB/styles/prosilver/template/ucp_reset_password.html b/phpBB/styles/prosilver/template/ucp_reset_password.html
index 8b700de430..3f9ffce519 100644
--- a/phpBB/styles/prosilver/template/ucp_reset_password.html
+++ b/phpBB/styles/prosilver/template/ucp_reset_password.html
@@ -1,32 +1,44 @@
<!-- INCLUDE overall_header.html -->
-<form action="{S_PROFILE_ACTION}" method="post" id="remind">
+<form action="{{ S_PROFILE_ACTION }}" method="post" id="remind">
<div class="panel">
<div class="inner">
<div class="content">
- <h2>{L_SEND_PASSWORD}</h2>
+ <h2>{{ lang('RESET_PASSWORD') }}</h2>
<fieldset>
+ {% if S_IS_PASSWORD_RESET %}
+ {% if ERROR %}<p class="error">{{ ERROR }}</p>{% endif %}
+ <dl>
+ <dt><label for="new_password">{{ lang('NEW_PASSWORD') ~ lang('COLON') }}</label></dt>
+ <dd><input type="password" name="new_password" id="new_password" size="25" maxlength="255" title="{{ lang('CHANGE_PASSWORD') }}" autocomplete="off" /></dd>
+ </dl>
+ <dl>
+ <dt><label for="new_password_confirm">{{ lang('CONFIRM_PASSWORD') ~ lang('COLON') }}</label></dt>
+ <dd><input type="password" name="new_password_confirm" id="new_password_confirm" size="25" maxlength="255" title="{{ lang('CONFIRM_PASSWORD') }}" autocomplete="off" /></dd>
+ </dl>
+ {% else %}
{% if USERNAME_REQUIRED %}
<p class="error">{{ lang('EMAIL_NOT_UNIQUE') }}</p>
{% endif %}
- <dl>
- <dt><label for="email">{L_EMAIL_ADDRESS}{L_COLON}</label><br /><span>{L_EMAIL_REMIND}</span></dt>
- <dd><input class="inputbox narrow" type="email" name="email" id="email" size="25" maxlength="100" value="{{ EMAIL }}" autofocus /></dd>
- </dl>
- {% if USERNAME_REQUIRED %}
- <dl>
- <dt><label for="username">{L_USERNAME}{L_COLON}</label></dt>
- <dd><input class="inputbox narrow" type="text" name="username" id="username" size="25" /></dd>
- </dl>
+ <dl>
+ <dt><label for="email">{{ lang('EMAIL_ADDRESS') ~ lang('COLON') }}</label><br /><span>{{ lang('EMAIL_REMIND') }}</span></dt>
+ <dd><input class="inputbox narrow" type="email" name="email" id="email" size="25" maxlength="100" value="{{ EMAIL }}" autofocus /></dd>
+ </dl>
+ {% if USERNAME_REQUIRED %}
+ <dl>
+ <dt><label for="username">{{ lang('USERNAME') ~ lang('COLON') }}</label></dt>
+ <dd><input class="inputbox narrow" type="text" name="username" id="username" size="25" /></dd>
+ </dl>
+ {% endif %}
{% endif %}
<dl>
<dt>&nbsp;</dt>
- <dd>{S_HIDDEN_FIELDS}<input type="submit" name="submit" id="submit" class="button1" value="{L_SUBMIT}" tabindex="2" />&nbsp; <input type="reset" value="{L_RESET}" name="reset" class="button2" /></dd>
+ <dd>{{ S_HIDDEN_FIELDS }}<input type="submit" name="submit" id="submit" class="button1" value="{{ lang('SUBMIT') }}" tabindex="2" />&nbsp; <input type="reset" value="{{ lang('RESET') }}" name="reset" class="button2" /></dd>
</dl>
- {S_FORM_TOKEN}
+ {{ S_FORM_TOKEN }}
</fieldset>
</div>