diff options
Diffstat (limited to 'phpBB/phpbb/ucp')
| -rw-r--r-- | phpBB/phpbb/ucp/controller/reset_password.php | 443 | 
1 files changed, 443 insertions, 0 deletions
| diff --git a/phpBB/phpbb/ucp/controller/reset_password.php b/phpBB/phpbb/ucp/controller/reset_password.php new file mode 100644 index 0000000000..5c27c4f414 --- /dev/null +++ b/phpBB/phpbb/ucp/controller/reset_password.php @@ -0,0 +1,443 @@ +<?php +/** +* +* This file is part of the phpBB Forum Software package. +* +* @copyright (c) phpBB Limited <https://www.phpbb.com> +* @license GNU General Public License, version 2 (GPL-2.0) +* +* For full copyright and license information, please see +* the docs/CREDITS.txt file. +* +*/ + +namespace phpbb\ucp\controller; + +use phpbb\auth\auth; +use phpbb\config\config; +use phpbb\controller\helper; +use phpbb\db\driver\driver_interface; +use phpbb\event\dispatcher; +use phpbb\exception\http_exception; +use phpbb\language\language; +use phpbb\log\log_interface; +use phpbb\passwords\manager; +use phpbb\request\request_interface; +use phpbb\template\template; +use phpbb\user; +use Symfony\Component\HttpFoundation\Response; + +/** +* Handling forgotten passwords via reset password functionality +*/ +class reset_password +{ +	/** @var config */ +	protected $config; + +	/** @var driver_interface */ +	protected $db; + +	/** @var dispatcher */ +	protected $dispatcher; + +	/** @var helper */ +	protected $helper; + +	/** @var language */ +	protected $language; + +	/** @var log_interface */ +	protected $log; + +	/** @var manager */ +	protected $passwords_manager; + +	/** @var request_interface */ +	protected $request; + +	/** @var template */ +	protected $template; + +	/** @var user */ +	protected $user; + +	/** @var array phpBB DB table names */ +	protected $users_table; + +	/** @var string phpBB root path */ +	protected $root_path; + +	/** @var string PHP extension */ +	protected $php_ext; + +	/** +	 * Reset password controller constructor. +	 * +	 * @param config $config +	 * @param driver_interface $db +	 * @param dispatcher $dispatcher +	 * @param helper $helper +	 * @param language $language +	 * @param log_interface $log +	 * @param manager $passwords_manager +	 * @param request_interface $request +	 * @param template $template +	 * @param user $user +	 * @param string $users_table +	 * @param string $root_path +	 * @param string $php_ext +	 */ +	public function __construct(config $config, driver_interface $db, dispatcher $dispatcher, helper $helper, +								language $language, log_interface $log, manager $passwords_manager, +								request_interface $request, template $template, user $user, string $users_table, +								string $root_path, string $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; +		$this->user = $user; +		$this->users_table = $users_table; +		$this->root_path = $root_path; +		$this->php_ext = $php_ext; +	} + +	/** +	 * Init controller +	 */ +	protected function init_controller() +	{ +		$this->language->add_lang('ucp'); + +		if (!$this->config['allow_password_reset']) +		{ +			throw new http_exception(Response::HTTP_OK, '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->users_table . ' +					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 +	 */ +	public function request() +	{ +		$this->init_controller(); + +		$submit		= $this->request->is_set_post('submit'); +		$username	= $this->request->variable('username', '', true); +		$email		= strtolower($this->request->variable('email', '')); + +		add_form_key('ucp_reset_password'); + +		if ($submit) +		{ +			if (!check_form_key('ucp_reset_password')) +			{ +				throw new http_exception(Response::HTTP_UNAUTHORIZED, 'FORM_INVALID'); +			} + +			if (empty($email)) +			{ +				return $this->helper->message('NO_EMAIL_USER'); +			} + +			$sql_array = [ +				'SELECT'	=> 'user_id, username, user_permissions, user_email, user_jabber, user_notify_type, user_type,' +								. ' user_lang, user_inactive_reason, reset_token, reset_token_expiration', +				'FROM'		=> [$this->users_table => 'u'], +				'WHERE'		=> "user_email = '" . $this->db->sql_escape($email) . "'" . +					(!empty($username) ? " AND username_clean = '" . $this->db->sql_escape(utf8_clean_string($username)) . "'" : ''), +			]; + +			/** +			 * Change SQL query for fetching user data +			 * +			 * @event core.ucp_remind_modify_select_sql +			 * @var	string	email		User's email from the form +			 * @var	string	username	User's username from the form +			 * @var	array	sql_array	Fully assembled SQL query with keys SELECT, FROM, WHERE +			 * @since 3.1.11-RC1 +			 * @changed 3.3.0-b1 Moved to reset password controller +			 */ +			$vars = [ +				'email', +				'username', +				'sql_array', +			]; +			extract($this->dispatcher->trigger_event('core.ucp_remind_modify_select_sql', compact($vars))); + +			$sql = $this->db->sql_build_query('SELECT', $sql_array); +			$result = $this->db->sql_query_limit($sql, 2); // don't waste resources on more rows than we need +			$rowset = $this->db->sql_fetchrowset($result); +			$this->db->sql_freeresult($result); + +			if (count($rowset) > 1) +			{ +				$this->template->assign_vars([ +					'USERNAME_REQUIRED'	=> true, +					'EMAIL'				=> $email, +				]); +			} +			else +			{ +				$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>'); + +				if (empty($rowset)) +				{ +					return $this->helper->message($message); +				} + +				$user_row = $rowset[0]; + +				if ($user_row['user_type'] == USER_IGNORE || $user_row['user_type'] == USER_INACTIVE) +				{ +					return $this->helper->message($message); +				} + +				// Do not create multiple valid reset tokens +				if (!empty($user_row['reset_token']) && (int) $user_row['reset_token_expiration'] >= time()) +				{ +					return $this->helper->message($message); +				} + +				// Check users permissions +				$auth = new auth(); +				$auth->acl($user_row); + +				if (!$auth->acl_get('u_chgpasswd')) +				{ +					return $this->helper->message($message); +				} + +				// Generate reset token +				$reset_token = strtolower(gen_rand_string(32)); + +				$sql_ary = [ +					'reset_token'				=> $reset_token, +					'reset_token_expiration'	=> strtotime('+1 day'), +				]; + +				$sql = 'UPDATE ' . $this->users_table . ' +					SET ' . $this->db->sql_build_array('UPDATE', $sql_ary) . ' +					WHERE user_id = ' . $user_row['user_id']; +				$this->db->sql_query($sql); + +				if (!class_exists('messenger')) +				{ +					include($this->root_path . 'includes/functions_messenger.' . $this->php_ext); +				} + +				/** @var \messenger $messenger */ +				$messenger = new \messenger(false); + +				$messenger->template('user_forgot_password', $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']), +						'U_RESET_PASSWORD'	=> generate_board_url(true) . $this->helper->route('phpbb_ucp_reset_password_controller', [ +							'u'		=> $user_row['user_id'], +							'token'	=> $reset_token, +						], false) +				]); + +				$messenger->send($user_row['user_notify_type']); + +				return $this->helper->message($message); +			} +		} + +		$this->template->assign_vars([ +			'USERNAME'					=> $username, +			'EMAIL'						=> $email, +			'U_RESET_PASSWORD_ACTION'	=> $this->helper->route('phpbb_ucp_forgot_password_controller'), +		]); + +		return $this->helper->render('ucp_reset_password.html', $this->language->lang('RESET_PASSWORD')); +	} + +	/** +	 * Handle controller requests +	 * +	 * @return Response +	 */ +	public function reset() +	{ +		$this->init_controller(); + +		$submit			= $this->request->is_set_post('submit'); +		$reset_token	= $this->request->variable('token', ''); +		$user_id		= $this->request->variable('u', 0); + +		if (empty($reset_token)) +		{ +			return $this->helper->message('NO_RESET_TOKEN'); +		} + +		if (!$user_id) +		{ +			return $this->helper->message('NO_USER'); +		} + +		add_form_key('ucp_reset_password'); + +		$sql_array = [ +			'SELECT'	=> 'user_id, username, user_permissions, user_email, user_jabber, user_notify_type, user_type,' +				. ' user_lang, user_inactive_reason, reset_token, reset_token_expiration', +			'FROM'		=> [$this->users_table => 'u'], +			'WHERE'		=> 'user_id = ' . $user_id, +		]; + +		/** +		 * Change SQL query for fetching user data +		 * +		 * @event core.ucp_reset_password_modify_select_sql +		 * @var	int	user_id		User ID from the form +		 * @var	string	reset_token Reset token +		 * @var	array	sql_array	Fully assembled SQL query with keys SELECT, FROM, WHERE +		 * @since 3.3.0-b1 +		 */ +		$vars = [ +			'user_id', +			'reset_token', +			'sql_array', +		]; +		extract($this->dispatcher->trigger_event('core.ucp_reset_password_modify_select_sql', compact($vars))); + +		$sql = $this->db->sql_build_query('SELECT', $sql_array); +		$result = $this->db->sql_query_limit($sql, 1); +		$user_row = $this->db->sql_fetchrow($result); +		$this->db->sql_freeresult($result); + +		$message = $this->language->lang('RESET_TOKEN_EXPIRED_OR_INVALID') . '<br /><br />' . $this->language->lang('RETURN_INDEX', '<a href="' . append_sid("{$this->root_path}index.{$this->php_ext}") . '">', '</a>'); + +		if (empty($user_row)) +		{ +			return $this->helper->message($message); +		} + +		if (!hash_equals($reset_token, $user_row['reset_token'])) +		{ +			return $this->helper->message($message); +		} + +		if ($user_row['reset_token_expiration'] < time()) +		{ +			$this->remove_reset_token($user_id); + +			return $this->helper->message($message); +		} + +		$errors = []; + +		if ($submit) +		{ +			if (!check_form_key('ucp_reset_password')) +			{ +				return $this->helper->message('FORM_INVALID'); +			} + +			if ($user_row['user_type'] == USER_IGNORE || $user_row['user_type'] == USER_INACTIVE) +			{ +				return $this->helper->message($message); +			} + +			// Check users permissions +			$auth = new auth(); +			$auth->acl($user_row); + +			if (!$auth->acl_get('u_chgpasswd')) +			{ +				return $this->helper->message($message); +			} + +			if (!function_exists('validate_data')) +			{ +				include($this->root_path . 'includes/functions_user.' . $this->php_ext); +			} + +			$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'], 0], +					['password'], +				], +				'password_confirm'	=> ['string', true, $this->config['min_pass_chars'], 0], +			]; +			$errors = array_merge($errors, validate_data($data, $check_data)); +			if (strcmp($data['new_password'], $data['password_confirm']) !== 0) +			{ +				$errors[] = $data['password_confirm'] ? 'NEW_PASSWORD_ERROR' : 'NEW_PASSWORD_CONFIRM_EMPTY'; +			} +			if (empty($errors)) +			{ +				$sql_ary = [ +					'user_password'				=> $this->passwords_manager->hash($data['new_password']), +					'user_login_attempts'		=> 0, +					'reset_token'				=> '', +					'reset_token_expiration'	=> 0, +				]; +				$sql = 'UPDATE ' . $this->users_table . ' +							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}")); +				return $this->helper->message($this->language->lang('PASSWORD_RESET')); +			} +		} + +		if (!empty($errors)) +		{ +			$this->template->assign_block_vars_array('PASSWORD_RESET_ERRORS', array_map([$this->language, 'lang'], $errors)); +		} + +		$this->template->assign_vars([ +			'S_IS_PASSWORD_RESET'		=> true, +			'U_RESET_PASSWORD_ACTION'	=> $this->helper->route('phpbb_ucp_reset_password_controller'), +			'S_HIDDEN_FIELDS'			=> build_hidden_fields([ +				'u'		=> $user_id, +				'token'	=> $reset_token, +			]), +		]); + +		return $this->helper->render('ucp_reset_password.html', $this->language->lang('RESET_PASSWORD')); +	} +} | 
