25a54ec4b72371c49ee270429b2d7a3b17efb367
[yaffs-website] / web / core / modules / user / src / Form / UserLoginForm.php
1 <?php
2
3 namespace Drupal\user\Form;
4
5 use Drupal\Core\Flood\FloodInterface;
6 use Drupal\Core\Form\FormBase;
7 use Drupal\Core\Form\FormStateInterface;
8 use Drupal\Core\Render\RendererInterface;
9 use Drupal\user\UserAuthInterface;
10 use Drupal\user\UserStorageInterface;
11 use Symfony\Component\DependencyInjection\ContainerInterface;
12
13 /**
14  * Provides a user login form.
15  */
16 class UserLoginForm extends FormBase {
17
18   /**
19    * The flood service.
20    *
21    * @var \Drupal\Core\Flood\FloodInterface
22    */
23   protected $flood;
24
25   /**
26    * The user storage.
27    *
28    * @var \Drupal\user\UserStorageInterface
29    */
30   protected $userStorage;
31
32   /**
33    * The user authentication object.
34    *
35    * @var \Drupal\user\UserAuthInterface
36    */
37   protected $userAuth;
38
39   /**
40    * The renderer.
41    *
42    * @var \Drupal\Core\Render\RendererInterface
43    */
44   protected $renderer;
45
46   /**
47    * Constructs a new UserLoginForm.
48    *
49    * @param \Drupal\Core\Flood\FloodInterface $flood
50    *   The flood service.
51    * @param \Drupal\user\UserStorageInterface $user_storage
52    *   The user storage.
53    * @param \Drupal\user\UserAuthInterface $user_auth
54    *   The user authentication object.
55    * @param \Drupal\Core\Render\RendererInterface $renderer
56    *   The renderer.
57    */
58   public function __construct(FloodInterface $flood, UserStorageInterface $user_storage, UserAuthInterface $user_auth, RendererInterface $renderer) {
59     $this->flood = $flood;
60     $this->userStorage = $user_storage;
61     $this->userAuth = $user_auth;
62     $this->renderer = $renderer;
63   }
64
65   /**
66    * {@inheritdoc}
67    */
68   public static function create(ContainerInterface $container) {
69     return new static(
70       $container->get('flood'),
71       $container->get('entity.manager')->getStorage('user'),
72       $container->get('user.auth'),
73       $container->get('renderer')
74     );
75   }
76
77   /**
78    * {@inheritdoc}
79    */
80   public function getFormId() {
81     return 'user_login_form';
82   }
83
84   /**
85    * {@inheritdoc}
86    */
87   public function buildForm(array $form, FormStateInterface $form_state) {
88     $config = $this->config('system.site');
89
90     // Display login form:
91     $form['name'] = [
92       '#type' => 'textfield',
93       '#title' => $this->t('Username'),
94       '#size' => 60,
95       '#maxlength' => USERNAME_MAX_LENGTH,
96       '#description' => $this->t('Enter your @s username.', ['@s' => $config->get('name')]),
97       '#required' => TRUE,
98       '#attributes' => [
99         'autocorrect' => 'none',
100         'autocapitalize' => 'none',
101         'spellcheck' => 'false',
102         'autofocus' => 'autofocus',
103       ],
104     ];
105
106     $form['pass'] = [
107       '#type' => 'password',
108       '#title' => $this->t('Password'),
109       '#size' => 60,
110       '#description' => $this->t('Enter the password that accompanies your username.'),
111       '#required' => TRUE,
112     ];
113
114     $form['actions'] = ['#type' => 'actions'];
115     $form['actions']['submit'] = ['#type' => 'submit', '#value' => $this->t('Log in')];
116
117     $form['#validate'][] = '::validateName';
118     $form['#validate'][] = '::validateAuthentication';
119     $form['#validate'][] = '::validateFinal';
120
121     $this->renderer->addCacheableDependency($form, $config);
122
123     return $form;
124   }
125
126   /**
127    * {@inheritdoc}
128    */
129   public function submitForm(array &$form, FormStateInterface $form_state) {
130     $account = $this->userStorage->load($form_state->get('uid'));
131
132     // A destination was set, probably on an exception controller,
133     if (!$this->getRequest()->request->has('destination')) {
134       $form_state->setRedirect(
135         'entity.user.canonical',
136         ['user' => $account->id()]
137       );
138     }
139     else {
140       $this->getRequest()->query->set('destination', $this->getRequest()->request->get('destination'));
141     }
142
143     user_login_finalize($account);
144   }
145
146   /**
147    * Sets an error if supplied username has been blocked.
148    */
149   public function validateName(array &$form, FormStateInterface $form_state) {
150     if (!$form_state->isValueEmpty('name') && user_is_blocked($form_state->getValue('name'))) {
151       // Blocked in user administration.
152       $form_state->setErrorByName('name', $this->t('The username %name has not been activated or is blocked.', ['%name' => $form_state->getValue('name')]));
153     }
154   }
155
156   /**
157    * Checks supplied username/password against local users table.
158    *
159    * If successful, $form_state->get('uid') is set to the matching user ID.
160    */
161   public function validateAuthentication(array &$form, FormStateInterface $form_state) {
162     $password = trim($form_state->getValue('pass'));
163     $flood_config = $this->config('user.flood');
164     if (!$form_state->isValueEmpty('name') && strlen($password) > 0) {
165       // Do not allow any login from the current user's IP if the limit has been
166       // reached. Default is 50 failed attempts allowed in one hour. This is
167       // independent of the per-user limit to catch attempts from one IP to log
168       // in to many different user accounts.  We have a reasonably high limit
169       // since there may be only one apparent IP for all users at an institution.
170       if (!$this->flood->isAllowed('user.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
171         $form_state->set('flood_control_triggered', 'ip');
172         return;
173       }
174       $accounts = $this->userStorage->loadByProperties(['name' => $form_state->getValue('name'), 'status' => 1]);
175       $account = reset($accounts);
176       if ($account) {
177         if ($flood_config->get('uid_only')) {
178           // Register flood events based on the uid only, so they apply for any
179           // IP address. This is the most secure option.
180           $identifier = $account->id();
181         }
182         else {
183           // The default identifier is a combination of uid and IP address. This
184           // is less secure but more resistant to denial-of-service attacks that
185           // could lock out all users with public user names.
186           $identifier = $account->id() . '-' . $this->getRequest()->getClientIP();
187         }
188         $form_state->set('flood_control_user_identifier', $identifier);
189
190         // Don't allow login if the limit for this user has been reached.
191         // Default is to allow 5 failed attempts every 6 hours.
192         if (!$this->flood->isAllowed('user.failed_login_user', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
193           $form_state->set('flood_control_triggered', 'user');
194           return;
195         }
196       }
197       // We are not limited by flood control, so try to authenticate.
198       // Store $uid in form state as a flag for self::validateFinal().
199       $uid = $this->userAuth->authenticate($form_state->getValue('name'), $password);
200       $form_state->set('uid', $uid);
201     }
202   }
203
204   /**
205    * Checks if user was not authenticated, or if too many logins were attempted.
206    *
207    * This validation function should always be the last one.
208    */
209   public function validateFinal(array &$form, FormStateInterface $form_state) {
210     $flood_config = $this->config('user.flood');
211     if (!$form_state->get('uid')) {
212       // Always register an IP-based failed login event.
213       $this->flood->register('user.failed_login_ip', $flood_config->get('ip_window'));
214       // Register a per-user failed login event.
215       if ($flood_control_user_identifier = $form_state->get('flood_control_user_identifier')) {
216         $this->flood->register('user.failed_login_user', $flood_config->get('user_window'), $flood_control_user_identifier);
217       }
218
219       if ($flood_control_triggered = $form_state->get('flood_control_triggered')) {
220         if ($flood_control_triggered == 'user') {
221           $form_state->setErrorByName('name', $this->formatPlural($flood_config->get('user_limit'), '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>.', '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>.', [':url' => $this->url('user.pass')]));
222         }
223         else {
224           // We did not find a uid, so the limit is IP-based.
225           $form_state->setErrorByName('name', $this->t('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>.', [':url' => $this->url('user.pass')]));
226         }
227       }
228       else {
229         // Use $form_state->getUserInput() in the error message to guarantee
230         // that we send exactly what the user typed in. The value from
231         // $form_state->getValue() may have been modified by validation
232         // handlers that ran earlier than this one.
233         $user_input = $form_state->getUserInput();
234         $query = isset($user_input['name']) ? ['name' => $user_input['name']] : [];
235         $form_state->setErrorByName('name', $this->t('Unrecognized username or password. <a href=":password">Forgot your password?</a>', [':password' => $this->url('user.pass', [], ['query' => $query])]));
236         $accounts = $this->userStorage->loadByProperties(['name' => $form_state->getValue('name')]);
237         if (!empty($accounts)) {
238           $this->logger('user')->notice('Login attempt failed for %user.', ['%user' => $form_state->getValue('name')]);
239         }
240         else {
241           // If the username entered is not a valid user,
242           // only store the IP address.
243           $this->logger('user')->notice('Login attempt failed from %ip.', ['%ip' => $this->getRequest()->getClientIp()]);
244         }
245       }
246     }
247     elseif ($flood_control_user_identifier = $form_state->get('flood_control_user_identifier')) {
248       // Clear past failures for this user so as not to block a user who might
249       // log in and out more than once in an hour.
250       $this->flood->clear('user.failed_login_user', $flood_control_user_identifier);
251     }
252   }
253
254 }