login.php 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936
  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;
  9. use Grav\Common\Grav;
  10. use Grav\Common\Language\Language;
  11. use Grav\Common\Page\Page;
  12. use Grav\Common\Page\Pages;
  13. use Grav\Common\Plugin;
  14. use Grav\Common\Twig\Twig;
  15. use Grav\Common\User\User;
  16. use Grav\Common\Utils;
  17. use Grav\Common\Uri;
  18. use Grav\Plugin\Login\Events\UserLoginEvent;
  19. use Grav\Plugin\Login\Login;
  20. use Grav\Plugin\Login\Controller;
  21. use Grav\Plugin\Login\RememberMe\RememberMe;
  22. use RocketTheme\Toolbox\Event\Event;
  23. use RocketTheme\Toolbox\Session\Message;
  24. /**
  25. * Class LoginPlugin
  26. * @package Grav\Plugin\Login
  27. */
  28. class LoginPlugin extends Plugin
  29. {
  30. const TMP_COOKIE_NAME = 'tmp-message';
  31. /** @var string */
  32. protected $route;
  33. /** @var string */
  34. protected $route_register;
  35. /** @var string */
  36. protected $route_forgot;
  37. /** @var bool */
  38. protected $authenticated = true;
  39. /** @var Login */
  40. protected $login;
  41. protected $redirect_to_login;
  42. /**
  43. * @return array
  44. */
  45. public static function getSubscribedEvents()
  46. {
  47. return [
  48. 'onPluginsInitialized' => [['initializeSession', 10000], ['initializeLogin', 1000]],
  49. 'onTask.login.login' => ['loginController', 0],
  50. 'onTask.login.forgot' => ['loginController', 0],
  51. 'onTask.login.logout' => ['loginController', 0],
  52. 'onTask.login.reset' => ['loginController', 0],
  53. 'onPagesInitialized' => [['storeReferrerPage', 0], ['pageVisibility', 0]],
  54. 'onPageInitialized' => ['authorizePage', 0],
  55. 'onPageFallBackUrl' => ['authorizeFallBackUrl', 0],
  56. 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0],
  57. 'onTwigSiteVariables' => ['onTwigSiteVariables', -100000],
  58. 'onFormProcessed' => ['onFormProcessed', 0],
  59. 'onUserLoginAuthenticate' => [['userLoginAuthenticateByRegistration', 10002], ['userLoginAuthenticateByRememberMe', 10001], ['userLoginAuthenticateByEmail', 10000], ['userLoginAuthenticate', 0]],
  60. 'onUserLoginAuthorize' => ['userLoginAuthorize', 0],
  61. 'onUserLoginFailure' => ['userLoginFailure', 0],
  62. 'onUserLogin' => ['userLogin', 0],
  63. 'onUserLogout' => ['userLogout', 0],
  64. ];
  65. }
  66. /**
  67. * [onPluginsInitialized] Initialize login plugin if path matches.
  68. * @throws \RuntimeException
  69. */
  70. public function initializeSession()
  71. {
  72. // Check to ensure sessions are enabled.
  73. if (!$this->config->get('system.session.enabled')) {
  74. throw new \RuntimeException('The Login plugin requires "system.session" to be enabled');
  75. }
  76. // Autoload classes
  77. $autoload = __DIR__ . '/vendor/autoload.php';
  78. if (!is_file($autoload)) {
  79. throw new \RuntimeException('Login Plugin failed to load. Composer dependencies not met.');
  80. }
  81. require_once $autoload;
  82. // Define login service.
  83. $this->grav['login'] = function (Grav $c) {
  84. return new Login($c);
  85. };
  86. // Define current user service.
  87. $this->grav['user'] = function (Grav $c) {
  88. $session = $c['session'];
  89. if (empty($session->user)) {
  90. $session->user = $c['login']->login(['username' => ''], ['remember_me' => true, 'remember_me_login' => true]);
  91. }
  92. return $session->user;
  93. };
  94. }
  95. /**
  96. * [onPluginsInitialized] Initialize login plugin if path matches.
  97. * @throws \RuntimeException
  98. */
  99. public function initializeLogin()
  100. {
  101. $this->login = $this->grav['login'];
  102. /** @var Uri $uri */
  103. $uri = $this->grav['uri'];
  104. // Admin has its own login; make sure we're not in admin.
  105. if (!isset($this->grav['admin'])) {
  106. $this->route = $this->config->get('plugins.login.route');
  107. }
  108. $path = $uri->path();
  109. $this->redirect_to_login = $this->config->get('plugins.login.redirect_to_login');
  110. // Register route to login page if it has been set.
  111. if ($this->route && $this->route === $path) {
  112. $this->enable([
  113. 'onPagesInitialized' => ['addLoginPage', 0],
  114. ]);
  115. return;
  116. }
  117. if ($path === $this->config->get('plugins.login.route_forgot')) {
  118. $this->enable([
  119. 'onPagesInitialized' => ['addForgotPage', 0],
  120. ]);
  121. return;
  122. }
  123. if ($path === $this->config->get('plugins.login.route_reset')) {
  124. $this->enable([
  125. 'onPagesInitialized' => ['addResetPage', 0],
  126. ]);
  127. return;
  128. }
  129. if ($path === $this->config->get('plugins.login.route_register')) {
  130. if ($this->config->get('plugins.login.user_registration.enabled')) {
  131. $this->enable([
  132. 'onPagesInitialized' => ['addRegisterPage', 0],
  133. ]);
  134. } else {
  135. throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.REGISTRATION_DISABLED'), 404);
  136. }
  137. return;
  138. }
  139. if ($path === $this->config->get('plugins.login.route_activate')) {
  140. $this->enable([
  141. 'onPagesInitialized' => ['handleUserActivation', 0],
  142. ]);
  143. return;
  144. }
  145. if ($path === $this->config->get('plugins.login.route_profile')) {
  146. $this->enable([
  147. 'onPagesInitialized' => ['addProfilePage', 0],
  148. ]);
  149. return;
  150. }
  151. }
  152. /**
  153. * Optional ability to dynamically set visibility based on page access and page header
  154. * that states `login.visibility_requires_access: true`
  155. *
  156. * Note that this setting may be slow on large sites as it loads all pages into memory for each page load!
  157. *
  158. * @param Event $e
  159. */
  160. public function pageVisibility(Event $e)
  161. {
  162. if ($this->config->get('plugins.login.dynamic_page_visibility')) {
  163. /** @var Pages $pages */
  164. $pages = $e['pages'];
  165. $user = $this->grav['user'];
  166. foreach ($pages->instances() as $page) {
  167. $header = $page->header();
  168. if (isset($header->login['visibility_requires_access'])) {
  169. $config = $this->mergeConfig($page);
  170. $access = $this->login->isUserAuthorizedForPage($user, $page, $config);
  171. if ($access === false) {
  172. $page->visible(false);
  173. }
  174. }
  175. }
  176. }
  177. }
  178. /**
  179. * [onPagesInitialized]
  180. */
  181. public function storeReferrerPage()
  182. {
  183. $invalid_redirect_routes = [
  184. $this->config->get('plugins.login.route') ?: '/login',
  185. $this->config->get('plugins.login.route_register') ?: '/register',
  186. $this->config->get('plugins.login.route_activate') ?: '/activate_user',
  187. $this->config->get('plugins.login.route_forgot') ?: '/forgot_password',
  188. $this->config->get('plugins.login.route_reset') ?: '/reset_password',
  189. ];
  190. /** @var Uri $uri */
  191. $uri = $this->grav['uri'];
  192. $current_route = $uri->route();
  193. if (!in_array($current_route, $invalid_redirect_routes, true)) {
  194. $allowed = true;
  195. /** @var Page $page */
  196. $page = $this->grav['pages']->dispatch($current_route);
  197. if ($page) {
  198. $header = $page->header();
  199. if (isset($header->login_redirect_here) && $header->login_redirect_here === false) {
  200. $allowed = false;
  201. }
  202. if ($allowed && $page->routable()) {
  203. $this->grav['session']->redirect_after_login = $page->route() . ($uri->params() ?: '');
  204. }
  205. }
  206. }
  207. }
  208. /**
  209. * Add Login page
  210. * @throws \Exception
  211. */
  212. public function addLoginPage()
  213. {
  214. /** @var Pages $pages */
  215. $pages = $this->grav['pages'];
  216. $page = $pages->dispatch($this->route);
  217. if (!$page) {
  218. // Only add login page if it hasn't already been defined.
  219. $page = new Page;
  220. $page->init(new \SplFileInfo(__DIR__ . '/pages/login.md'));
  221. $page->slug(basename($this->route));
  222. $pages->addPage($page, $this->route);
  223. }
  224. }
  225. /**
  226. * Add Login page
  227. * @throws \Exception
  228. */
  229. public function addForgotPage()
  230. {
  231. $route = $this->config->get('plugins.login.route_forgot');
  232. /** @var Pages $pages */
  233. $pages = $this->grav['pages'];
  234. $page = $pages->dispatch($route);
  235. if (!$page) {
  236. // Only add forgot page if it hasn't already been defined.
  237. $page = new Page;
  238. $page->init(new \SplFileInfo(__DIR__ . '/pages/forgot.md'));
  239. $page->slug(basename($route));
  240. $pages->addPage($page, $route);
  241. }
  242. }
  243. /**
  244. * Add Reset page
  245. * @throws \Exception
  246. */
  247. public function addResetPage()
  248. {
  249. $route = $this->config->get('plugins.login.route_reset');
  250. $uri = $this->grav['uri'];
  251. $token = $uri->param('token');
  252. $user = $uri->param('user');
  253. if (!$user || !$token) {
  254. return;
  255. }
  256. /** @var Pages $pages */
  257. $pages = $this->grav['pages'];
  258. $page = $pages->dispatch($route);
  259. if (!$page) {
  260. // Only add login page if it hasn't already been defined.
  261. $page = new Page;
  262. $page->init(new \SplFileInfo(__DIR__ . '/pages/reset.md'));
  263. $page->slug(basename($route));
  264. $pages->addPage($page, $route);
  265. }
  266. }
  267. /**
  268. * Add Register page
  269. * @throws \Exception
  270. */
  271. public function addRegisterPage()
  272. {
  273. $route = $this->config->get('plugins.login.route_register');
  274. /** @var Pages $pages */
  275. $pages = $this->grav['pages'];
  276. $page = $pages->dispatch($route);
  277. if (!$page) {
  278. $page = new Page;
  279. $page->init(new \SplFileInfo(__DIR__ . '/pages/register.md'));
  280. $page->template('form');
  281. $page->slug(basename($route));
  282. $pages->addPage($page, $route);
  283. }
  284. }
  285. /**
  286. * Handle user activation
  287. * @throws \RuntimeException
  288. */
  289. public function handleUserActivation()
  290. {
  291. /** @var Uri $uri */
  292. $uri = $this->grav['uri'];
  293. /** @var Message $messages */
  294. $messages = $this->grav['messages'];
  295. $username = $uri->param('username');
  296. $token = $uri->param('token');
  297. $user = User::load($username);
  298. if (empty($user->activation_token)) {
  299. $message = $this->grav['language']->translate('PLUGIN_LOGIN.INVALID_REQUEST');
  300. $messages->add($message, 'error');
  301. } else {
  302. list($good_token, $expire) = explode('::', $user->activation_token, 2);
  303. if ($good_token === $token) {
  304. if (time() > $expire) {
  305. $message = $this->grav['language']->translate('PLUGIN_LOGIN.ACTIVATION_LINK_EXPIRED');
  306. $messages->add($message, 'error');
  307. } else {
  308. $user['state'] = 'enabled';
  309. unset($user['activation_token']);
  310. $user->save();
  311. $message = $this->grav['language']->translate('PLUGIN_LOGIN.USER_ACTIVATED_SUCCESSFULLY');
  312. $messages->add($message, 'info');
  313. if ($this->config->get('plugins.login.user_registration.options.send_welcome_email', false)) {
  314. $this->login->sendWelcomeEmail($user);
  315. }
  316. if ($this->config->get('plugins.login.user_registration.options.send_notification_email', false)) {
  317. $this->login->sendNotificationEmail($user);
  318. }
  319. if ($this->config->get('plugins.login.user_registration.options.login_after_registration', false)) {
  320. $this->login->login(['username' => $username], ['after_registration' => true]);
  321. }
  322. }
  323. } else {
  324. $message = $this->grav['language']->translate('PLUGIN_LOGIN.INVALID_REQUEST');
  325. $messages->add($message, 'error');
  326. }
  327. }
  328. $redirect_route = $this->config->get('plugins.login.user_registration.redirect_after_activation', '/');
  329. $this->grav->redirect($redirect_route);
  330. }
  331. /**
  332. * Add Profile page
  333. */
  334. public function addProfilePage()
  335. {
  336. $route = $this->config->get('plugins.login.route_profile');
  337. /** @var Pages $pages */
  338. $pages = $this->grav['pages'];
  339. $page = $pages->dispatch($route);
  340. if (!$page) {
  341. // Only add forgot page if it hasn't already been defined.
  342. $page = new Page;
  343. $page->init(new \SplFileInfo(__DIR__ . "/pages/profile.md"));
  344. $page->slug(basename($route));
  345. $pages->addPage($page, $route);
  346. }
  347. $this->storeReferrerPage();
  348. }
  349. /**
  350. * Set Unauthorized page
  351. * @throws \Exception
  352. */
  353. public function setUnauthorizedPage()
  354. {
  355. $route = $this->config->get('plugins.login.route_unauthorized');
  356. /** @var Pages $pages */
  357. $pages = $this->grav['pages'];
  358. $page = $pages->dispatch($route);
  359. if (!$page) {
  360. $page = new Page;
  361. $page->init(new \SplFileInfo(__DIR__ . '/pages/unauthorized.md'));
  362. $page->template('default');
  363. $page->slug(basename($route));
  364. $pages->addPage($page, $route);
  365. }
  366. unset($this->grav['page']);
  367. $this->grav['page'] = $page;
  368. }
  369. /**
  370. * Initialize login controller
  371. */
  372. public function loginController()
  373. {
  374. /** @var Uri $uri */
  375. $uri = $this->grav['uri'];
  376. $task = !empty($_POST['task']) ? $_POST['task'] : $uri->param('task');
  377. $task = substr($task, strlen('login.'));
  378. $post = !empty($_POST) ? $_POST : [];
  379. switch ($task) {
  380. case 'login':
  381. if (!isset($post['login-form-nonce']) || !Utils::verifyNonce($post['login-form-nonce'], 'login-form')) {
  382. $this->grav['messages']->add($this->grav['language']->translate('PLUGIN_LOGIN.ACCESS_DENIED'),
  383. 'info');
  384. $twig = $this->grav['twig'];
  385. $twig->twig_vars['notAuthorized'] = true;
  386. return;
  387. }
  388. break;
  389. case 'forgot':
  390. if (!isset($post['forgot-form-nonce']) || !Utils::verifyNonce($post['forgot-form-nonce'], 'forgot-form')) {
  391. $this->grav['messages']->add($this->grav['language']->translate('PLUGIN_LOGIN.ACCESS_DENIED'),'info');
  392. return;
  393. }
  394. break;
  395. }
  396. $controller = new Controller($this->grav, $task, $post);
  397. $controller->execute();
  398. $controller->redirect();
  399. }
  400. /**
  401. * Authorize the Page fallback url (page media accessed through the page route)
  402. */
  403. public function authorizeFallBackUrl()
  404. {
  405. if ($this->config->get('plugins.login.protect_protected_page_media', false)) {
  406. $page_url = dirname($this->grav['uri']->path());
  407. $page = $this->grav['pages']->find($page_url);
  408. unset($this->grav['page']);
  409. $this->grav['page'] = $page;
  410. $this->authorizePage();
  411. }
  412. }
  413. /**
  414. * [onPageInitialized] Authorize Page
  415. */
  416. public function authorizePage()
  417. {
  418. if (!$this->authenticated) {
  419. return;
  420. }
  421. /** @var User $user */
  422. $user = $this->grav['user'];
  423. /** @var Page $page */
  424. $page = $this->grav['page'];
  425. if (!$page || $this->grav['login']->isUserAuthorizedForPage($user, $page, $this->mergeConfig($page))) {
  426. return;
  427. }
  428. // If this is not an HTML page request, simply throw a 403 error
  429. $uri_extension = $this->grav['uri']->extension('html');
  430. $supported_types = $this->config->get('media.types');
  431. if ($uri_extension !== 'html' && array_key_exists($uri_extension, $supported_types)) {
  432. header('HTTP/1.0 403 Forbidden');
  433. exit;
  434. }
  435. // User is not logged in; redirect to login page.
  436. if ($this->redirect_to_login && $this->route && !$user->authenticated) {
  437. $this->grav->redirect($this->route, 302);
  438. }
  439. /** @var Twig $twig */
  440. $twig = $this->grav['twig'];
  441. // Reset page with login page.
  442. if (empty($user->authenticated)) {
  443. if ($this->route) {
  444. $page = $this->grav['pages']->dispatch($this->route);
  445. } else {
  446. $page = new Page;
  447. // $this->grav['session']->redirect_after_login = $this->grav['uri']->path() . ($this->grav['uri']->params() ?: '');
  448. // Get the admin Login page is needed, else teh default
  449. if ($this->isAdmin()) {
  450. $login_file = $this->grav['locator']->findResource('plugins://admin/pages/admin/login.md');
  451. $page->init(new \SplFileInfo($login_file));
  452. } else {
  453. $page->init(new \SplFileInfo(__DIR__ . '/pages/login.md'));
  454. }
  455. $page->slug(basename($this->route));
  456. }
  457. $this->authenticated = false;
  458. unset($this->grav['page']);
  459. $this->grav['page'] = $page;
  460. $twig->twig_vars['form'] = new Form($page);
  461. } else {
  462. /** @var Language $l */
  463. $l = $this->grav['language'];
  464. $this->grav['messages']->add($l->translate('PLUGIN_LOGIN.ACCESS_DENIED'), 'error');
  465. $twig->twig_vars['notAuthorized'] = true;
  466. $this->setUnauthorizedPage();
  467. }
  468. }
  469. /**
  470. * [onTwigTemplatePaths] Add twig paths to plugin templates.
  471. */
  472. public function onTwigTemplatePaths()
  473. {
  474. $twig = $this->grav['twig'];
  475. $twig->twig_paths[] = __DIR__ . '/templates';
  476. }
  477. /**
  478. * [onTwigSiteVariables] Set all twig variables for generating output.
  479. */
  480. public function onTwigSiteVariables()
  481. {
  482. /** @var Twig $twig */
  483. $twig = $this->grav['twig'];
  484. $this->grav->fireEvent('onLoginPage');
  485. $extension = $this->grav['uri']->extension();
  486. $extension = $extension ?: 'html';
  487. if (!$this->authenticated) {
  488. $twig->template = "login.{$extension}.twig";
  489. }
  490. // add CSS for frontend if required
  491. if (!$this->isAdmin() && $this->config->get('plugins.login.built_in_css')) {
  492. $this->grav['assets']->add('plugin://login/css/login.css');
  493. }
  494. $task = $this->grav['uri']->param('task') ?: (isset($_POST['task']) ? $_POST['task'] : '');
  495. $task = substr($task, strlen('login.'));
  496. if ($task === 'reset') {
  497. $username = $this->grav['uri']->param('user');
  498. $token = $this->grav['uri']->param('token');
  499. if (!empty($username) && !empty($token)) {
  500. $twig->twig_vars['username'] = $username;
  501. $twig->twig_vars['token'] = $token;
  502. }
  503. } elseif ($task === 'login') {
  504. $twig->twig_vars['username'] = isset($_POST['username']) ? $_POST['username'] : '';
  505. }
  506. $flashData = $this->grav['session']->getFlashCookieObject(self::TMP_COOKIE_NAME);
  507. if (isset($flashData->message)) {
  508. $this->grav['messages']->add($flashData->message, $flashData->status);
  509. }
  510. }
  511. /**
  512. * Process the user registration, triggered by a registration form
  513. *
  514. * @param Form $form
  515. * @throws \RuntimeException
  516. */
  517. private function processUserRegistration($form, Event $event)
  518. {
  519. $language = $this->grav['language'];
  520. $messages = $this->grav['messages'];
  521. if (!$this->config->get('plugins.login.enabled')) {
  522. throw new \RuntimeException($language->translate('PLUGIN_LOGIN.PLUGIN_LOGIN_DISABLED'));
  523. }
  524. if (!$this->config->get('plugins.login.user_registration.enabled')) {
  525. throw new \RuntimeException($language->translate('PLUGIN_LOGIN.USER_REGISTRATION_DISABLED'));
  526. }
  527. // Check for existing username
  528. $username = $form->value('username');
  529. $existing_username = User::find($username,['username']);
  530. if ($existing_username->exists()) {
  531. $this->grav->fireEvent('onFormValidationError', new Event([
  532. 'form' => $form,
  533. 'message' => $language->translate([
  534. 'PLUGIN_LOGIN.USERNAME_NOT_AVAILABLE',
  535. $username
  536. ])
  537. ]));
  538. $event->stopPropagation();
  539. return;
  540. }
  541. // Check for existing email
  542. $email = $form->value('email');
  543. $existing_email = User::find($email,['email']);
  544. if ($existing_email->exists()) {
  545. $this->grav->fireEvent('onFormValidationError', new Event([
  546. 'form' => $form,
  547. 'message' => $language->translate([
  548. 'PLUGIN_LOGIN.EMAIL_NOT_AVAILABLE',
  549. $email
  550. ])
  551. ]));
  552. $event->stopPropagation();
  553. return;
  554. }
  555. $data = [];
  556. $data['username'] = $username;
  557. // if multiple password fields, check they match and set password field from it
  558. if ($this->config->get('plugins.login.user_registration.options.validate_password1_and_password2',
  559. false)
  560. ) {
  561. if ($form->value('password1') !== $form->value('password2')) {
  562. $this->grav->fireEvent('onFormValidationError', new Event([
  563. 'form' => $form,
  564. 'message' => $language->translate('PLUGIN_LOGIN.PASSWORDS_DO_NOT_MATCH')
  565. ]));
  566. $event->stopPropagation();
  567. return;
  568. }
  569. $data['password'] = $form->value('password1');
  570. }
  571. $fields = (array)$this->config->get('plugins.login.user_registration.fields', []);
  572. foreach ($fields as $field) {
  573. // Process value of field if set in the page process.register_user
  574. $default_values = (array)$this->config->get('plugins.login.user_registration.default_values');
  575. if ($default_values) {
  576. foreach ($default_values as $key => $param) {
  577. if ($key === $field) {
  578. if (is_array($param)) {
  579. $values = explode(',', $param);
  580. } else {
  581. $values = $param;
  582. }
  583. $data[$field] = $values;
  584. }
  585. }
  586. }
  587. if (!isset($data[$field]) && $form->value($field)) {
  588. $data[$field] = $form->value($field);
  589. }
  590. }
  591. if ($this->config->get('plugins.login.user_registration.options.set_user_disabled', false)) {
  592. $data['state'] = 'disabled';
  593. } else {
  594. $data['state'] = 'enabled';
  595. }
  596. $this->grav->fireEvent('onUserLoginRegisterData', new Event(['data' => &$data]));
  597. $user = $this->login->register($data);
  598. $fullname = $user->fullname ?: $user->username;
  599. if ($this->config->get('plugins.login.user_registration.options.send_activation_email', false)) {
  600. $this->login->sendActivationEmail($user);
  601. $message = $language->translate(['PLUGIN_LOGIN.ACTIVATION_NOTICE_MSG', $fullname]);
  602. $messages->add($message, 'info');
  603. } else {
  604. if ($this->config->get('plugins.login.user_registration.options.send_welcome_email', false)) {
  605. $this->login->sendWelcomeEmail($user);
  606. }
  607. if ($this->config->get('plugins.login.user_registration.options.send_notification_email', false)) {
  608. $this->login->sendNotificationEmail($user);
  609. }
  610. $message = $language->translate(['PLUGIN_LOGIN.WELCOME_NOTICE_MSG', $fullname]);
  611. $messages->add($message, 'info');
  612. }
  613. $this->grav->fireEvent('onUserLoginRegistered', new Event(['user' => $user]));
  614. if (isset($data['state']) && $data['state'] === 'enabled' && $this->config->get('plugins.login.user_registration.options.login_after_registration', false)) {
  615. $this->login->login(['username' => $username], ['after_registration' => true], ['user' => $user]);
  616. }
  617. $redirect = $this->config->get('plugins.login.user_registration.redirect_after_registration', false);
  618. if ($redirect) {
  619. $this->grav->redirect($redirect);
  620. }
  621. }
  622. /**
  623. * Save user profile information
  624. *
  625. * @param Form $form
  626. * @return bool
  627. */
  628. private function processUserProfile($form)
  629. {
  630. $user = $this->grav['user'];
  631. $user->merge($form->getData()->toArray());
  632. try {
  633. $user->save();
  634. } catch (\Exception $e) {
  635. $form->setMessage($e->getMessage(), 'error');
  636. return false;
  637. }
  638. return true;
  639. }
  640. /**
  641. * [onFormProcessed] Process a registration form. Handles the following actions:
  642. *
  643. * - register_user: registers a user
  644. * - update_user: updates user profile
  645. *
  646. * @param Event $event
  647. * @throws \RuntimeException
  648. */
  649. public function onFormProcessed(Event $event)
  650. {
  651. $form = $event['form'];
  652. $action = $event['action'];
  653. switch ($action) {
  654. case 'register_user':
  655. $this->processUserRegistration($form, $event);
  656. break;
  657. case 'update_user':
  658. $this->processUserProfile($form);
  659. break;
  660. }
  661. }
  662. /**
  663. * @param UserLoginEvent $event
  664. * @throws \RuntimeException
  665. */
  666. public function userLoginAuthenticateByRegistration(UserLoginEvent $event)
  667. {
  668. // Check that we're logging in after registration.
  669. if (!$event->getOption('after_registration') || $this->isAdmin()) {
  670. return;
  671. }
  672. $event->setStatus($event::AUTHENTICATION_SUCCESS);
  673. $event->stopPropagation();
  674. }
  675. /**
  676. * @param UserLoginEvent $event
  677. * @throws \RuntimeException
  678. */
  679. public function userLoginAuthenticateByRememberMe(UserLoginEvent $event)
  680. {
  681. // Check that we're logging in with remember me.
  682. if (!$event->getOption('remember_me_login') || !$event->getOption('remember_me') || $this->isAdmin()) {
  683. return;
  684. }
  685. // Only use remember me if user isn't set and feature is enabled.
  686. if ($this->grav['config']->get('plugins.login.rememberme.enabled') && !$event->getUser()->exists()) {
  687. /** @var RememberMe $rememberMe */
  688. $rememberMe = $this->grav['login']->rememberMe();
  689. $username = $rememberMe->login();
  690. if ($rememberMe->loginTokenWasInvalid()) {
  691. // Token was invalid. We will display error page as this was likely an attack.
  692. throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.REMEMBER_ME_STOLEN_COOKIE'), 403);
  693. }
  694. if ($username === false) {
  695. // User has not been remembered, there is no point of continuing.
  696. $event->setStatus($event::AUTHENTICATION_FAILURE);
  697. $event->stopPropagation();
  698. return;
  699. }
  700. // Allow remember me to work with different login methods.
  701. $event->setCredential('username', $username);
  702. $event->setUser(User::load($username, false));
  703. }
  704. }
  705. public function userLoginAuthenticateByEmail(UserLoginEvent $event)
  706. {
  707. if (($username = $event->getCredential('username')) && !$event->getUser()->exists()) {
  708. $event->setUser(User::find($username));
  709. }
  710. }
  711. public function userLoginAuthenticate(UserLoginEvent $event)
  712. {
  713. $user = $event->getUser();
  714. $credentials = $event->getCredentials();
  715. if (!$user->exists()) {
  716. // Never let non-existing users to pass the authentication.
  717. // Higher level plugins may override this behavior by stopping propagation.
  718. $event->setStatus($event::AUTHENTICATION_FAILURE);
  719. $event->stopPropagation();
  720. return;
  721. }
  722. // Never let empty password to pass the authentication.
  723. // Higher level plugins may override this behavior by stopping propagation.
  724. if (empty($credentials['password'])) {
  725. $event->setStatus($event::AUTHENTICATION_FAILURE);
  726. $event->stopPropagation();
  727. return;
  728. }
  729. // Try default user authentication. Stop propagation if authentication succeeds.
  730. if ($user->authenticate($credentials['password'])) {
  731. $event->setStatus($event::AUTHENTICATION_SUCCESS);
  732. $event->stopPropagation();
  733. return;
  734. }
  735. // If authentication status is undefined, lower level event handlers may still be able to authenticate user.
  736. }
  737. public function userLoginAuthorize(UserLoginEvent $event)
  738. {
  739. // Always block access if authorize defaulting to site.login fails.
  740. $user = $event->getUser();
  741. foreach ($event->getAuthorize() as $authorize) {
  742. if (!$user->authorize($authorize)) {
  743. $event->setStatus($event::AUTHORIZATION_DENIED);
  744. $event->stopPropagation();
  745. return;
  746. }
  747. }
  748. }
  749. public function userLoginFailure(UserLoginEvent $event)
  750. {
  751. $this->grav['session']->user = User::load('', false);
  752. }
  753. public function userLogin(UserLoginEvent $event)
  754. {
  755. $session = $this->grav['session'];
  756. $session->user = $event->getUser();
  757. if ($event->getOption('remember_me')) {
  758. /** @var Login $login */
  759. $login = $this->grav['login'];
  760. $session->remember_me = (bool)$event->getOption('remember_me_login');
  761. // If the user wants to be remembered, create Rememberme cookie.
  762. $username = $event->getUser()->get('username');
  763. if ($event->getCredential('rememberme')) {
  764. $login->rememberMe()->createCookie($username);
  765. } else {
  766. $login->rememberMe()->getStorage()->cleanAllTriplets($username);
  767. $login->rememberMe()->clearCookie();
  768. }
  769. }
  770. }
  771. public function userLogout(UserLoginEvent $event)
  772. {
  773. if ($event->getOption('remember_me')) {
  774. /** @var Login $login */
  775. $login = $this->grav['login'];
  776. if (!$login->rememberMe()->login()) {
  777. $login->rememberMe()->getStorage()->cleanAllTriplets($event->getUser()->get('username'));
  778. }
  779. $login->rememberMe()->clearCookie();
  780. }
  781. $this->grav['session']->invalidate()->start();
  782. $this->grav['session']->setFlashCookieObject(self::TMP_COOKIE_NAME, ['message' => $this->grav['language']->translate('PLUGIN_LOGIN.LOGGED_OUT'),
  783. 'status' => 'info']);
  784. }
  785. }