Login.php 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834
  1. <?php
  2. /**
  3. * @package Grav\Plugin\Login
  4. *
  5. * @copyright Copyright (C) 2014 - 2021 RocketTheme, LLC. All rights reserved.
  6. * @license MIT License; see LICENSE file for details.
  7. */
  8. namespace Grav\Plugin\Login;
  9. use Birke\Rememberme\Cookie;
  10. use Grav\Common\Config\Config;
  11. use Grav\Common\Data\Data;
  12. use Grav\Common\Debugger;
  13. use Grav\Common\Grav;
  14. use Grav\Common\Language\Language;
  15. use Grav\Common\Language\LanguageCodes;
  16. use Grav\Common\Page\Interfaces\PageInterface;
  17. use Grav\Common\Page\Page;
  18. use Grav\Common\Page\Pages;
  19. use Grav\Common\Session;
  20. use Grav\Common\User\Interfaces\UserCollectionInterface;
  21. use Grav\Common\User\Interfaces\UserInterface;
  22. use Grav\Common\Uri;
  23. use Grav\Common\Utils;
  24. use Grav\Plugin\Login\Events\PageAuthorizeEvent;
  25. use Grav\Plugin\Login\Events\UserLoginEvent;
  26. use Grav\Plugin\Login\Invitations\Invitation;
  27. use Grav\Plugin\Login\RememberMe\RememberMe;
  28. use Grav\Plugin\Login\RememberMe\TokenStorage;
  29. use Grav\Plugin\Login\TwoFactorAuth\TwoFactorAuth;
  30. /**
  31. * Class Login
  32. * @package Grav\Plugin
  33. */
  34. class Login
  35. {
  36. public const DEBUG = 0;
  37. /** @var Grav */
  38. protected $grav;
  39. /** @var Config */
  40. protected $config;
  41. /** @var Language $language */
  42. protected $language;
  43. /** @var Session */
  44. protected $session;
  45. /** @var Uri */
  46. protected $uri;
  47. /** @var RememberMe */
  48. protected $rememberMe;
  49. /** @var TwoFactorAuth */
  50. protected $twoFa;
  51. /** @var RateLimiter[] */
  52. protected $rateLimiters = [];
  53. /** @var array */
  54. protected $provider_login_templates = [];
  55. /**
  56. * Login constructor.
  57. *
  58. * @param Grav $grav
  59. */
  60. public function __construct(Grav $grav)
  61. {
  62. $this->grav = $grav;
  63. $this->config = $this->grav['config'];
  64. $this->language = $this->grav['language'];
  65. $this->session = $this->grav['session'];
  66. $this->uri = $this->grav['uri'];
  67. }
  68. /**
  69. * @param string $message
  70. * @param object|array $data
  71. */
  72. public static function addDebugMessage(string $message, $data = []): void
  73. {
  74. /** @var Debugger $debugger */
  75. $debugger = Grav::instance()['debugger'];
  76. $debugger->addMessage($message, 'debug', $data);
  77. }
  78. /**
  79. * Login user.
  80. *
  81. * @param array $credentials Login credentials, eg: ['username' => '', 'password' => '']
  82. * @param array $options Login options, eg: ['remember_me' => true]
  83. * @param array $extra Example: ['authorize' => 'site.login', 'user' => null], undefined variables get set.
  84. * @return UserInterface|UserLoginEvent Returns event if $extra['return_event'] is true.
  85. */
  86. public function login(array $credentials, array $options = [], array $extra = [])
  87. {
  88. $grav = Grav::instance();
  89. $eventOptions = [
  90. 'credentials' => $credentials,
  91. 'options' => $options
  92. ] + $extra;
  93. // Attempt to authenticate the user.
  94. $event = new UserLoginEvent($eventOptions);
  95. $grav->fireEvent('onUserLoginAuthenticate', $event);
  96. if ($event->isSuccess()) {
  97. static::DEBUG && static::addDebugMessage('Login onUserLoginAuthenticate: success', $event);
  98. // Make sure that event didn't mess up with the user authorization.
  99. $user = $event->getUser();
  100. $user->authenticated = true;
  101. $user->authorized = false;
  102. // Allow plugins to prevent login after successful authentication.
  103. $event = new UserLoginEvent($event->toArray());
  104. $grav->fireEvent('onUserLoginAuthorize', $event);
  105. }
  106. if ($event->isSuccess()) {
  107. static::DEBUG && static::addDebugMessage('Login onUserLoginAuthorize: success', $event);
  108. // User has been logged in, let plugins know.
  109. $event = new UserLoginEvent($event->toArray());
  110. $grav->fireEvent('onUserLogin', $event);
  111. // Make sure that event didn't mess up with the user authorization.
  112. $user = $event->getUser();
  113. $user->authenticated = true;
  114. $user->authorized = !$event->isDelayed();
  115. if ($user->authorized) {
  116. $event = new UserLoginEvent($event->toArray());
  117. $this->grav->fireEvent('onUserLoginAuthorized', $event);
  118. }
  119. } else {
  120. static::DEBUG && static::addDebugMessage('Login failed', $event);
  121. // Allow plugins to log errors or do other tasks on failure.
  122. $eventName = $event->getOption('failureEvent') ?? 'onUserLoginFailure';
  123. $event = new UserLoginEvent($event->toArray());
  124. $grav->fireEvent($eventName, $event);
  125. // Make sure that event didn't mess up with the user authorization.
  126. $user = $event->getUser();
  127. $user->authenticated = false;
  128. $user->authorized = false;
  129. }
  130. $user = $event->getUser();
  131. $user->def('language', 'en');
  132. return !empty($event['return_event']) ? $event : $user;
  133. }
  134. /**
  135. * Logout user.
  136. *
  137. * @param array $options
  138. * @param array|UserInterface $extra Array of: ['user' => $user, ...] or UserInterface object (deprecated).
  139. * @return UserInterface|UserLoginEvent Returns event if $extra['return_event'] is true.
  140. */
  141. public function logout(array $options = [], $extra = [])
  142. {
  143. $grav = Grav::instance();
  144. if ($extra instanceof UserInterface) {
  145. user_error(__METHOD__ . '($options, $user) is deprecated since Login Plugin 3.5.0, use logout($options, [\'user\' => $user]) instead', E_USER_DEPRECATED);
  146. $extra = ['user' => $extra];
  147. } elseif (isset($extra['user'])) {
  148. $extra['user'] = $grav['user'];
  149. }
  150. $eventOptions = [
  151. 'options' => $options
  152. ] + $extra;
  153. $event = new UserLoginEvent($eventOptions);
  154. // Logout the user.
  155. $grav->fireEvent('onUserLogout', $event);
  156. $user = $event->getUser();
  157. $user->authenticated = false;
  158. $user->authorized = false;
  159. return !empty($event['return_event']) ? $event : $user;
  160. }
  161. /**
  162. * Authenticate user.
  163. *
  164. * @param array $credentials Form fields.
  165. * @param array $options
  166. *
  167. * @return bool
  168. * @deprecated Uses the Controller::taskLogin() event
  169. */
  170. public function authenticate($credentials, $options = ['remember_me' => true])
  171. {
  172. $event = $this->login($credentials, $options, ['return_event' => true]);
  173. $user = $event['user'];
  174. $redirect = $event->getRedirect();
  175. $message = $event->getMessage();
  176. $messageType = $event->getMessageType();
  177. if ($user->authenticated && $user->authorized) {
  178. if (!$message) {
  179. $message = 'PLUGIN_LOGIN.LOGIN_SUCCESSFUL';
  180. $messageType = 'info';
  181. }
  182. if (!$redirect) {
  183. $redirect = $this->uri->route();
  184. }
  185. }
  186. if ($message) {
  187. $this->grav['messages']->add($this->language->translate($message, [$user->language]), $messageType);
  188. }
  189. if ($redirect) {
  190. $this->grav->redirectLangSafe($redirect, $event->getRedirectCode());
  191. }
  192. return $user->authenticated && $user->authorized;
  193. }
  194. /**
  195. * Create a new user file
  196. *
  197. * @param array $data
  198. * @param array $files
  199. *
  200. * @return UserInterface
  201. */
  202. public function register(array $data, array $files = [])
  203. {
  204. // Add defaults and mandatory fields.
  205. $data += [
  206. 'username' => null,
  207. 'email' => null
  208. ];
  209. if (!isset($data['groups'])) {
  210. //Add new user ACL settings
  211. $groups = (array) $this->config->get('plugins.login.user_registration.groups', []);
  212. if (\count($groups) > 0) {
  213. $data['groups'] = $groups;
  214. }
  215. }
  216. if (!isset($data['access'])) {
  217. $access = (array) $this->config->get('plugins.login.user_registration.access.site', []);
  218. if (\count($access) > 0) {
  219. $data['access']['site'] = $access;
  220. }
  221. }
  222. // Validate fields from the form.
  223. $password = $this->validateField('password1', $data['password'] ?? $data['password1'] ?? null);
  224. foreach ($data as $key => &$value) {
  225. $value = $this->validateField($key, $value, $key === 'password2' ? $password : '');
  226. }
  227. unset($value);
  228. /** @var UserCollectionInterface $accounts */
  229. $accounts = $this->grav['accounts'];
  230. // Check whether username already exists.
  231. $username = $data['username'];
  232. if (!$username || $accounts->find($username, ['username'])->exists()) {
  233. /** @var Language $language */
  234. $language = $this->grav['language'];
  235. throw new \RuntimeException($language->translate(['PLUGIN_LOGIN.USERNAME_NOT_AVAILABLE', $username]));
  236. }
  237. // Check whether email already exists.
  238. $email = $data['email'];
  239. if (!$email || $accounts->find($email, ['email'])->exists()) {
  240. /** @var Language $language */
  241. $language = $this->grav['language'];
  242. throw new \RuntimeException($language->translate(['PLUGIN_LOGIN.EMAIL_NOT_AVAILABLE', $email]));
  243. }
  244. $user = $accounts->load($username);
  245. $user->update($data, $files);
  246. if (isset($data['groups'])) {
  247. $user->groups = $data['groups'];
  248. }
  249. if (isset($data['access'])) {
  250. $user->access = $data['access'];
  251. }
  252. $user->save();
  253. return $user;
  254. }
  255. /**
  256. * @param string $username
  257. * @param string|null $ip
  258. * @return int Return positive number if rate limited, otherwise return 0.
  259. */
  260. public function checkLoginRateLimit(string $username, string $ip = null): int
  261. {
  262. $ipKey = $this->getIpKey($ip);
  263. $rateLimiter = $this->getRateLimiter('login_attempts');
  264. $rateLimiter->registerRateLimitedAction($ipKey, 'ip')->registerRateLimitedAction($username);
  265. // Check rate limit for both IP and user, but allow each IP a single try even if user is already rate limited.
  266. $attempts = \count($rateLimiter->getAttempts($ipKey, 'ip'));
  267. if ($rateLimiter->isRateLimited($ipKey, 'ip') || ($attempts && $rateLimiter->isRateLimited($username))) {
  268. return $rateLimiter->getInterval();
  269. }
  270. return 0;
  271. }
  272. /**
  273. * @param string $username
  274. * @param string|null $ip
  275. */
  276. public function resetLoginRateLimit(string $username, string $ip = null): void
  277. {
  278. $ipKey = $this->getIpKey($ip);
  279. $rateLimiter = $this->getRateLimiter('login_attempts');
  280. $rateLimiter->resetRateLimit($ipKey, 'ip')->resetRateLimit($username);
  281. }
  282. /**
  283. * @param string|null $ip
  284. * @return string
  285. */
  286. public function getIpKey(string $ip = null): string
  287. {
  288. if (null === $ip) {
  289. $ip = Uri::ip();
  290. }
  291. $isIPv4 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
  292. $ipKey = $isIPv4 ? $ip : Utils::getSubnet($ip, $this->grav['config']->get('plugins.login.ipv6_subnet_size'));
  293. // Pseudonymization of the IP
  294. return sha1($ipKey . $this->grav['config']->get('security.salt'));
  295. }
  296. /**
  297. * @param string $type
  298. * @param mixed $value
  299. * @param string $extra
  300. *
  301. * @return string
  302. */
  303. public function validateField($type, $value, $extra = '')
  304. {
  305. switch ($type) {
  306. case 'user':
  307. case 'username':
  308. /** @var Config $config */
  309. $config = Grav::instance()['config'];
  310. $username_regex = '/' . $config->get('system.username_regex') . '/';
  311. $value = \is_string($value) ? trim($value) : '';
  312. if ($value === '' || !preg_match($username_regex, $value)) {
  313. throw new \RuntimeException('Username does not pass the minimum requirements');
  314. }
  315. break;
  316. case 'password':
  317. case 'password1':
  318. /** @var Config $config */
  319. $config = Grav::instance()['config'];
  320. $pwd_regex = '/' . $config->get('system.pwd_regex') . '/';
  321. $value = \is_string($value) ? $value : '';
  322. if ($value === '' || !preg_match($pwd_regex, $value)) {
  323. throw new \RuntimeException('Password does not pass the minimum requirements');
  324. }
  325. break;
  326. case 'password2':
  327. $value = \is_string($value) ? $value : '';
  328. if ($value === '' || $value !== $extra) {
  329. throw new \RuntimeException('Passwords did not match.');
  330. }
  331. break;
  332. case 'email':
  333. $value = \is_string($value) ? trim($value) : '';
  334. if ($value === '' || !filter_var($value, FILTER_VALIDATE_EMAIL)) {
  335. throw new \RuntimeException('Not a valid email address');
  336. }
  337. break;
  338. case 'permissions':
  339. if (!\in_array($value, ['a', 's', 'b'], true)) {
  340. throw new \RuntimeException('Permissions ' . $value . ' are invalid.');
  341. }
  342. break;
  343. case 'state':
  344. if ($value !== 'enabled' && $value !== 'disabled') {
  345. throw new \RuntimeException('State is not valid');
  346. }
  347. break;
  348. case 'language':
  349. $languages = new LanguageCodes();
  350. if ($value !== null && !array_key_exists($value, $languages->getList())) {
  351. throw new \RuntimeException('Language code is not valid');
  352. }
  353. break;
  354. }
  355. return $value;
  356. }
  357. /**
  358. * Handle the email to notify the user account creation to the site admin.
  359. *
  360. * @param UserInterface $user
  361. *
  362. * @return bool True if the action was performed.
  363. * @throws \RuntimeException
  364. */
  365. public function sendNotificationEmail(UserInterface $user)
  366. {
  367. if (empty($user->email)) {
  368. throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.USER_NEEDS_EMAIL_FIELD'));
  369. }
  370. try {
  371. Email::sendNotificationEmail($user);
  372. } catch (\Exception $e) {
  373. throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.EMAIL_SENDING_FAILURE'));
  374. }
  375. return true;
  376. }
  377. /**
  378. * Handle the email to welcome the new user
  379. *
  380. * @param UserInterface $user
  381. *
  382. * @return bool True if the action was performed.
  383. * @throws \RuntimeException
  384. */
  385. public function sendWelcomeEmail(UserInterface $user)
  386. {
  387. if (empty($user->email)) {
  388. throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.USER_NEEDS_EMAIL_FIELD'));
  389. }
  390. try {
  391. Email::sendWelcomeEmail($user);
  392. } catch (\Exception $e) {
  393. throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.EMAIL_SENDING_FAILURE'));
  394. }
  395. return true;
  396. }
  397. /**
  398. * Handle the email to activate the user account.
  399. *
  400. * @param UserInterface $user
  401. *
  402. * @return bool True if the action was performed.
  403. * @throws \RuntimeException
  404. */
  405. public function sendActivationEmail(UserInterface $user)
  406. {
  407. if (empty($user->email)) {
  408. throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.USER_NEEDS_EMAIL_FIELD'));
  409. }
  410. $token = md5(uniqid(mt_rand(), true));
  411. $expire = time() + 604800; // next week
  412. $user->activation_token = $token . '::' . $expire;
  413. $user->save();
  414. try {
  415. Email::sendActivationEmail($user);
  416. } catch (\Exception $e) {
  417. throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.EMAIL_SENDING_FAILURE'));
  418. }
  419. return true;
  420. }
  421. /**
  422. * Handle the email to invite user.
  423. *
  424. * @param Invitation $invitation
  425. * @param string|null $message
  426. * @param UserInterface|null $user
  427. * @return bool True if the action was performed.
  428. * @throws \RuntimeException
  429. */
  430. public function sendInviteEmail(Invitation $invitation, string $message = null, UserInterface $user = null)
  431. {
  432. try {
  433. Email::sendInvitationEmail($invitation, $message, $user);
  434. } catch (\Exception $e) {
  435. throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.EMAIL_SENDING_FAILURE'));
  436. }
  437. return true;
  438. }
  439. /**
  440. * Gets and sets the RememberMe class
  441. *
  442. * @param mixed $var A rememberMe instance to set
  443. *
  444. * @return RememberMe Returns the current rememberMe instance
  445. * @throws \InvalidArgumentException
  446. */
  447. public function rememberMe($var = null)
  448. {
  449. if ($var !== null) {
  450. $this->rememberMe = $var;
  451. }
  452. if (!$this->rememberMe) {
  453. /** @var Config $config */
  454. $config = $this->grav['config'];
  455. $cookieName = $config->get('plugins.login.rememberme.name');
  456. $timeout = $config->get('plugins.login.rememberme.timeout');
  457. // Setup storage for RememberMe cookies
  458. $storage = new TokenStorage('user-data://rememberme', $timeout);
  459. $this->rememberMe = new RememberMe($storage);
  460. $this->rememberMe->setCookieName($cookieName);
  461. $this->rememberMe->setExpireTime($timeout);
  462. // Hardening cookies with user-agent and random salt or
  463. // fallback to use system based cache key
  464. $server_agent = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
  465. $data = $server_agent . $config->get('security.salt', $this->grav['cache']->getKey());
  466. $this->rememberMe->setSalt(hash('sha512', $data));
  467. // Set cookie with correct base path of Grav install
  468. $cookie = new Cookie;
  469. $cookie->setPath($this->grav['base_url_relative'] ?: '/');
  470. $this->rememberMe->setCookie($cookie);
  471. }
  472. return $this->rememberMe;
  473. }
  474. /**
  475. * Gets and sets the TwoFactorAuth object
  476. *
  477. * @param TwoFactorAuth $var
  478. * @return TwoFactorAuth
  479. * @throws \RobThree\Auth\TwoFactorAuthException
  480. */
  481. public function twoFactorAuth($var = null)
  482. {
  483. if ($var !== null) {
  484. $this->twoFa = $var;
  485. }
  486. if (!$this->twoFa) {
  487. $this->twoFa = new TwoFactorAuth;
  488. }
  489. return $this->twoFa;
  490. }
  491. /**
  492. * @param string $context
  493. * @param int $maxCount
  494. * @param int $interval
  495. * @return RateLimiter
  496. */
  497. public function getRateLimiter($context, $maxCount = null, $interval = null)
  498. {
  499. if (!isset($this->rateLimiters[$context])) {
  500. switch ($context) {
  501. case 'login_attempts':
  502. $maxCount = $this->grav['config']->get('plugins.login.max_login_count', 5);
  503. $interval = $this->grav['config']->get('plugins.login.max_login_interval', 10);
  504. break;
  505. case 'pw_resets':
  506. $maxCount = $this->grav['config']->get('plugins.login.max_pw_resets_count', 2);
  507. $interval = $this->grav['config']->get('plugins.login.max_pw_resets_interval', 60);
  508. break;
  509. }
  510. $this->rateLimiters[$context] = new RateLimiter($context, $maxCount, $interval);
  511. }
  512. return $this->rateLimiters[$context];
  513. }
  514. /**
  515. * @param string $type
  516. * @param string|null $route
  517. * @param PageInterface|null $page
  518. * @return PageInterface|null
  519. */
  520. public function getPage(string $type, string $route = null, PageInterface $page = null): ?PageInterface
  521. {
  522. $route = $route ?? $this->getRoute($type, true);
  523. if (null === $route) {
  524. return null;
  525. }
  526. if ($page) {
  527. $page->route($route);
  528. $page->slug(basename($route));
  529. } else {
  530. /** @var Pages $pages */
  531. $pages = $this->grav['pages'];
  532. $page = $pages->find($route);
  533. }
  534. if (!$page instanceof PageInterface) {
  535. // Only add login page if it hasn't already been defined.
  536. $page = new Page();
  537. $page->init(new \SplFileInfo('plugin://login/pages/' . $type . '.md'));
  538. $page->route($route);
  539. $page->slug(basename($route));
  540. }
  541. // Login page may not have the correct Cache-Control header set, force no-store for the proxies.
  542. $cacheControl = $page->cacheControl();
  543. if (!$cacheControl) {
  544. $page->cacheControl('private, no-cache, must-revalidate');
  545. }
  546. return $page;
  547. }
  548. /**
  549. * Add Login page.
  550. *
  551. * @param string $type
  552. * @param string|null $route Optional route if we want to force-add the page.
  553. * @param PageInterface|null $page
  554. * @return PageInterface|null
  555. */
  556. public function addPage(string $type, string $route = null, PageInterface $page = null): ?PageInterface
  557. {
  558. $page = $this->getPage($type, $route, $page);
  559. if (null === $page) {
  560. return null;
  561. }
  562. /** @var Pages $pages */
  563. $pages = $this->grav['pages'];
  564. $pages->addPage($page, $route);
  565. return $page;
  566. }
  567. /**
  568. * Get route to a given login page.
  569. *
  570. * @param string $type Use one of: login, activate, forgot, reset, profile, unauthorized, after_login, after_logout,
  571. * register, after_registration, after_activation
  572. * @param bool|null $enabled
  573. * @return string|null Returns route or null if the route has been disabled.
  574. */
  575. public function getRoute(string $type, bool $enabled = null): ?string
  576. {
  577. switch ($type) {
  578. case 'login':
  579. $route = $this->config->get('plugins.login.route');
  580. break;
  581. case 'activate':
  582. case 'forgot':
  583. case 'reset':
  584. case 'profile':
  585. $route = $this->config->get('plugins.login.route_' . $type);
  586. break;
  587. case 'unauthorized':
  588. $route = $this->config->get('plugins.login.route_' . $type, '/');
  589. break;
  590. case 'after_login':
  591. case 'after_logout':
  592. $route = $this->config->get('plugins.login.redirect_' . $type);
  593. if ($route === true) {
  594. $route = $this->config->get('plugins.login.route_' . $type);
  595. }
  596. break;
  597. case 'register':
  598. $enabled = $enabled ?? $this->config->get('plugins.login.user_registration.enabled', false);
  599. $route = $enabled === true ? $this->config->get('plugins.login.route_' . $type) : null;
  600. break;
  601. case 'after_registration':
  602. case 'after_activation':
  603. $route = $this->config->get('plugins.login.redirect_' . $type);
  604. break;
  605. default:
  606. $route = null;
  607. }
  608. if (!is_string($route) || $route === '') {
  609. return null;
  610. }
  611. return $route;
  612. }
  613. /**
  614. * @param UserInterface $user
  615. * @param PageInterface $page
  616. * @param Data|null $config
  617. * @return bool
  618. */
  619. public function isUserAuthorizedForPage(UserInterface $user, PageInterface $page, Data $config = null): bool
  620. {
  621. /** @var PageAuthorizeEvent $event */
  622. $event = $this->grav->dispatchEvent(new PageAuthorizeEvent($page, $user, $config));
  623. if (!$event->hasProtectedAccess()) {
  624. return true;
  625. }
  626. // All access protected pages have a private cache-control. This includes pages which are for guests only.
  627. $cacheControl = $page->cacheControl();
  628. if (!$cacheControl) {
  629. $cacheControl = 'private, no-cache, must-revalidate';
  630. } else {
  631. // The response is intended for a single user only and must not be stored by a shared cache.
  632. $cacheControl = str_replace('public', 'private', $cacheControl);
  633. if (strpos($cacheControl, 'private') === false) {
  634. $cacheControl = 'private, ' . $cacheControl;
  635. }
  636. // The cache will send the request to the origin server for validation before releasing a cached copy.
  637. if (strpos($cacheControl, 'no-cache') === false) {
  638. $cacheControl .= ', no-cache';
  639. }
  640. // The cache must verify the status of the stale resources before using the copy and expired ones should not be used.
  641. if (strpos($cacheControl, 'must-revalidate') === false) {
  642. $cacheControl .= ', must-revalidate';
  643. }
  644. }
  645. $page->cacheControl($cacheControl);
  646. // Deny access if user has not completed 2FA challenge.
  647. $user = $event->user;
  648. if ($user->authenticated && !$user->authorized) {
  649. $event->deny();
  650. }
  651. return $event->isAllowed();
  652. }
  653. /**
  654. * Check if user may use password reset functionality.
  655. *
  656. * @param UserInterface $user
  657. * @param string $field
  658. * @param int $count
  659. * @param int $interval
  660. * @return bool
  661. * @deprecated 2.5.0 Use $grav['login']->getRateLimiter($context) instead. See Grav\Plugin\Login\RateLimiter class.
  662. */
  663. public function isUserRateLimited(UserInterface $user, $field, $count, $interval)
  664. {
  665. if ($count > 0) {
  666. if (!isset($user->{$field})) {
  667. $user->{$field} = [];
  668. }
  669. //remove older than $interval x minute attempts
  670. $actual_resets = [];
  671. foreach ((array)$user->{$field} as $reset) {
  672. if ($reset > (time() - $interval * 60)) {
  673. $actual_resets[] = $reset;
  674. }
  675. }
  676. if (\count($actual_resets) >= $count) {
  677. return true;
  678. }
  679. $actual_resets[] = time(); // current reset
  680. $user->{$field} = $actual_resets;
  681. }
  682. return false;
  683. }
  684. /**
  685. * Reset the rate limit counter.
  686. *
  687. * @param UserInterface $user
  688. * @param string $field
  689. * @deprecated 2.5.0 Use $grav['login']->getRateLimiter($context) instead. See Grav\Plugin\Login\RateLimiter class.
  690. */
  691. public function resetRateLimit(UserInterface $user, $field)
  692. {
  693. $user->{$field} = [];
  694. }
  695. /**
  696. * Get Current logged in user
  697. *
  698. * @return UserInterface
  699. * @deprecated 2.5.0 Use $grav['user'] instead.
  700. */
  701. public function getUser()
  702. {
  703. /** @var UserInterface $user */
  704. return $this->grav['user'];
  705. }
  706. public function addProviderLoginTemplate($template)
  707. {
  708. $this->provider_login_templates[] = $template;
  709. }
  710. public function getProviderLoginTemplates()
  711. {
  712. return $this->provider_login_templates;
  713. }
  714. }