PageRenderTime 27ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/web/core/modules/user/src/Controller/UserAuthenticationController.php

https://gitlab.com/mohamed_hussein/prodt
PHP | 411 lines | 200 code | 43 blank | 168 comment | 33 complexity | 6ce727bccfd06566cdf75782710c4072 MD5 | raw file
  1. <?php
  2. namespace Drupal\user\Controller;
  3. use Drupal\Core\Access\CsrfTokenGenerator;
  4. use Drupal\Core\Controller\ControllerBase;
  5. use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
  6. use Drupal\Core\Routing\RouteProviderInterface;
  7. use Drupal\user\UserAuthInterface;
  8. use Drupal\user\UserFloodControlInterface;
  9. use Drupal\user\UserInterface;
  10. use Drupal\user\UserStorageInterface;
  11. use Psr\Log\LoggerInterface;
  12. use Symfony\Component\DependencyInjection\ContainerInterface;
  13. use Symfony\Component\HttpFoundation\Request;
  14. use Symfony\Component\HttpFoundation\Response;
  15. use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
  16. use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
  17. use Symfony\Component\Serializer\Encoder\JsonEncoder;
  18. use Symfony\Component\Serializer\Serializer;
  19. /**
  20. * Provides controllers for login, login status and logout via HTTP requests.
  21. */
  22. class UserAuthenticationController extends ControllerBase implements ContainerInjectionInterface {
  23. /**
  24. * String sent in responses, to describe the user as being logged in.
  25. *
  26. * @var string
  27. */
  28. const LOGGED_IN = 1;
  29. /**
  30. * String sent in responses, to describe the user as being logged out.
  31. *
  32. * @var string
  33. */
  34. const LOGGED_OUT = 0;
  35. /**
  36. * The user flood control service.
  37. *
  38. * @var \Drupal\user\UserFloodControl
  39. */
  40. protected $userFloodControl;
  41. /**
  42. * The user storage.
  43. *
  44. * @var \Drupal\user\UserStorageInterface
  45. */
  46. protected $userStorage;
  47. /**
  48. * The CSRF token generator.
  49. *
  50. * @var \Drupal\Core\Access\CsrfTokenGenerator
  51. */
  52. protected $csrfToken;
  53. /**
  54. * The user authentication.
  55. *
  56. * @var \Drupal\user\UserAuthInterface
  57. */
  58. protected $userAuth;
  59. /**
  60. * The route provider.
  61. *
  62. * @var \Drupal\Core\Routing\RouteProviderInterface
  63. */
  64. protected $routeProvider;
  65. /**
  66. * The serializer.
  67. *
  68. * @var \Symfony\Component\Serializer\Serializer
  69. */
  70. protected $serializer;
  71. /**
  72. * The available serialization formats.
  73. *
  74. * @var array
  75. */
  76. protected $serializerFormats = [];
  77. /**
  78. * A logger instance.
  79. *
  80. * @var \Psr\Log\LoggerInterface
  81. */
  82. protected $logger;
  83. /**
  84. * Constructs a new UserAuthenticationController object.
  85. *
  86. * @param \Drupal\user\UserFloodControlInterface $user_flood_control
  87. * The user flood control service.
  88. * @param \Drupal\user\UserStorageInterface $user_storage
  89. * The user storage.
  90. * @param \Drupal\Core\Access\CsrfTokenGenerator $csrf_token
  91. * The CSRF token generator.
  92. * @param \Drupal\user\UserAuthInterface $user_auth
  93. * The user authentication.
  94. * @param \Drupal\Core\Routing\RouteProviderInterface $route_provider
  95. * The route provider.
  96. * @param \Symfony\Component\Serializer\Serializer $serializer
  97. * The serializer.
  98. * @param array $serializer_formats
  99. * The available serialization formats.
  100. * @param \Psr\Log\LoggerInterface $logger
  101. * A logger instance.
  102. */
  103. public function __construct($user_flood_control, UserStorageInterface $user_storage, CsrfTokenGenerator $csrf_token, UserAuthInterface $user_auth, RouteProviderInterface $route_provider, Serializer $serializer, array $serializer_formats, LoggerInterface $logger) {
  104. if (!$user_flood_control instanceof UserFloodControlInterface) {
  105. @trigger_error('Passing the flood service to ' . __METHOD__ . ' is deprecated in drupal:9.1.0 and is replaced by user.flood_control in drupal:10.0.0. See https://www.drupal.org/node/3067148', E_USER_DEPRECATED);
  106. $user_flood_control = \Drupal::service('user.flood_control');
  107. }
  108. $this->userFloodControl = $user_flood_control;
  109. $this->userStorage = $user_storage;
  110. $this->csrfToken = $csrf_token;
  111. $this->userAuth = $user_auth;
  112. $this->serializer = $serializer;
  113. $this->serializerFormats = $serializer_formats;
  114. $this->routeProvider = $route_provider;
  115. $this->logger = $logger;
  116. }
  117. /**
  118. * {@inheritdoc}
  119. */
  120. public static function create(ContainerInterface $container) {
  121. if ($container->hasParameter('serializer.formats') && $container->has('serializer')) {
  122. $serializer = $container->get('serializer');
  123. $formats = $container->getParameter('serializer.formats');
  124. }
  125. else {
  126. $formats = ['json'];
  127. $encoders = [new JsonEncoder()];
  128. $serializer = new Serializer([], $encoders);
  129. }
  130. return new static(
  131. $container->get('user.flood_control'),
  132. $container->get('entity_type.manager')->getStorage('user'),
  133. $container->get('csrf_token'),
  134. $container->get('user.auth'),
  135. $container->get('router.route_provider'),
  136. $serializer,
  137. $formats,
  138. $container->get('logger.factory')->get('user')
  139. );
  140. }
  141. /**
  142. * Logs in a user.
  143. *
  144. * @param \Symfony\Component\HttpFoundation\Request $request
  145. * The request.
  146. *
  147. * @return \Symfony\Component\HttpFoundation\Response
  148. * A response which contains the ID and CSRF token.
  149. */
  150. public function login(Request $request) {
  151. $format = $this->getRequestFormat($request);
  152. $content = $request->getContent();
  153. $credentials = $this->serializer->decode($content, $format);
  154. if (!isset($credentials['name']) && !isset($credentials['pass'])) {
  155. throw new BadRequestHttpException('Missing credentials.');
  156. }
  157. if (!isset($credentials['name'])) {
  158. throw new BadRequestHttpException('Missing credentials.name.');
  159. }
  160. if (!isset($credentials['pass'])) {
  161. throw new BadRequestHttpException('Missing credentials.pass.');
  162. }
  163. $this->floodControl($request, $credentials['name']);
  164. if ($this->userIsBlocked($credentials['name'])) {
  165. throw new BadRequestHttpException('The user has not been activated or is blocked.');
  166. }
  167. if ($uid = $this->userAuth->authenticate($credentials['name'], $credentials['pass'])) {
  168. $this->userFloodControl->clear('user.http_login', $this->getLoginFloodIdentifier($request, $credentials['name']));
  169. /** @var \Drupal\user\UserInterface $user */
  170. $user = $this->userStorage->load($uid);
  171. $this->userLoginFinalize($user);
  172. // Send basic metadata about the logged in user.
  173. $response_data = [];
  174. if ($user->get('uid')->access('view', $user)) {
  175. $response_data['current_user']['uid'] = $user->id();
  176. }
  177. if ($user->get('roles')->access('view', $user)) {
  178. $response_data['current_user']['roles'] = $user->getRoles();
  179. }
  180. if ($user->get('name')->access('view', $user)) {
  181. $response_data['current_user']['name'] = $user->getAccountName();
  182. }
  183. $response_data['csrf_token'] = $this->csrfToken->get('rest');
  184. $logout_route = $this->routeProvider->getRouteByName('user.logout.http');
  185. // Trim '/' off path to match \Drupal\Core\Access\CsrfAccessCheck.
  186. $logout_path = ltrim($logout_route->getPath(), '/');
  187. $response_data['logout_token'] = $this->csrfToken->get($logout_path);
  188. $encoded_response_data = $this->serializer->encode($response_data, $format);
  189. return new Response($encoded_response_data);
  190. }
  191. $flood_config = $this->config('user.flood');
  192. if ($identifier = $this->getLoginFloodIdentifier($request, $credentials['name'])) {
  193. $this->userFloodControl->register('user.http_login', $flood_config->get('user_window'), $identifier);
  194. }
  195. // Always register an IP-based failed login event.
  196. $this->userFloodControl->register('user.failed_login_ip', $flood_config->get('ip_window'));
  197. throw new BadRequestHttpException('Sorry, unrecognized username or password.');
  198. }
  199. /**
  200. * Resets a user password.
  201. *
  202. * @param \Symfony\Component\HttpFoundation\Request $request
  203. * The request.
  204. *
  205. * @return \Symfony\Component\HttpFoundation\Response
  206. * The response object.
  207. */
  208. public function resetPassword(Request $request) {
  209. $format = $this->getRequestFormat($request);
  210. $content = $request->getContent();
  211. $credentials = $this->serializer->decode($content, $format);
  212. // Check if a name or mail is provided.
  213. if (!isset($credentials['name']) && !isset($credentials['mail'])) {
  214. throw new BadRequestHttpException('Missing credentials.name or credentials.mail');
  215. }
  216. // Load by name if provided.
  217. if (isset($credentials['name'])) {
  218. $users = $this->userStorage->loadByProperties(['name' => trim($credentials['name'])]);
  219. }
  220. elseif (isset($credentials['mail'])) {
  221. $users = $this->userStorage->loadByProperties(['mail' => trim($credentials['mail'])]);
  222. }
  223. /** @var \Drupal\Core\Session\AccountInterface $account */
  224. $account = reset($users);
  225. if ($account && $account->id()) {
  226. if ($this->userIsBlocked($account->getAccountName())) {
  227. throw new BadRequestHttpException('The user has not been activated or is blocked.');
  228. }
  229. // Send the password reset email.
  230. $mail = _user_mail_notify('password_reset', $account);
  231. if (empty($mail)) {
  232. throw new BadRequestHttpException('Unable to send email. Contact the site administrator if the problem persists.');
  233. }
  234. else {
  235. $this->logger->notice('Password reset instructions mailed to %name at %email.', ['%name' => $account->getAccountName(), '%email' => $account->getEmail()]);
  236. return new Response();
  237. }
  238. }
  239. // Error if no users found with provided name or mail.
  240. throw new BadRequestHttpException('Unrecognized username or email address.');
  241. }
  242. /**
  243. * Verifies if the user is blocked.
  244. *
  245. * @param string $name
  246. * The username.
  247. *
  248. * @return bool
  249. * TRUE if the user is blocked, otherwise FALSE.
  250. */
  251. protected function userIsBlocked($name) {
  252. return user_is_blocked($name);
  253. }
  254. /**
  255. * Finalizes the user login.
  256. *
  257. * @param \Drupal\user\UserInterface $user
  258. * The user.
  259. */
  260. protected function userLoginFinalize(UserInterface $user) {
  261. user_login_finalize($user);
  262. }
  263. /**
  264. * Logs out a user.
  265. *
  266. * @return \Symfony\Component\HttpFoundation\Response
  267. * The response object.
  268. */
  269. public function logout() {
  270. $this->userLogout();
  271. return new Response(NULL, 204);
  272. }
  273. /**
  274. * Logs the user out.
  275. */
  276. protected function userLogout() {
  277. user_logout();
  278. }
  279. /**
  280. * Checks whether a user is logged in or not.
  281. *
  282. * @return \Symfony\Component\HttpFoundation\Response
  283. * The response.
  284. */
  285. public function loginStatus() {
  286. if ($this->currentUser()->isAuthenticated()) {
  287. $response = new Response(self::LOGGED_IN);
  288. }
  289. else {
  290. $response = new Response(self::LOGGED_OUT);
  291. }
  292. $response->headers->set('Content-Type', 'text/plain');
  293. return $response;
  294. }
  295. /**
  296. * Gets the format of the current request.
  297. *
  298. * @param \Symfony\Component\HttpFoundation\Request $request
  299. * The current request.
  300. *
  301. * @return string
  302. * The format of the request.
  303. */
  304. protected function getRequestFormat(Request $request) {
  305. $format = $request->getRequestFormat();
  306. if (!in_array($format, $this->serializerFormats)) {
  307. throw new BadRequestHttpException("Unrecognized format: $format.");
  308. }
  309. return $format;
  310. }
  311. /**
  312. * Enforces flood control for the current login request.
  313. *
  314. * @param \Symfony\Component\HttpFoundation\Request $request
  315. * The current request.
  316. * @param string $username
  317. * The user name sent for login credentials.
  318. */
  319. protected function floodControl(Request $request, $username) {
  320. $flood_config = $this->config('user.flood');
  321. if (!$this->userFloodControl->isAllowed('user.failed_login_ip', $flood_config->get('ip_limit'), $flood_config->get('ip_window'))) {
  322. throw new AccessDeniedHttpException('Access is blocked because of IP based flood prevention.', NULL, Response::HTTP_TOO_MANY_REQUESTS);
  323. }
  324. if ($identifier = $this->getLoginFloodIdentifier($request, $username)) {
  325. // Don't allow login if the limit for this user has been reached.
  326. // Default is to allow 5 failed attempts every 6 hours.
  327. if (!$this->userFloodControl->isAllowed('user.http_login', $flood_config->get('user_limit'), $flood_config->get('user_window'), $identifier)) {
  328. if ($flood_config->get('uid_only')) {
  329. $error_message = sprintf('There have been more than %s failed login attempts for this account. It is temporarily blocked. Try again later or request a new password.', $flood_config->get('user_limit'));
  330. }
  331. else {
  332. $error_message = 'Too many failed login attempts from your IP address. This IP address is temporarily blocked.';
  333. }
  334. throw new AccessDeniedHttpException($error_message, NULL, Response::HTTP_TOO_MANY_REQUESTS);
  335. }
  336. }
  337. }
  338. /**
  339. * Gets the login identifier for user login flood control.
  340. *
  341. * @param \Symfony\Component\HttpFoundation\Request $request
  342. * The current request.
  343. * @param string $username
  344. * The username supplied in login credentials.
  345. *
  346. * @return string
  347. * The login identifier or if the user does not exist an empty string.
  348. */
  349. protected function getLoginFloodIdentifier(Request $request, $username) {
  350. $flood_config = $this->config('user.flood');
  351. $accounts = $this->userStorage->loadByProperties(['name' => $username, 'status' => 1]);
  352. if ($account = reset($accounts)) {
  353. if ($flood_config->get('uid_only')) {
  354. // Register flood events based on the uid only, so they apply for any
  355. // IP address. This is the most secure option.
  356. $identifier = $account->id();
  357. }
  358. else {
  359. // The default identifier is a combination of uid and IP address. This
  360. // is less secure but more resistant to denial-of-service attacks that
  361. // could lock out all users with public user names.
  362. $identifier = $account->id() . '-' . $request->getClientIp();
  363. }
  364. return $identifier;
  365. }
  366. return '';
  367. }
  368. }