Commit 0b8c5860 authored by alexpott's avatar alexpott

Issue #1987896 by tim.plunkett, kgoel, vijaycs85: Convert user_page() to a new style controller.

parent 4f8b8a50
......@@ -2529,8 +2529,8 @@ function install_configure_form_submit($form, &$form_state) {
$account->name = $form_state['values']['account']['name'];
$account->save();
// Load global $user and perform final login tasks.
$user = user_load(1);
user_login_finalize();
$account = user_load(1);
user_login_finalize($account);
// Record when this install ran.
variable_set('install_time', $_SERVER['REQUEST_TIME']);
......
......@@ -7,27 +7,38 @@
namespace Drupal\user\Controller;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\user\Form\UserLoginForm;
use Symfony\Component\DependencyInjection\ContainerAware;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\HttpFoundation\Request;
use Drupal\Core\Controller\ControllerInterface;
/**
* Controller routines for user routes.
*/
class UserController implements ControllerInterface {
class UserController extends ContainerAware {
/**
* Constructs an UserController object.
*/
public function __construct() {
}
/**
* {@inheritdoc}
* Returns the user page.
*
* Displays user profile if user is logged in, or login form for anonymous
* users.
*
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
*
* @return \Symfony\Component\HttpFoundation\RedirectResponse|array
* Returns either a redirect to the user page or the render
* array of the login form.
*/
public static function create(ContainerInterface $container) {
return new static();
public function userPage(Request $request) {
global $user;
if ($user->uid) {
$response = new RedirectResponse(url('user/' . $user->uid, array('absolute' => TRUE)));
}
else {
$response = drupal_get_form(UserLoginForm::create($this->container), $request);
}
return $response;
}
/**
......
<?php
/**
* @file
* Contains \Drupal\user\Form\UserLoginForm.
*/
namespace Drupal\user\Form;
use Drupal\Core\Config\ConfigFactory;
use Drupal\Core\Controller\ControllerInterface;
use Drupal\Core\Database\Connection;
use Drupal\Core\Flood\FloodInterface;
use Drupal\Core\Form\FormInterface;
use Drupal\user\UserStorageControllerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides a user login form.
*/
class UserLoginForm implements FormInterface, ControllerInterface {
/**
* The config factory.
*
* @var \Drupal\Core\Config\ConfigFactory
*/
protected $configFactory;
/**
* The request object.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* The flood service.
*
* @var \Drupal\Core\Flood\FloodInterface
*/
protected $flood;
/**
* The user storage controller.
*
* @var \Drupal\user\UserStorageControllerInterface
*/
protected $storageController;
/**
* Constructs a new UserLoginForm.
*
* @param \Drupal\Core\Config\ConfigFactory $config_factory
* The config factory.
* @param \Drupal\Core\Flood\FloodInterface $flood
* The flood service.
* @param \Drupal\user\UserStorageControllerInterface $storage_controller
* The user storage controller.
*/
public function __construct(ConfigFactory $config_factory, FloodInterface $flood, UserStorageControllerInterface $storage_controller) {
$this->configFactory = $config_factory;
$this->flood = $flood;
$this->storageController = $storage_controller;
}
/**
* {@inheritdoc}
*/
public static function create(ContainerInterface $container) {
return new static(
$container->get('config.factory'),
$container->get('flood'),
$container->get('plugin.manager.entity')->getStorageController('user')
);
}
/**
* {@inheritdoc}
*/
public function getFormID() {
return 'user_login_form';
}
/**
* {@inheritdoc}
*/
public function buildForm(array $form, array &$form_state, Request $request = NULL) {
$this->request = $request;
// Display login form:
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Username'),
'#size' => 60,
'#maxlength' => USERNAME_MAX_LENGTH,
'#description' => t('Enter your @s username.', array('@s' => $this->configFactory->get('system.site')->get('name'))),
'#required' => TRUE,
'#attributes' => array(
'autocorrect' => 'off',
'autocapitalize' => 'off',
'spellcheck' => 'false',
'autofocus' => 'autofocus',
),
);
$form['pass'] = array(
'#type' => 'password',
'#title' => t('Password'),
'#size' => 60,
'#description' => t('Enter the password that accompanies your username.'),
'#required' => TRUE,
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Log in'));
$form['#validate'][] = array($this, 'validateName');
$form['#validate'][] = array($this, 'validateAuthentication');
$form['#validate'][] = array($this, 'validateFinal');
return $form;
}
/**
* {@inheritdoc}
*/
public function validateForm(array &$form, array &$form_state) {
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, array &$form_state) {
$accounts = $this->storageController->load(array($form_state['uid']));
$account = reset($accounts)->getBCEntity();
$form_state['redirect'] = 'user/' . $account->id();
user_login_finalize($account);
}
/**
* Sets an error if supplied username has been blocked.
*/
public function validateName(array &$form, array &$form_state) {
if (!empty($form_state['values']['name']) && user_is_blocked($form_state['values']['name'])) {
// Blocked in user administration.
form_set_error('name', t('The username %name has not been activated or is blocked.', array('%name' => $form_state['values']['name'])));
}
}
/**
* Checks supplied username/password against local users table.
*
* If successful, $form_state['uid'] is set to the matching user ID.
*/
public function validateAuthentication(array &$form, array &$form_state) {
$password = trim($form_state['values']['pass']);
$flood_config = $this->configFactory->get('user.flood');
if (!empty($form_state['values']['name']) && !empty($password)) {
// Do not allow any login from the current user's IP if the limit has been
// reached. Default is 50 failed attempts allowed in one hour. This is
// independent of the per-user limit to catch attempts from one IP to log
// in to many different user accounts. We have a reasonably high limit
// since there may be only one apparent IP for all users at an institution.
if (!$this->flood->isAllowed('user.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
$form_state['flood_control_triggered'] = 'ip';
return;
}
$accounts = $this->storageController->loadByProperties(array('name' => $form_state['values']['name'], 'status' => 1));
$account = reset($accounts);
if ($account) {
if ($flood_config->get('uid_only')) {
// Register flood events based on the uid only, so they apply for any
// IP address. This is the most secure option.
$identifier = $account->id();
}
else {
// The default identifier is a combination of uid and IP address. This
// is less secure but more resistant to denial-of-service attacks that
// could lock out all users with public user names.
$identifier = $account->id() . '-' . $this->request->getClientIP();
}
$form_state['flood_control_user_identifier'] = $identifier;
// Don't allow login if the limit for this user has been reached.
// Default is to allow 5 failed attempts every 6 hours.
if (!$this->flood->isAllowed('user.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
$form_state['flood_control_triggered'] = 'user';
return;
}
}
// We are not limited by flood control, so try to authenticate.
// Set $form_state['uid'] as a flag for user_login_final_validate().
$form_state['uid'] = user_authenticate($form_state['values']['name'], $password);
}
}
/**
* Checks if user was not authenticated, or if too many logins were attempted.
*
* This validation function should always be the last one.
*/
public function validateFinal(array &$form, array &$form_state) {
$flood_config = $this->configFactory->get('user.flood');
if (empty($form_state['uid'])) {
// Always register an IP-based failed login event.
$this->flood->register('user.failed_login_ip', $flood_config->get('ip_window'));
// Register a per-user failed login event.
if (isset($form_state['flood_control_user_identifier'])) {
$this->flood->register('user.failed_login_user', $flood_config->get('user_window'), $form_state['flood_control_user_identifier']);
}
if (isset($form_state['flood_control_triggered'])) {
if ($form_state['flood_control_triggered'] == 'user') {
form_set_error('name', format_plural($flood_config->get('user_limit'), 'Sorry, there has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', 'Sorry, there have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => url('user/password'))));
}
else {
// We did not find a uid, so the limit is IP-based.
form_set_error('name', t('Sorry, too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => url('user/password'))));
}
}
else {
form_set_error('name', t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>', array('@password' => url('user/password', array('query' => array('name' => $form_state['values']['name']))))));
$accounts = $this->storageController->loadByProperties(array('name' => $form_state['values']['name']));
if (!empty($accounts)) {
watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name']));
}
else {
// If the username entered is not a valid user,
// only store the IP address.
watchdog('user', 'Login attempt failed from %ip.', array('%ip' => $this->request->getClientIp()));
}
}
}
elseif (isset($form_state['flood_control_user_identifier'])) {
// Clear past failures for this user so as not to block a user who might
// log in and out more than once in an hour.
$this->flood->clear('user.failed_login_user', $form_state['flood_control_user_identifier']);
}
}
}
......@@ -10,6 +10,11 @@
use Drupal\block\BlockBase;
use Drupal\Component\Annotation\Plugin;
use Drupal\Core\Annotation\Translation;
use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\user\Form\UserLoginForm;
use Symfony\Component\DependencyInjection\ContainerAwareInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpFoundation\Request;
/**
* Provides a 'User login' block.
......@@ -20,7 +25,55 @@
* module = "user"
* )
*/
class UserLoginBlock extends BlockBase {
class UserLoginBlock extends BlockBase implements ContainerFactoryPluginInterface {
/**
* The DI Container.
*
* @var \Symfony\Component\DependencyInjection\ContainerInterface
*/
protected $container;
/**
* The request object.
*
* @var \Symfony\Component\HttpFoundation\Request
*/
protected $request;
/**
* Constructs a new UserLoginBlock.
*
* @param array $configuration
* A configuration array containing information about the plugin instance.
* @param string $plugin_id
* The plugin ID for the plugin instance.
* @param array $plugin_definition
* The plugin implementation definition.
* @param \Symfony\Component\DependencyInjection\ContainerInterface $container
* The DI Container.
* @param \Symfony\Component\HttpFoundation\Request $request
* The request object.
*/
public function __construct(array $configuration, $plugin_id, array $plugin_definition, ContainerInterface $container, Request $request) {
parent::__construct($configuration, $plugin_id, $plugin_definition);
$this->container = $container;
$this->request = $request;
}
/**
* {@inheritdo}
*/
public static function create(ContainerInterface $container, array $configuration, $plugin_id, array $plugin_definition) {
return new static(
$configuration,
$plugin_id,
$plugin_definition,
$container,
$container->get('request')
);
}
/**
* Overrides \Drupal\block\BlockBase::access().
......@@ -33,7 +86,7 @@ public function access() {
* {@inheritdoc}
*/
public function build() {
$form = drupal_get_form('user_login_form');
$form = drupal_get_form(UserLoginForm::create($this->container), $this->request);
unset($form['name']['#attributes']['autofocus']);
unset($form['name']['#description']);
unset($form['pass']['#description']);
......
......@@ -120,8 +120,7 @@ public function save(array $form, array &$form_state) {
// No e-mail verification required; log in user immediately.
elseif (!$admin && !config('user.settings')->get('verify_mail') && $account->status) {
_user_mail_notify('register_no_approval_required', $account);
$form_state['uid'] = $account->uid;
user_login_form_submit(array(), $form_state);
user_login_finalize($account);
drupal_set_message(t('Registration successful. You are now logged in.'));
$form_state['redirect'] = '';
}
......
......@@ -850,16 +850,13 @@ function user_menu() {
$items['user'] = array(
'title' => 'User account',
'title callback' => 'user_menu_title',
'page callback' => 'user_page',
'access callback' => TRUE,
'file' => 'user.pages.inc',
'weight' => -10,
'route_name' => 'user_page',
'menu_name' => 'account',
);
$items['user/login'] = array(
'title' => 'Log in',
'access callback' => 'user_is_anonymous',
'type' => MENU_DEFAULT_LOCAL_TASK,
);
// Other authentication methods may add pages below user/login/.
......@@ -1150,44 +1147,6 @@ function user_page_title($account) {
return is_object($account) ? user_format_name($account) : '';
}
/**
* Form builder; the main user login form.
*
* @ingroup forms
*/
function user_login_form($form, &$form_state) {
// Display login form:
$form['name'] = array(
'#type' => 'textfield',
'#title' => t('Username'),
'#size' => 60,
'#maxlength' => USERNAME_MAX_LENGTH,
'#description' => t('Enter your @s username.', array('@s' => config('system.site')->get('name'))),
'#required' => TRUE,
'#attributes' => array(
'autocorrect' => 'off',
'autocapitalize' => 'off',
'spellcheck' => 'false',
'autofocus' => 'autofocus',
),
);
$form['pass'] = array(
'#type' => 'password',
'#title' => t('Password'),
'#size' => 60,
'#description' => t('Enter the password that accompanies your username.'),
'#required' => TRUE,
);
$form['actions'] = array('#type' => 'actions');
$form['actions']['submit'] = array('#type' => 'submit', '#value' => t('Log in'));
$form['#validate'] = user_login_default_validators();
return $form;
}
/**
* Set up a series for validators which check for blocked users,
* then authenticate against local database, then return an error if
......@@ -1210,109 +1169,6 @@ function user_login_default_validators() {
return array('user_login_name_validate', 'user_login_authenticate_validate', 'user_login_final_validate');
}
/**
* A FAPI validate handler. Sets an error if supplied username has been blocked.
*/
function user_login_name_validate($form, &$form_state) {
if (!empty($form_state['values']['name']) && user_is_blocked($form_state['values']['name'])) {
// Blocked in user administration.
form_set_error('name', t('The username %name has not been activated or is blocked.', array('%name' => $form_state['values']['name'])));
}
}
/**
* A validate handler on the login form. Check supplied username/password
* against local users table. If successful, $form_state['uid']
* is set to the matching user ID.
*/
function user_login_authenticate_validate($form, &$form_state) {
$password = trim($form_state['values']['pass']);
$flood_config = config('user.flood');
$flood = Drupal::service('flood');
if (!empty($form_state['values']['name']) && !empty($password)) {
// Do not allow any login from the current user's IP if the limit has been
// reached. Default is 50 failed attempts allowed in one hour. This is
// independent of the per-user limit to catch attempts from one IP to log
// in to many different user accounts. We have a reasonably high limit
// since there may be only one apparent IP for all users at an institution.
if (!$flood->isAllowed('user.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
$form_state['flood_control_triggered'] = 'ip';
return;
}
$account = db_query("SELECT * FROM {users} WHERE name = :name AND status = 1", array(':name' => $form_state['values']['name']))->fetchObject();
if ($account) {
if ($flood_config->get('uid_only')) {
// Register flood events based on the uid only, so they apply for any
// IP address. This is the most secure option.
$identifier = $account->uid;
}
else {
// The default identifier is a combination of uid and IP address. This
// is less secure but more resistant to denial-of-service attacks that
// could lock out all users with public user names.
$identifier = $account->uid . '-' . Drupal::request()->getClientIP();
}
$form_state['flood_control_user_identifier'] = $identifier;
// Don't allow login if the limit for this user has been reached.
// Default is to allow 5 failed attempts every 6 hours.
if (!$flood->isAllowed('user.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
$form_state['flood_control_triggered'] = 'user';
return;
}
}
// We are not limited by flood control, so try to authenticate.
// Set $form_state['uid'] as a flag for user_login_final_validate().
$form_state['uid'] = user_authenticate($form_state['values']['name'], $password);
}
}
/**
* The final validation handler on the login form.
*
* Sets a form error if user has not been authenticated, or if too many
* logins have been attempted. This validation function should always
* be the last one.
*/
function user_login_final_validate($form, &$form_state) {
$flood_config = config('user.flood');
$flood = Drupal::service('flood');
if (empty($form_state['uid'])) {
// Always register an IP-based failed login event.
$flood->register('user.failed_login_ip', $flood_config->get('ip_window'));
// Register a per-user failed login event.
if (isset($form_state['flood_control_user_identifier'])) {
$flood->register('user.failed_login_user', $flood_config->get('user_window'), $form_state['flood_control_user_identifier']);
}
if (isset($form_state['flood_control_triggered'])) {
if ($form_state['flood_control_triggered'] == 'user') {
form_set_error('name', format_plural($flood_config->get('user_limit'), 'Sorry, there has been more than one failed login attempt for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', 'Sorry, there have been more than @count failed login attempts for this account. It is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => url('user/password'))));
}
else {
// We did not find a uid, so the limit is IP-based.
form_set_error('name', t('Sorry, too many failed login attempts from your IP address. This IP address is temporarily blocked. Try again later or <a href="@url">request a new password</a>.', array('@url' => url('user/password'))));
}
}
else {
form_set_error('name', t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>', array('@password' => url('user/password', array('query' => array('name' => $form_state['values']['name']))))));
if (user_load_by_name($form_state['values']['name'])) {
watchdog('user', 'Login attempt failed for %user.', array('%user' => $form_state['values']['name']));
}
else {
// If the username entered is not a valid user,
// only store the IP address.
watchdog('user', 'Login attempt failed from %ip.', array('%ip' => Drupal::request()->getClientIp()));
}
}
}
elseif (isset($form_state['flood_control_user_identifier'])) {
// Clear past failures for this user so as not to block a user who might
// log in and out more than once in an hour.
$flood->clear('user.failed_login_user', $form_state['flood_control_user_identifier']);
}
}
/**
* Try to validate the user's login credentials locally.
*
......@@ -1345,18 +1201,22 @@ function user_authenticate($name, $password) {
}
/**
* Finalize the login process. Must be called when logging in a user.
* Finalizes the login process and logs in a user.
*
* The function records a watchdog message about the new session, saves the
* login timestamp, calls hook_user_login(), and generates a new session.
* The function logs in the user, records a watchdog message about the new
* session, saves the login timestamp, calls hook_user_login(), and generates a
* new session.
*
* @param array $edit
* The array of form values submitted by the user.
* The global $user object is replaced with the passed in account.
*
* @param \Drupal\Core\Session\AccountInterface $account
* The account to log in.
*
* @see hook_user_login()
*/
function user_login_finalize(&$edit = array()) {
function user_login_finalize(AccountInterface $account) {
global $user;
$user = $account;
watchdog('user', 'Session opened for %name.', array('%name' => $user->name));
// Update the user table timestamp noting user has logged in.
// This is also used to invalidate one-time login links.
......@@ -1374,19 +1234,6 @@ function user_login_finalize(&$edit = array()) {
module_invoke_all('user_login', $user);
}
/**
* Submit handler for the login form. Load $user object and perform standard login
* tasks. The user is then redirected to the My Account page. Setting the
* destination in the query string overrides the redirect.
*/
function user_login_form_submit($form, &$form_state) {
global $user;
$user = user_load($form_state['uid']);
$form_state['redirect'] = 'user/' . $user->uid;
user_login_finalize($form_state);
}
/**
* Implements hook_user_login().
*/
......@@ -2329,21 +2176,6 @@ function user_modules_uninstalled($modules) {
drupal_container()->get('user.data')->delete($modules);
}
/**
* Helper function to rewrite the destination to avoid redirecting to login page after login.
*
* Third-party authentication modules may use this function to determine the
* proper destination after a user has been properly logged in.
*/
function user_login_destination() {
$destination = drupal_get_destination();
// Modules may provide login pages under the "user/login/" path prefix.
if (preg_match('@^user/login(/.*|)$@', $destination['destination'])) {
$destination['destination'] = 'user';
}
return $destination;
}
/**
* Saves visitor information as a cookie so it can be reused.
*
......
......@@ -53,10 +53,9 @@ function user_pass_reset($form, &$form_state, $uid, $timestamp, $hashed_pass, $a
// First stage is a confirmation form, then login
if ($action == 'login') {
// Set the new user.
$user = $account;
// user_login_finalize() also updates the login timestamp of the
// user, which invalidates further use of the one-time login link.
user_login_finalize();
user_login_finalize($account);
watchdog('user', 'User %name used one-time login link at time %timestamp.', array('%name' => $account->name, '%timestamp' => $timestamp));
drupal_set_message(t('You have just used your one-time login link. It is no longer necessary to use this link to log in. Please change your password.'));
// Let the user's password be changed without the current password check.
......@@ -324,19 +323,3 @@ function user_cancel_confirm($account, $timestamp = 0, $hashed_pass = '') {
}
throw new AccessDeniedHttpException();
}
/**
* Access callback for path /user.
*
* Displays user profile if user is logged in, or login form for anonymous
* users.
*/
function user_page() {
global $user;
if ($user->uid) {
return new RedirectResponse(url('user/' . $user->uid, array('absolute' => TRUE)));
}
else {
return drupal_get_form('user_login_form');
}
}
......@@ -74,3 +74,17 @@ user_pass:
_form: '\Drupal\user\Form\UserPasswordForm'
requirements:
_access: 'TRUE'
user_page:
pattern: '/user'
defaults:
_content: '\Drupal\user\Controller\UserController::userPage'
requirements:
_access: 'TRUE'
user_login:
pattern: '/user/login'
defaults:
_form: '\Drupal\user\Form\UserLoginForm'
requirements:
_access: 'TRUE'
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment