Login.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724
  1. <?php
  2. /**
  3. * @package Grav\Plugin\Login
  4. *
  5. * @copyright Copyright (C) 2014 - 2017 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\Page\Interfaces\PageInterface;
  16. use Grav\Common\Session;
  17. use Grav\Common\User\Interfaces\UserCollectionInterface;
  18. use Grav\Common\User\Interfaces\UserInterface;
  19. use Grav\Common\Uri;
  20. use Grav\Common\Utils;
  21. use Grav\Plugin\Email\Utils as EmailUtils;
  22. use Grav\Plugin\Login\Events\UserLoginEvent;
  23. use Grav\Plugin\Login\RememberMe\RememberMe;
  24. use Grav\Plugin\Login\RememberMe\TokenStorage;
  25. use Grav\Plugin\Login\TwoFactorAuth\TwoFactorAuth;
  26. /**
  27. * Class Login
  28. * @package Grav\Plugin
  29. */
  30. class Login
  31. {
  32. public const DEBUG = 0;
  33. /** @var Grav */
  34. protected $grav;
  35. /** @var Config */
  36. protected $config;
  37. /** @var Language $language */
  38. protected $language;
  39. /** @var Session */
  40. protected $session;
  41. /** @var Uri */
  42. protected $uri;
  43. /** @var RememberMe */
  44. protected $rememberMe;
  45. /** @var TwoFactorAuth */
  46. protected $twoFa;
  47. /** @var RateLimiter[] */
  48. protected $rateLimiters = [];
  49. /** @var array */
  50. protected $provider_login_templates = [];
  51. /**
  52. * Login constructor.
  53. *
  54. * @param Grav $grav
  55. */
  56. public function __construct(Grav $grav)
  57. {
  58. $this->grav = $grav;
  59. $this->config = $this->grav['config'];
  60. $this->language = $this->grav['language'];
  61. $this->session = $this->grav['session'];
  62. $this->uri = $this->grav['uri'];
  63. }
  64. /**
  65. * @param string $message
  66. * @param array $data
  67. */
  68. public static function addDebugMessage(string $message, $data = [])
  69. {
  70. /** @var Debugger $debugger */
  71. $debugger = Grav::instance()['debugger'];
  72. $debugger->addMessage($message, 'debug', $data);
  73. }
  74. /**
  75. * Login user.
  76. *
  77. * @param array $credentials Login credentials, eg: ['username' => '', 'password' => '']
  78. * @param array $options Login options, eg: ['remember_me' => true]
  79. * @param array $extra Example: ['authorize' => 'site.login', 'user' => null], undefined variables get set.
  80. * @return UserInterface|UserLoginEvent Returns event if $extra['return_event'] is true.
  81. */
  82. public function login(array $credentials, array $options = [], array $extra = [])
  83. {
  84. $grav = Grav::instance();
  85. $eventOptions = [
  86. 'credentials' => $credentials,
  87. 'options' => $options
  88. ] + $extra;
  89. // Attempt to authenticate the user.
  90. $event = new UserLoginEvent($eventOptions);
  91. $grav->fireEvent('onUserLoginAuthenticate', $event);
  92. if ($event->isSuccess()) {
  93. static::DEBUG && static::addDebugMessage('Login onUserLoginAuthenticate: success', $event);
  94. // Make sure that event didn't mess up with the user authorization.
  95. $user = $event->getUser();
  96. $user->authenticated = true;
  97. $user->authorized = false;
  98. // Allow plugins to prevent login after successful authentication.
  99. $event = new UserLoginEvent($event->toArray());
  100. $grav->fireEvent('onUserLoginAuthorize', $event);
  101. }
  102. if ($event->isSuccess()) {
  103. static::DEBUG && static::addDebugMessage('Login onUserLoginAuthorize: success', $event);
  104. // User has been logged in, let plugins know.
  105. $event = new UserLoginEvent($event->toArray());
  106. $grav->fireEvent('onUserLogin', $event);
  107. // Make sure that event didn't mess up with the user authorization.
  108. $user = $event->getUser();
  109. $user->authenticated = true;
  110. $user->authorized = !$event->isDelayed();
  111. if ($user->authorized) {
  112. $event = new UserLoginEvent($event->toArray());
  113. $this->grav->fireEvent('onUserLoginAuthorized', $event);
  114. }
  115. } else {
  116. static::DEBUG && static::addDebugMessage('Login failed', $event);
  117. // Allow plugins to log errors or do other tasks on failure.
  118. $eventName = $event->getOption('failureEvent') ?? 'onUserLoginFailure';
  119. $event = new UserLoginEvent($event->toArray());
  120. $grav->fireEvent($eventName, $event);
  121. // Make sure that event didn't mess up with the user authorization.
  122. $user = $event->getUser();
  123. $user->authenticated = false;
  124. $user->authorized = false;
  125. }
  126. $user = $event->getUser();
  127. $user->def('language', 'en');
  128. return !empty($event['return_event']) ? $event : $user;
  129. }
  130. /**
  131. * Logout user.
  132. *
  133. * @param array $options
  134. * @param array|UserInterface $extra Array of: ['user' => $user, ...] or UserInterface object (deprecated).
  135. * @return UserInterface|UserLoginEvent Returns event if $extra['return_event'] is true.
  136. */
  137. public function logout(array $options = [], $extra = [])
  138. {
  139. $grav = Grav::instance();
  140. if ($extra instanceof UserInterface) {
  141. $extra = ['user' => $extra];
  142. } elseif (isset($extra['user'])) {
  143. $extra['user'] = $grav['user'];
  144. }
  145. $eventOptions = [
  146. 'options' => $options
  147. ] + $extra;
  148. $event = new UserLoginEvent($eventOptions);
  149. // Logout the user.
  150. $grav->fireEvent('onUserLogout', $event);
  151. $user = $event->getUser();
  152. $user->authenticated = false;
  153. $user->authorized = false;
  154. return !empty($event['return_event']) ? $event : $user;
  155. }
  156. /**
  157. * Authenticate user.
  158. *
  159. * @param array $credentials Form fields.
  160. * @param array $options
  161. *
  162. * @return bool
  163. * @deprecated Uses the Controller::taskLogin() event
  164. */
  165. public function authenticate($credentials, $options = ['remember_me' => true])
  166. {
  167. $event = $this->login($credentials, $options, ['return_event' => true]);
  168. $user = $event['user'];
  169. $redirect = $event->getRedirect();
  170. $message = $event->getMessage();
  171. $messageType = $event->getMessageType();
  172. if ($user->authenticated && $user->authorized) {
  173. if (!$message) {
  174. $message = 'PLUGIN_LOGIN.LOGIN_SUCCESSFUL';
  175. $messageType = 'info';
  176. }
  177. if (!$redirect) {
  178. $redirect = $this->uri->route();
  179. }
  180. }
  181. if ($message) {
  182. $this->grav['messages']->add($this->language->translate($message, [$user->language]), $messageType);
  183. }
  184. if ($redirect) {
  185. $this->grav->redirectLangSafe($redirect, $event->getRedirectCode());
  186. }
  187. return $user->authenticated && $user->authorized;
  188. }
  189. /**
  190. * Create a new user file
  191. *
  192. * @param array $data
  193. * @param array $files
  194. *
  195. * @return UserInterface
  196. */
  197. public function register(array $data, array $files = [])
  198. {
  199. if (!isset($data['groups'])) {
  200. //Add new user ACL settings
  201. $groups = (array) $this->config->get('plugins.login.user_registration.groups', []);
  202. if (\count($groups) > 0) {
  203. $data['groups'] = $groups;
  204. }
  205. }
  206. if (!isset($data['access'])) {
  207. $access = (array) $this->config->get('plugins.login.user_registration.access.site', []);
  208. if (\count($access) > 0) {
  209. $data['access']['site'] = $access;
  210. }
  211. }
  212. $username = $this->validateField('username', $data['username']);
  213. /** @var UserCollectionInterface $users */
  214. $users = $this->grav['accounts'];
  215. // Create user object and save it
  216. $user = $users->load($username);
  217. if ($user->exists()) {
  218. throw new \RuntimeException('User ' . $username . ' cannot be registered: user already exists!');
  219. }
  220. $user->update($data, $files);
  221. if (isset($data['groups'])) {
  222. $user->groups = $data['groups'];
  223. }
  224. if (isset($data['access'])) {
  225. $user->access = $data['access'];
  226. }
  227. $user->save();
  228. return $user;
  229. }
  230. /**
  231. * @param string $username
  232. * @param string|null $ip
  233. * @return int Return positive number if rate limited, otherwise return 0.
  234. */
  235. public function checkLoginRateLimit(string $username, string $ip = null): int
  236. {
  237. $ipKey = $this->getIpKey($ip);
  238. $rateLimiter = $this->getRateLimiter('login_attempts');
  239. $rateLimiter->registerRateLimitedAction($ipKey, 'ip')->registerRateLimitedAction($username);
  240. // Check rate limit for both IP and user, but allow each IP a single try even if user is already rate limited.
  241. $attempts = \count($rateLimiter->getAttempts($ipKey, 'ip'));
  242. if ($rateLimiter->isRateLimited($ipKey, 'ip') || ($attempts && $rateLimiter->isRateLimited($username))) {
  243. return $rateLimiter->getInterval();
  244. }
  245. return 0;
  246. }
  247. /**
  248. * @param string $username
  249. * @param string|null $ip
  250. */
  251. public function resetLoginRateLimit(string $username, string $ip = null): void
  252. {
  253. $ipKey = $this->getIpKey($ip);
  254. $rateLimiter = $this->getRateLimiter('login_attempts');
  255. $rateLimiter->resetRateLimit($ipKey, 'ip')->resetRateLimit($username);
  256. }
  257. /**
  258. * @param string|null $ip
  259. * @return string
  260. */
  261. public function getIpKey(string $ip = null): string
  262. {
  263. if (null === $ip) {
  264. $ip = Uri::ip();
  265. }
  266. $isIPv4 = filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
  267. $ipKey = $isIPv4 ? $ip : Utils::getSubnet($ip, $this->grav['config']->get('plugins.login.ipv6_subnet_size'));
  268. // Pseudonymization of the IP
  269. return sha1($ipKey . $this->grav['config']->get('security.salt'));
  270. }
  271. /**
  272. * @param string $type
  273. * @param mixed $value
  274. * @param string $extra
  275. *
  276. * @return string
  277. */
  278. public function validateField($type, $value, $extra = '')
  279. {
  280. switch ($type) {
  281. case 'user':
  282. case 'username':
  283. /** @var Config $config */
  284. $config = Grav::instance()['config'];
  285. $username_regex = '/' . $config->get('system.username_regex') . '/';
  286. if (!\is_string($value) || !preg_match($username_regex, $value)) {
  287. throw new \RuntimeException('Username does not pass the minimum requirements');
  288. }
  289. break;
  290. case 'password1':
  291. /** @var Config $config */
  292. $config = Grav::instance()['config'];
  293. $pwd_regex = '/' . $config->get('system.pwd_regex') . '/';
  294. if (!\is_string($value) || !preg_match($pwd_regex, $value)) {
  295. throw new \RuntimeException('Password does not pass the minimum requirements');
  296. }
  297. break;
  298. case 'password2':
  299. if (!\is_string($value) || strcmp($value, $extra)) {
  300. throw new \RuntimeException('Passwords did not match.');
  301. }
  302. break;
  303. case 'email':
  304. if (!\is_string($value) || !filter_var($value, FILTER_VALIDATE_EMAIL)) {
  305. throw new \RuntimeException('Not a valid email address');
  306. }
  307. break;
  308. case 'permissions':
  309. if (!\is_string($value) || !\in_array($value, ['a', 's', 'b'], true)) {
  310. throw new \RuntimeException('Permissions ' . $value . ' are invalid.');
  311. }
  312. break;
  313. case 'fullname':
  314. if (!\is_string($value) || trim($value) === '') {
  315. throw new \RuntimeException('Fullname cannot be empty');
  316. }
  317. break;
  318. case 'state':
  319. if ($value !== 'enabled' && $value !== 'disabled') {
  320. throw new \RuntimeException('State is not valid');
  321. }
  322. break;
  323. }
  324. return $value;
  325. }
  326. /**
  327. * Handle the email to notify the user account creation to the site admin.
  328. *
  329. * @param UserInterface $user
  330. *
  331. * @return bool True if the action was performed.
  332. * @throws \RuntimeException
  333. */
  334. public function sendNotificationEmail(UserInterface $user)
  335. {
  336. if (empty($user->email)) {
  337. throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.USER_NEEDS_EMAIL_FIELD'));
  338. }
  339. $site_name = $this->config->get('site.title', 'Website');
  340. $subject = $this->language->translate(['PLUGIN_LOGIN.NOTIFICATION_EMAIL_SUBJECT', $site_name]);
  341. $content = $this->language->translate([
  342. 'PLUGIN_LOGIN.NOTIFICATION_EMAIL_BODY',
  343. $site_name,
  344. $user->username,
  345. $user->email,
  346. $this->grav['base_url_absolute'],
  347. ]);
  348. $to = $this->config->get('plugins.email.to');
  349. if (empty($to)) {
  350. throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.EMAIL_NOT_CONFIGURED'));
  351. }
  352. $sent = EmailUtils::sendEmail($subject, $content, $to);
  353. if ($sent < 1) {
  354. throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.EMAIL_SENDING_FAILURE'));
  355. }
  356. return true;
  357. }
  358. /**
  359. * Handle the email to welcome the new user
  360. *
  361. * @param UserInterface $user
  362. *
  363. * @return bool True if the action was performed.
  364. * @throws \RuntimeException
  365. */
  366. public function sendWelcomeEmail(UserInterface $user)
  367. {
  368. if (empty($user->email)) {
  369. throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.USER_NEEDS_EMAIL_FIELD'));
  370. }
  371. $site_name = $this->config->get('site.title', 'Website');
  372. $author = $this->grav['config']->get('site.author.name', '');
  373. $fullname = $user->fullname ?: $user->username;
  374. $subject = $this->language->translate(['PLUGIN_LOGIN.WELCOME_EMAIL_SUBJECT', $site_name]);
  375. $content = $this->language->translate(['PLUGIN_LOGIN.WELCOME_EMAIL_BODY',
  376. $fullname,
  377. $this->grav['base_url_absolute'],
  378. $site_name,
  379. $author
  380. ]);
  381. $to = $user->email;
  382. $sent = EmailUtils::sendEmail($subject, $content, $to);
  383. if ($sent < 1) {
  384. throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.EMAIL_SENDING_FAILURE'));
  385. }
  386. return true;
  387. }
  388. /**
  389. * Handle the email to activate the user account.
  390. *
  391. * @param UserInterface $user
  392. *
  393. * @return bool True if the action was performed.
  394. * @throws \RuntimeException
  395. */
  396. public function sendActivationEmail(UserInterface $user)
  397. {
  398. if (empty($user->email)) {
  399. throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.USER_NEEDS_EMAIL_FIELD'));
  400. }
  401. $token = md5(uniqid(mt_rand(), true));
  402. $expire = time() + 604800; // next week
  403. $user->activation_token = $token . '::' . $expire;
  404. $user->save();
  405. $param_sep = $this->config->get('system.param_sep', ':');
  406. $activation_link = $this->grav['base_url_absolute'] . $this->config->get('plugins.login.route_activate') . '/token' . $param_sep . $token . '/username' . $param_sep . $user->username;
  407. $site_name = $this->config->get('site.title', 'Website');
  408. $author = $this->grav['config']->get('site.author.name', '');
  409. $fullname = $user->fullname ?: $user->username;
  410. $subject = $this->language->translate(['PLUGIN_LOGIN.ACTIVATION_EMAIL_SUBJECT', $site_name]);
  411. $content = $this->language->translate(['PLUGIN_LOGIN.ACTIVATION_EMAIL_BODY',
  412. $fullname,
  413. $activation_link,
  414. $site_name,
  415. $author
  416. ]);
  417. $to = $user->email;
  418. $sent = EmailUtils::sendEmail($subject, $content, $to);
  419. if ($sent < 1) {
  420. throw new \RuntimeException($this->language->translate('PLUGIN_LOGIN.EMAIL_SENDING_FAILURE'));
  421. }
  422. return true;
  423. }
  424. /**
  425. * Gets and sets the RememberMe class
  426. *
  427. * @param mixed $var A rememberMe instance to set
  428. *
  429. * @return RememberMe Returns the current rememberMe instance
  430. * @throws \InvalidArgumentException
  431. */
  432. public function rememberMe($var = null)
  433. {
  434. if ($var !== null) {
  435. $this->rememberMe = $var;
  436. }
  437. if (!$this->rememberMe) {
  438. /** @var Config $config */
  439. $config = $this->grav['config'];
  440. $cookieName = $config->get('plugins.login.rememberme.name');
  441. $timeout = $config->get('plugins.login.rememberme.timeout');
  442. // Setup storage for RememberMe cookies
  443. $storage = new TokenStorage('user-data://rememberme', $timeout);
  444. $this->rememberMe = new RememberMe($storage);
  445. $this->rememberMe->setCookieName($cookieName);
  446. $this->rememberMe->setExpireTime($timeout);
  447. // Hardening cookies with user-agent and random salt or
  448. // fallback to use system based cache key
  449. $server_agent = $_SERVER['HTTP_USER_AGENT'] ?? 'unknown';
  450. $data = $server_agent . $config->get('security.salt', $this->grav['cache']->getKey());
  451. $this->rememberMe->setSalt(hash('sha512', $data));
  452. // Set cookie with correct base path of Grav install
  453. $cookie = new Cookie;
  454. $cookie->setPath($this->grav['base_url_relative'] ?: '/');
  455. $this->rememberMe->setCookie($cookie);
  456. }
  457. return $this->rememberMe;
  458. }
  459. /**
  460. * Gets and sets the TwoFactorAuth object
  461. *
  462. * @param TwoFactorAuth $var
  463. * @return TwoFactorAuth
  464. * @throws \RobThree\Auth\TwoFactorAuthException
  465. */
  466. public function twoFactorAuth($var = null)
  467. {
  468. if ($var !== null) {
  469. $this->twoFa = $var;
  470. }
  471. if (!$this->twoFa) {
  472. $this->twoFa = new TwoFactorAuth;
  473. }
  474. return $this->twoFa;
  475. }
  476. /**
  477. * @param string $context
  478. * @param int $maxCount
  479. * @param int $interval
  480. * @return RateLimiter
  481. */
  482. public function getRateLimiter($context, $maxCount = null, $interval = null)
  483. {
  484. if (!isset($this->rateLimiters[$context])) {
  485. switch ($context) {
  486. case 'login_attempts':
  487. $maxCount = $this->grav['config']->get('plugins.login.max_login_count', 5);
  488. $interval = $this->grav['config']->get('plugins.login.max_login_interval', 10);
  489. break;
  490. case 'pw_resets':
  491. $maxCount = $this->grav['config']->get('plugins.login.max_pw_resets_count', 2);
  492. $interval = $this->grav['config']->get('plugins.login.max_pw_resets_interval', 60);
  493. break;
  494. }
  495. $this->rateLimiters[$context] = new RateLimiter($context, $maxCount, $interval);
  496. }
  497. return $this->rateLimiters[$context];
  498. }
  499. /**
  500. * @param UserInterface $user
  501. * @param PageInterface $page
  502. * @param Data|null $config
  503. * @return bool
  504. */
  505. public function isUserAuthorizedForPage(UserInterface $user, PageInterface $page, $config = null)
  506. {
  507. $header = $page->header();
  508. $rules = isset($header->access) ? (array)$header->access : [];
  509. if (!$rules && $config !== null && $config->get('parent_acl')) {
  510. // If page has no ACL rules, use its parent's rules
  511. $parent = $page->parent();
  512. while (!$rules and $parent) {
  513. $header = $parent->header();
  514. $rules = isset($header->access) ? (array)$header->access : [];
  515. $parent = $parent->parent();
  516. }
  517. }
  518. // Continue to the page if it has no ACL rules.
  519. if (!$rules) {
  520. return true;
  521. }
  522. // Deny access if user has not completed 2FA challenge.
  523. if ($user->authenticated && !$user->authorized) {
  524. return false;
  525. }
  526. // Continue to the page if user is authorized to access the page.
  527. foreach ($rules as $rule => $value) {
  528. if (is_int($rule)) {
  529. if ($user->authorize($value) === true) {
  530. return true;
  531. }
  532. } elseif (\is_array($value)) {
  533. foreach ($value as $nested_rule => $nested_value) {
  534. if ($user->authorize($rule . '.' . $nested_rule) === Utils::isPositive($nested_value)) {
  535. return true;
  536. }
  537. }
  538. } elseif ($user->authorize($rule) === Utils::isPositive($value)) {
  539. return true;
  540. }
  541. }
  542. return false;
  543. }
  544. /**
  545. * Check if user may use password reset functionality.
  546. *
  547. * @param UserInterface $user
  548. * @param string $field
  549. * @param int $count
  550. * @param int $interval
  551. * @return bool
  552. * @deprecated 2.5.0 Use $grav['login']->getRateLimiter($context) instead. See Grav\Plugin\Login\RateLimiter class.
  553. */
  554. public function isUserRateLimited(UserInterface $user, $field, $count, $interval)
  555. {
  556. if ($count > 0) {
  557. if (!isset($user->{$field})) {
  558. $user->{$field} = [];
  559. }
  560. //remove older than $interval x minute attempts
  561. $actual_resets = [];
  562. foreach ((array)$user->{$field} as $reset) {
  563. if ($reset > (time() - $interval * 60)) {
  564. $actual_resets[] = $reset;
  565. }
  566. }
  567. if (\count($actual_resets) >= $count) {
  568. return true;
  569. }
  570. $actual_resets[] = time(); // current reset
  571. $user->{$field} = $actual_resets;
  572. }
  573. return false;
  574. }
  575. /**
  576. * Reset the rate limit counter.
  577. *
  578. * @param UserInterface $user
  579. * @param string $field
  580. * @deprecated 2.5.0 Use $grav['login']->getRateLimiter($context) instead. See Grav\Plugin\Login\RateLimiter class.
  581. */
  582. public function resetRateLimit(UserInterface $user, $field)
  583. {
  584. $user->{$field} = [];
  585. }
  586. /**
  587. * Get Current logged in user
  588. *
  589. * @return UserInterface
  590. * @deprecated 2.5.0 Use $grav['user'] instead.
  591. */
  592. public function getUser()
  593. {
  594. /** @var UserInterface $user */
  595. return $this->grav['user'];
  596. }
  597. public function addProviderLoginTemplate($template)
  598. {
  599. $this->provider_login_templates[] = $template;
  600. }
  601. public function getProviderLoginTemplates()
  602. {
  603. return $this->provider_login_templates;
  604. }
  605. }