1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237 |
- <?php
- /**
- * @package Grav\Plugin\Login
- *
- * @copyright Copyright (C) 2014 - 2020 RocketTheme, LLC. All rights reserved.
- * @license MIT License; see LICENSE file for details.
- */
- namespace Grav\Plugin;
- use Composer\Autoload\ClassLoader;
- use Grav\Common\Data\Data;
- use Grav\Common\Debugger;
- use Grav\Common\Flex\Types\Users\UserObject;
- use Grav\Common\Grav;
- use Grav\Common\Language\Language;
- use Grav\Common\Page\Interfaces\PageInterface;
- use Grav\Common\Page\Page;
- use Grav\Common\Page\Pages;
- use Grav\Common\Plugin;
- use Grav\Common\Twig\Twig;
- use Grav\Common\User\Interfaces\UserCollectionInterface;
- use Grav\Common\User\Interfaces\UserInterface;
- use Grav\Common\Utils;
- use Grav\Common\Uri;
- use Grav\Events\SessionStartEvent;
- use Grav\Framework\Flex\Interfaces\FlexCollectionInterface;
- use Grav\Framework\Flex\Interfaces\FlexObjectInterface;
- use Grav\Framework\Session\SessionInterface;
- use Grav\Plugin\Form\Form;
- use Grav\Plugin\Login\Events\UserLoginEvent;
- use Grav\Plugin\Login\Login;
- use Grav\Plugin\Login\Controller;
- use Grav\Plugin\Login\RememberMe\RememberMe;
- use RocketTheme\Toolbox\Event\Event;
- use RocketTheme\Toolbox\Session\Message;
- /**
- * Class LoginPlugin
- * @package Grav\Plugin\Login
- */
- class LoginPlugin extends Plugin
- {
- const TMP_COOKIE_NAME = 'tmp-message';
- /** @var string */
- protected $route;
- /** @var bool */
- protected $authenticated = true;
- /** @var Login */
- protected $login;
- /** @var bool */
- protected $redirect_to_login;
- /**
- * @return array
- */
- public static function getSubscribedEvents()
- {
- return [
- SessionStartEvent::class => ['onSessionStart', 0],
- 'onPluginsInitialized' => [['autoload', 100000], ['initializeSession', 10000], ['initializeLogin', 1000]],
- 'onTask.login.login' => ['loginController', 0],
- 'onTask.login.twofa' => ['loginController', 0],
- 'onTask.login.twofa_cancel' => ['loginController', 0],
- 'onTask.login.forgot' => ['loginController', 0],
- 'onTask.login.logout' => ['loginController', 0],
- 'onTask.login.reset' => ['loginController', 0],
- 'onTask.login.regenerate2FASecret' => ['loginController', 0],
- 'onPagesInitialized' => ['storeReferrerPage', 0],
- 'onPageInitialized' => ['authorizePage', 0],
- 'onPageFallBackUrl' => ['authorizeFallBackUrl', 0],
- 'onTwigTemplatePaths' => ['onTwigTemplatePaths', 0],
- 'onTwigSiteVariables' => ['onTwigSiteVariables', -100000],
- 'onFormProcessed' => ['onFormProcessed', 0],
- 'onUserLoginAuthenticate' => [['userLoginAuthenticateRateLimit', 10003], ['userLoginAuthenticateByRegistration', 10002], ['userLoginAuthenticateByRememberMe', 10001], ['userLoginAuthenticateByEmail', 10000], ['userLoginAuthenticate', 0]],
- 'onUserLoginAuthorize' => ['userLoginAuthorize', 0],
- 'onUserLoginFailure' => ['userLoginGuest', 0],
- 'onUserLoginGuest' => ['userLoginGuest', 0],
- 'onUserLogin' => [['userLoginResetRateLimit', 1000], ['userLogin', 0]],
- 'onUserLogout' => ['userLogout', 0],
- ];
- }
- /**
- * [onPluginsInitialized:100000] Composer autoload.
- *
- * @return ClassLoader
- */
- public function autoload() : ClassLoader
- {
- return require __DIR__ . '/vendor/autoload.php';
- }
- public function onSessionStart(SessionStartEvent $event)
- {
- $session = $event->session;
- $user = $session->user ?? null;
- if ($user && $user->exists() && ($this->config()['session_user_sync'] ?? false)) {
- // User is stored into the filesystem.
- /** @var UserCollectionInterface $accounts */
- $accounts = $this->grav['accounts'];
- /** @var UserObject $stored */
- if ($accounts instanceof FlexCollectionInterface) {
- $stored = $accounts[$user->username];
- } else {
- // TODO: remove when removing legacy support.
- $stored = $accounts->load($user->username);
- }
- if ($stored && $stored->exists()) {
- // User still exists, update user object in the session.
- $user->update($stored->jsonSerialize());
- } else {
- // User doesn't exist anymore, prepare for session invalidation.
- $user->state = 'disabled';
- }
- if ($user->state !== 'enabled') {
- // If user isn't enabled, clear all session data and display error.
- $session->invalidate()->start();
- /** @var Message $messages */
- $messages = $this->grav['messages'];
- $messages->add($this->grav['language']->translate('PLUGIN_LOGIN.USER_ACCOUNT_DISABLED'), 'error');
- }
- }
- }
- /**
- * [onPluginsInitialized] Initialize login plugin if path matches.
- * @throws \RuntimeException
- */
- public function initializeSession()
- {
- // Check to ensure sessions are enabled.
- if (!$this->config->get('system.session.enabled')) {
- throw new \RuntimeException('The Login plugin requires "system.session" to be enabled');
- }
- // Define login service.
- $this->grav['login'] = static function (Grav $c) {
- return new Login($c);
- };
- // Define current user service.
- $this->grav['user'] = static function (Grav $c) {
- $session = $c['session'];
- if (empty($session->user)) {
- // Try remember me login.
- $session->user = $c['login']->login(
- ['username' => ''],
- ['remember_me' => true, 'remember_me_login' => true, 'failureEvent' => 'onUserLoginGuest']
- );
- }
- return $session->user;
- };
- }
- /**
- * [onPluginsInitialized] Initialize login plugin if path matches.
- * @throws \RuntimeException
- */
- public function initializeLogin()
- {
- $this->login = $this->grav['login'];
- /** @var Uri $uri */
- $uri = $this->grav['uri'];
- // Admin has its own login; make sure we're not in admin.
- if (!isset($this->grav['admin'])) {
- $this->route = $this->config->get('plugins.login.route');
- $this->enable([
- 'onPagesInitialized' => ['pageVisibility', 0],
- ]);
- }
- $path = $uri->path();
- $this->redirect_to_login = $this->config->get('plugins.login.redirect_to_login');
- // Register route to login page if it has been set.
- if ($this->route && $this->route === $path) {
- $this->enable([
- 'onPagesInitialized' => ['addLoginPage', 0],
- ]);
- return;
- }
- if ($path === $this->config->get('plugins.login.route_forgot')) {
- $this->enable([
- 'onPagesInitialized' => ['addForgotPage', 0],
- ]);
- return;
- }
- if ($path === $this->config->get('plugins.login.route_reset')) {
- $this->enable([
- 'onPagesInitialized' => ['addResetPage', 0],
- ]);
- return;
- }
- if ($path === $this->config->get('plugins.login.route_register')) {
- if ($this->config->get('plugins.login.user_registration.enabled')) {
- $this->enable([
- 'onPagesInitialized' => ['addRegisterPage', 0],
- ]);
- } else {
- throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.REGISTRATION_DISABLED'), 404);
- }
- return;
- }
- if ($path === $this->config->get('plugins.login.route_activate')) {
- $this->enable([
- 'onPagesInitialized' => ['handleUserActivation', 0],
- ]);
- return;
- }
- if ($path === $this->config->get('plugins.login.route_profile')) {
- $this->enable([
- 'onPagesInitialized' => ['addProfilePage', 0],
- ]);
- return;
- }
- }
- /**
- * Optional ability to dynamically set visibility based on page access and page header
- * that states `login.visibility_requires_access: true`
- *
- * Note that this setting may be slow on large sites as it loads all pages into memory for each page load!
- *
- * @param Event $e
- */
- public function pageVisibility(Event $e)
- {
- if ($this->config->get('plugins.login.dynamic_page_visibility')) {
- /** @var Pages $pages */
- $pages = $e['pages'];
- $user = $this->grav['user'];
- foreach ($pages->instances() as $page) {
- if ($page) {
- $header = $page->header();
- if ($header && isset($header->access) && isset($header->login['visibility_requires_access']) && $header->login['visibility_requires_access'] === true) {
- $config = $this->mergeConfig($page);
- $access = $this->login->isUserAuthorizedForPage($user, $page, $config);
- if ($access === false) {
- $page->visible(false);
- }
- }
- }
- }
- }
- }
- /**
- * [onPagesInitialized]
- */
- public function storeReferrerPage()
- {
- $invalid_redirect_routes = [
- $this->config->get('plugins.login.route') ?: '/login',
- $this->config->get('plugins.login.route_register') ?: '/register',
- $this->config->get('plugins.login.route_activate') ?: '/activate_user',
- $this->config->get('plugins.login.route_forgot') ?: '/forgot_password',
- $this->config->get('plugins.login.route_reset') ?: '/reset_password',
- ];
- /** @var Uri $uri */
- $uri = $this->grav['uri'];
- $current_route = $uri->route();
- $redirect = static::defaultRedirectAfterLogin();
- if (!$redirect && !in_array($current_route, $invalid_redirect_routes, true)) {
- // No login redirect set in the configuration; can we redirect to the current page?
- $allowed = true;
- /** @var PageInterface $page */
- $page = $this->grav['pages']->dispatch($current_route);
- if ($page) {
- $header = $page->header();
- if (isset($header->login_redirect_here) && $header->login_redirect_here === false) {
- $allowed = false;
- }
- if ($allowed && $page->routable()) {
- $redirect = $page->route();
- foreach ($uri->params(null, true) as $key => $value) {
- if (!in_array($key, ['task', 'nonce', 'login-nonce', 'logout-nonce'], true)) {
- $redirect .= $uri->params($key);
- }
- }
- }
- }
- } else {
- $redirect = $this->grav['session']->redirect_after_login;
- }
- $this->grav['session']->redirect_after_login = $redirect;
- }
- /**
- * Add Login page
- * @throws \Exception
- */
- public function addLoginPage()
- {
- /** @var Pages $pages */
- $pages = $this->grav['pages'];
- $page = $pages->dispatch($this->route);
- if (!$page) {
- // Only add login page if it hasn't already been defined.
- $page = new Page();
- $page->init(new \SplFileInfo(__DIR__ . '/pages/login.md'));
- $page->slug(basename($this->route));
- $pages->addPage($page, $this->route);
- }
- }
- /**
- * Add Login page
- * @throws \Exception
- */
- public function addForgotPage()
- {
- $route = $this->config->get('plugins.login.route_forgot');
- /** @var Pages $pages */
- $pages = $this->grav['pages'];
- $page = $pages->dispatch($route);
- if (!$page) {
- // Only add forgot page if it hasn't already been defined.
- $page = new Page();
- $page->init(new \SplFileInfo(__DIR__ . '/pages/forgot.md'));
- $page->slug(basename($route));
- $pages->addPage($page, $route);
- }
- }
- /**
- * Add Reset page
- * @throws \Exception
- */
- public function addResetPage()
- {
- $route = $this->config->get('plugins.login.route_reset');
- $uri = $this->grav['uri'];
- $token = $uri->param('token');
- $user = $uri->param('user');
- if (!$user || !$token) {
- return;
- }
- /** @var Pages $pages */
- $pages = $this->grav['pages'];
- $page = $pages->dispatch($route);
- if (!$page) {
- // Only add login page if it hasn't already been defined.
- $page = new Page();
- $page->init(new \SplFileInfo(__DIR__ . '/pages/reset.md'));
- $page->slug(basename($route));
- $pages->addPage($page, $route);
- }
- }
- /**
- * Add Register page
- * @throws \Exception
- */
- public function addRegisterPage()
- {
- $route = $this->config->get('plugins.login.route_register');
- /** @var Pages $pages */
- $pages = $this->grav['pages'];
- $page = $pages->dispatch($route);
- if (!$page) {
- $page = new Page();
- $page->init(new \SplFileInfo(__DIR__ . '/pages/register.md'));
- $page->slug(basename($route));
- $pages->addPage($page, $route);
- }
- }
- /**
- * Handle user activation
- * @throws \RuntimeException
- */
- public function handleUserActivation()
- {
- /** @var Uri $uri */
- $uri = $this->grav['uri'];
- /** @var Message $messages */
- $messages = $this->grav['messages'];
- /** @var UserCollectionInterface $users */
- $users = $this->grav['accounts'];
- $username = $uri->param('username');
- $token = $uri->param('token');
- $user = $users->load($username);
- $redirect_route = $this->config->get('plugins.login.user_registration.redirect_after_activation');
- $redirect_code = null;
- if (empty($user->activation_token)) {
- $message = $this->grav['language']->translate('PLUGIN_LOGIN.INVALID_REQUEST');
- $messages->add($message, 'error');
- } else {
- [$good_token, $expire] = explode('::', $user->activation_token, 2);
- if ($good_token === $token) {
- if (time() > $expire) {
- $message = $this->grav['language']->translate('PLUGIN_LOGIN.ACTIVATION_LINK_EXPIRED');
- $messages->add($message, 'error');
- } else {
- if ($this->config->get('plugins.login.user_registration.options.manually_enable', false)) {
- $message = $this->grav['language']->translate('PLUGIN_LOGIN.USER_ACTIVATED_SUCCESSFULLY_NOT_ENABLED');
- } else {
- $user['state'] = 'enabled';
- $message = $this->grav['language']->translate('PLUGIN_LOGIN.USER_ACTIVATED_SUCCESSFULLY');
- }
- $messages->add($message, 'info');
- unset($user->activation_token);
- $user->save();
- if ($this->config->get('plugins.login.user_registration.options.send_welcome_email', false)) {
- $this->login->sendWelcomeEmail($user);
- }
- if ($this->config->get('plugins.login.user_registration.options.send_notification_email', false)) {
- $this->login->sendNotificationEmail($user);
- }
- if ($this->config->get('plugins.login.user_registration.options.login_after_registration', false)) {
- $loginEvent = $this->login->login(['username' => $username], ['after_registration' => true], ['user' => $user, 'return_event' => true]);
- // If there's no activation redirect, get one from login.
- if (!$redirect_route) {
- $message = $loginEvent->getMessage();
- if ($message) {
- $messages->add($message, $loginEvent->getMessageType());
- }
- $redirect_route = $loginEvent->getRedirect();
- $redirect_code = $loginEvent->getRedirectCode();
- }
- }
- $this->grav->fireEvent('onUserActivated', new Event(['user' => $user]));
- }
- } else {
- $message = $this->grav['language']->translate('PLUGIN_LOGIN.INVALID_REQUEST');
- $messages->add($message, 'error');
- }
- }
- $this->grav->redirectLangSafe($redirect_route ?: '/', $redirect_code);
- }
- /**
- * Add Profile page
- */
- public function addProfilePage()
- {
- $route = $this->config->get('plugins.login.route_profile');
- /** @var Pages $pages */
- $pages = $this->grav['pages'];
- $page = $pages->dispatch($route);
- if (!$page) {
- // Only add forgot page if it hasn't already been defined.
- $page = new Page();
- $page->init(new \SplFileInfo(__DIR__ . '/pages/profile.md'));
- $page->slug(basename($route));
- $pages->addPage($page, $route);
- }
- $this->storeReferrerPage();
- }
- /**
- * Set Unauthorized page
- * @throws \Exception
- */
- public function setUnauthorizedPage()
- {
- $route = $this->config->get('plugins.login.route_unauthorized');
- /** @var Pages $pages */
- $pages = $this->grav['pages'];
- $page = $pages->dispatch($route);
- if (!$page) {
- $page = new Page();
- $page->init(new \SplFileInfo(__DIR__ . '/pages/unauthorized.md'));
- $page->slug(basename($route));
- $pages->addPage($page, $route);
- }
- unset($this->grav['page']);
- $this->grav['page'] = $page;
- }
- /**
- * Initialize login controller
- */
- public function loginController()
- {
- /** @var Uri $uri */
- $uri = $this->grav['uri'];
- $task = $_POST['task'] ?? $uri->param('task');
- $task = substr($task, \strlen('login.'));
- $post = !empty($_POST) ? $_POST : [];
- switch ($task) {
- case 'login':
- if (!isset($post['login-form-nonce']) || !Utils::verifyNonce($post['login-form-nonce'], 'login-form')) {
- $this->grav['messages']->add($this->grav['language']->translate('PLUGIN_LOGIN.ACCESS_DENIED'),
- 'info');
- $twig = $this->grav['twig'];
- $twig->twig_vars['notAuthorized'] = true;
- return;
- }
- break;
- case 'forgot':
- if (!isset($post['forgot-form-nonce']) || !Utils::verifyNonce($post['forgot-form-nonce'], 'forgot-form')) {
- $this->grav['messages']->add($this->grav['language']->translate('PLUGIN_LOGIN.ACCESS_DENIED'),'info');
- return;
- }
- break;
- }
- $controller = new Controller($this->grav, $task, $post);
- $controller->execute();
- $controller->redirect();
- }
- /**
- * Authorize the Page fallback url (page media accessed through the page route)
- */
- public function authorizeFallBackUrl()
- {
- if ($this->config->get('plugins.login.protect_protected_page_media', false)) {
- $page_url = \dirname($this->grav['uri']->path());
- $page = $this->grav['pages']->find($page_url);
- unset($this->grav['page']);
- $this->grav['page'] = $page;
- $this->authorizePage();
- }
- }
- /**
- * [onPageInitialized] Authorize Page
- */
- public function authorizePage()
- {
- if (!$this->authenticated) {
- return;
- }
- /** @var UserInterface $user */
- $user = $this->grav['user'];
- /** @var PageInterface $page */
- $page = $this->grav['page'];
- if (!$page || $this->grav['login']->isUserAuthorizedForPage($user, $page, $this->mergeConfig($page))) {
- return;
- }
- // If this is not an HTML page request, simply throw a 403 error
- $uri_extension = $this->grav['uri']->extension('html');
- $supported_types = $this->config->get('media.types');
- if ($uri_extension !== 'html' && array_key_exists($uri_extension, $supported_types)) {
- header('HTTP/1.0 403 Forbidden');
- exit;
- }
- $authorized = $user->authenticated && $user->authorized;
- // User is not logged in; redirect to login page.
- if ($this->redirect_to_login && $this->route && !$authorized) {
- $this->grav->redirectLangSafe($this->route, 302);
- }
- /** @var Twig $twig */
- $twig = $this->grav['twig'];
- $login_page = null;
- // Reset page with login page.
- if (!$authorized) {
- if ($this->route) {
- $login_page = $this->grav['pages']->dispatch($this->route);
- }
- if (!$login_page) {
- $login_page = new Page();
- // Get the admin Login page is needed, else the default
- if ($this->isAdmin()) {
- $login_file = $this->grav['locator']->findResource('plugins://admin/pages/admin/login.md');
- $login_page->init(new \SplFileInfo($login_file));
- } else {
- $login_page->init(new \SplFileInfo(__DIR__ . '/pages/login.md'));
- }
- $login_page->slug(basename($this->route));
- /** @var Pages $pages */
- $pages = $this->grav['pages'];
- $pages->addPage($login_page, $this->route);
- }
- $this->authenticated = false;
- unset($this->grav['page']);
- $this->grav['page'] = $login_page;
- $twig->twig_vars['form'] = new Form($login_page);
- } else {
- /** @var Language $l */
- $l = $this->grav['language'];
- $this->grav['messages']->add($l->translate('PLUGIN_LOGIN.ACCESS_DENIED'), 'error');
- $twig->twig_vars['notAuthorized'] = true;
- $this->setUnauthorizedPage();
- }
- }
- /**
- * [onTwigTemplatePaths] Add twig paths to plugin templates.
- */
- public function onTwigTemplatePaths()
- {
- $twig = $this->grav['twig'];
- $twig->twig_paths[] = __DIR__ . '/templates';
- }
- /**
- * [onTwigSiteVariables] Set all twig variables for generating output.
- */
- public function onTwigSiteVariables()
- {
- /** @var Twig $twig */
- $twig = $this->grav['twig'];
- $this->grav->fireEvent('onLoginPage');
- $extension = $this->grav['uri']->extension();
- $extension = $extension ?: 'html';
- if (!$this->authenticated) {
- $twig->template = "login.{$extension}.twig";
- }
- // add CSS for frontend if required
- if (!$this->isAdmin() && $this->config->get('plugins.login.built_in_css')) {
- $this->grav['assets']->add('plugin://login/css/login.css');
- }
- $task = $this->grav['uri']->param('task') ?: ($_POST['task'] ?? '');
- $task = substr($task, \strlen('login.'));
- if ($task === 'reset') {
- $username = $this->grav['uri']->param('user');
- $token = $this->grav['uri']->param('token');
- if (!empty($username) && !empty($token)) {
- $twig->twig_vars['username'] = $username;
- $twig->twig_vars['token'] = $token;
- }
- } elseif ($task === 'login') {
- $twig->twig_vars['username'] = $_POST['username'] ?? '';
- }
- $flashData = $this->grav['session']->getFlashCookieObject(self::TMP_COOKIE_NAME);
- if (isset($flashData->message)) {
- $this->grav['messages']->add($flashData->message, $flashData->status);
- }
- }
- /**
- * Process the user registration, triggered by a registration form
- *
- * @param Form $form
- * @throws \RuntimeException
- */
- private function processUserRegistration($form, Event $event)
- {
- $language = $this->grav['language'];
- $messages = $this->grav['messages'];
- if (!$this->config->get('plugins.login.enabled')) {
- throw new \RuntimeException($language->translate('PLUGIN_LOGIN.PLUGIN_LOGIN_DISABLED'));
- }
- if (!$this->config->get('plugins.login.user_registration.enabled')) {
- throw new \RuntimeException($language->translate('PLUGIN_LOGIN.USER_REGISTRATION_DISABLED'));
- }
- $form->validate();
- /** @var Data $form_data */
- $form_data = $form->getData();
- /** @var UserCollectionInterface $users */
- $users = $this->grav['accounts'];
- // Check for existing username
- $username = $form_data->get('username');
- $existing_username = $users->find($username, ['username']);
- if ($existing_username->exists()) {
- $this->grav->fireEvent('onFormValidationError', new Event([
- 'form' => $form,
- 'message' => $language->translate([
- 'PLUGIN_LOGIN.USERNAME_NOT_AVAILABLE',
- $username
- ])
- ]));
- $event->stopPropagation();
- return;
- }
- // Check for existing email
- $email = $form_data->get('email');
- $existing_email = $users->find($email, ['email']);
- if ($existing_email->exists()) {
- $this->grav->fireEvent('onFormValidationError', new Event([
- 'form' => $form,
- 'message' => $language->translate([
- 'PLUGIN_LOGIN.EMAIL_NOT_AVAILABLE',
- $email
- ])
- ]));
- $event->stopPropagation();
- return;
- }
- $data = [];
- $data['username'] = $username;
- // if multiple password fields, check they match and set password field from it
- if ($this->config->get('plugins.login.user_registration.options.validate_password1_and_password2',
- false)
- ) {
- if ($form_data->get('password1') !== $form_data->get('password2')) {
- $this->grav->fireEvent('onFormValidationError', new Event([
- 'form' => $form,
- 'message' => $language->translate('PLUGIN_LOGIN.PASSWORDS_DO_NOT_MATCH')
- ]));
- $event->stopPropagation();
- return;
- }
- $data['password'] = $form_data->get('password1');
- }
- $fields = (array)$this->config->get('plugins.login.user_registration.fields', []);
- foreach ($fields as $field) {
- // Process value of field if set in the page process.register_user
- $default_values = (array)$this->config->get('plugins.login.user_registration.default_values');
- if ($default_values) {
- foreach ($default_values as $key => $param) {
- if ($key === $field) {
- if (\is_array($param)) {
- $values = explode(',', $param);
- } else {
- $values = $param;
- }
- $data[$field] = $values;
- }
- }
- }
- if (!isset($data[$field]) && $form_data->get($field)) {
- $data[$field] = $form_data->get($field);
- }
- }
- if ($this->config->get('plugins.login.user_registration.options.set_user_disabled', false)) {
- $data['state'] = 'disabled';
- } else {
- $data['state'] = 'enabled';
- }
- $data_object = (object) $data;
- $this->grav->fireEvent('onUserLoginRegisterData', new Event(['data' => &$data_object]));
- $flash = $form->getFlash();
- $user = $this->login->register((array)$data_object, $flash->getFilesByFields(true));
- if ($user instanceof FlexObjectInterface) {
- $flash->clearFiles();
- $flash->save();
- }
- $this->grav->fireEvent('onUserLoginRegisteredUser', new Event(['user' => &$user]));
- $fullname = $user->fullname ?? $user->username;
- if ($this->config->get('plugins.login.user_registration.options.send_activation_email', false)) {
- $this->login->sendActivationEmail($user);
- $message = $language->translate(['PLUGIN_LOGIN.ACTIVATION_NOTICE_MSG', $fullname]);
- $messages->add($message, 'info');
- } else {
- if ($this->config->get('plugins.login.user_registration.options.send_welcome_email', false)) {
- $this->login->sendWelcomeEmail($user);
- }
- if ($this->config->get('plugins.login.user_registration.options.send_notification_email', false)) {
- $this->login->sendNotificationEmail($user);
- }
- $message = $language->translate(['PLUGIN_LOGIN.WELCOME_NOTICE_MSG', $fullname]);
- $messages->add($message, 'info');
- }
- $this->grav->fireEvent('onUserLoginRegistered', new Event(['user' => $user]));
- $redirect = $this->config->get('plugins.login.user_registration.redirect_after_registration');
- $redirect_code = null;
- if (isset($user['state']) && $user['state'] === 'enabled' && $this->config->get('plugins.login.user_registration.options.login_after_registration', false)) {
- $loginEvent = $this->login->login(['username' => $user->username], ['after_registration' => true], ['user' => $user, 'return_event' => true]);
- // If there's no registration redirect, get one from login.
- if (!$redirect) {
- $message = $loginEvent->getMessage();
- if ($message) {
- $messages->add($message, $loginEvent->getMessageType());
- }
- $redirect = $loginEvent->getRedirect();
- $redirect_code = $loginEvent->getRedirectCode();
- }
- }
- if ($redirect) {
- $event['redirect'] = $redirect;
- $event['redirect_code'] = $redirect_code;
- }
- }
- /**
- * Save user profile information
- *
- * @param Form $form
- * @param Event $event
- * @return bool
- */
- private function processUserProfile($form, Event $event)
- {
- /** @var UserInterface $user */
- $user = $this->grav['user'];
- $language = $this->grav['language'];
- $form->validate();
- /** @var Data $form_data */
- $form_data = $form->getData();
- // Don't save if user doesn't exist
- if (!$user->exists()) {
- $this->grav->fireEvent('onFormValidationError', new Event([
- 'form' => $form,
- 'message' => $language->translate('PLUGIN_LOGIN.USER_IS_REMOTE_ONLY')
- ]));
- $event->stopPropagation();
- return false;
- }
- // Stop overloading of username
- $username = $form->data('username');
- if (isset($username)) {
- $this->grav->fireEvent('onFormValidationError', new Event([
- 'form' => $form,
- 'message' => $language->translate([
- 'PLUGIN_LOGIN.USERNAME_NOT_AVAILABLE',
- $username
- ])
- ]));
- $event->stopPropagation();
- return false;
- }
- /** @var UserCollectionInterface $users */
- $users = $this->grav['accounts'];
- // Check for existing email
- $email = $form->getData('email');
- $existing_email = $users->find($email, ['email']);
- if ($user->username !== $existing_email->username && $existing_email->exists()) {
- $this->grav->fireEvent('onFormValidationError', new Event([
- 'form' => $form,
- 'message' => $language->translate([
- 'PLUGIN_LOGIN.EMAIL_NOT_AVAILABLE',
- $email
- ])
- ]));
- $event->stopPropagation();
- return false;
- }
- $fields = (array)$this->config->get('plugins.login.user_registration.fields', []);
- $data = [];
- foreach ($fields as $field) {
- $data_field = $form_data->get($field);
- if (!isset($data[$field]) && isset($data_field)) {
- $data[$field] = $form_data->get($field);
- }
- }
- try {
- $flash = $form->getFlash();
- $user->update($data, $flash->getFilesByFields(true));
- $user->save();
- if ($user instanceof FlexObjectInterface) {
- $flash->clearFiles();
- $flash->save();
- }
- } catch (\Exception $e) {
- $form->setMessage($e->getMessage(), 'error');
- return false;
- }
- return true;
- }
- /**
- * [onFormProcessed] Process a registration form. Handles the following actions:
- *
- * - register_user: registers a user
- * - update_user: updates user profile
- *
- * @param Event $event
- * @throws \RuntimeException
- */
- public function onFormProcessed(Event $event)
- {
- $form = $event['form'];
- $action = $event['action'];
- switch ($action) {
- case 'register_user':
- $this->processUserRegistration($form, $event);
- break;
- case 'update_user':
- $this->processUserProfile($form, $event);
- break;
- }
- }
- /**
- * @param UserLoginEvent $event
- * @throws \RuntimeException
- */
- public function userLoginAuthenticateRateLimit(UserLoginEvent $event)
- {
- // Check that we're logging in with rate limit turned on.
- if (!$event->getOption('rate_limit')) {
- return;
- }
- $credentials = $event->getCredentials();
- $username = $credentials['username'];
- // Check rate limit for both IP and user, but allow each IP a single try even if user is already rate limited.
- if ($interval = $this->login->checkLoginRateLimit($username)) {
- /** @var Language $t */
- $t = $this->grav['language'];
- $event->setMessage($t->translate(['PLUGIN_LOGIN.TOO_MANY_LOGIN_ATTEMPTS', $interval]), 'error');
- $event->setRedirect($this->grav['config']->get('plugins.login.route', '/'));
- $event->setStatus(UserLoginEvent::AUTHENTICATION_CANCELLED);
- $event->stopPropagation();
- }
- }
- /**
- * @param UserLoginEvent $event
- * @throws \RuntimeException
- */
- public function userLoginAuthenticateByRegistration(UserLoginEvent $event)
- {
- // Check that we're logging in after registration.
- if (!$event->getOption('after_registration') || $this->isAdmin()) {
- return;
- }
- $event->setStatus($event::AUTHENTICATION_SUCCESS);
- $event->stopPropagation();
- }
- /**
- * @param UserLoginEvent $event
- * @throws \RuntimeException
- */
- public function userLoginAuthenticateByRememberMe(UserLoginEvent $event)
- {
- // Check that we're logging in with remember me.
- if (!$event->getOption('remember_me_login') || !$event->getOption('remember_me') || $this->isAdmin()) {
- return;
- }
- // Only use remember me if user isn't set and feature is enabled.
- if ($this->grav['config']->get('plugins.login.rememberme.enabled') && !$event->getUser()->exists()) {
- /** @var Debugger $debugger */
- $debugger = $this->grav['debugger'];
- /** @var RememberMe $rememberMe */
- $rememberMe = $this->grav['login']->rememberMe();
- $username = $rememberMe->login();
- if ($rememberMe->loginTokenWasInvalid()) {
- // Token was invalid. We will display error page as this was likely an attack.
- $debugger->addMessage('Remember Me: Stolen token!');
- throw new \RuntimeException($this->grav['language']->translate('PLUGIN_LOGIN.REMEMBER_ME_STOLEN_COOKIE'), 403);
- }
- if ($username === false) {
- // User has not been remembered, there is no point of continuing.
- $debugger->addMessage('Remember Me: No token matched.');
- $event->setStatus($event::AUTHENTICATION_FAILURE);
- $event->stopPropagation();
- return;
- }
- /** @var UserCollectionInterface $users */
- $users = $this->grav['accounts'];
- // Allow remember me to work with different login methods.
- $user = $users->load($username);
- $event->setCredential('username', $username);
- $event->setUser($user);
- if (!$user->exists()) {
- $debugger->addMessage('Remember Me: User does not exist');
- $event->setStatus($event::AUTHENTICATION_FAILURE);
- $event->stopPropagation();
- return;
- }
- $debugger->addMessage('Remember Me: Authenticated!');
- $event->setStatus($event::AUTHENTICATION_SUCCESS);
- $event->stopPropagation();
- return;
- }
- }
- public function userLoginAuthenticateByEmail(UserLoginEvent $event)
- {
- if (($username = $event->getCredential('username')) && !$event->getUser()->exists()) {
- /** @var UserCollectionInterface $users */
- $users = $this->grav['accounts'];
- $event->setUser($users->find($username));
- }
- }
- public function userLoginAuthenticate(UserLoginEvent $event)
- {
- $user = $event->getUser();
- $credentials = $event->getCredentials();
- if (!$user->exists()) {
- // Never let non-existing users to pass the authentication.
- // Higher level plugins may override this behavior by stopping propagation.
- $event->setStatus($event::AUTHENTICATION_FAILURE);
- $event->stopPropagation();
- return;
- }
- // Never let empty password to pass the authentication.
- // Higher level plugins may override this behavior by stopping propagation.
- if (empty($credentials['password'])) {
- $event->setStatus($event::AUTHENTICATION_FAILURE);
- $event->stopPropagation();
- return;
- }
- // Try default user authentication. Stop propagation if authentication succeeds.
- if ($user->authenticate($credentials['password'])) {
- $event->setStatus($event::AUTHENTICATION_SUCCESS);
- $event->stopPropagation();
- return;
- }
- // If authentication status is undefined, lower level event handlers may still be able to authenticate user.
- }
- public function userLoginAuthorize(UserLoginEvent $event)
- {
- // Always block access if authorize defaulting to site.login fails.
- $user = $event->getUser();
- foreach ($event->getAuthorize() as $authorize) {
- if (!$user->authorize($authorize)) {
- if ($user->state !== 'enabled') {
- $event->setMessage($this->grav['language']->translate('PLUGIN_LOGIN.USER_ACCOUNT_DISABLED'), 'error');
- }
- $event->setStatus($event::AUTHORIZATION_DENIED);
- $event->stopPropagation();
- return;
- }
- }
- if ($event->getOption('twofa') && $user->twofa_enabled && $user->twofa_secret) {
- $event->setStatus($event::AUTHORIZATION_DELAYED);
- }
- }
- public function userLoginGuest(UserLoginEvent $event)
- {
- /** @var UserCollectionInterface $users */
- $users = $this->grav['accounts'];
- $user = $users->load('');
- $event->setUser($user);
- $this->grav['session']->user = $user;
- }
- public function userLoginResetRateLimit(UserLoginEvent $event)
- {
- if ($event->getOption('rate_limit')) {
- // Reset user rate limit.
- $user = $event->getUser();
- $this->login->resetLoginRateLimit($user->get('username'));
- }
- }
- public function userLogin(UserLoginEvent $event)
- {
- /** @var SessionInterface $session */
- $session = $this->grav['session'];
- // Prevent session fixation if supported.
- // TODO: remove method_exists() test when requiring Grav v1.7
- if (method_exists($session, 'regenerateId')) {
- $session->regenerateId();
- }
- $session->user = $user = $event->getUser();
- if ($event->getOption('remember_me')) {
- /** @var Login $login */
- $login = $this->grav['login'];
- $session->remember_me = (bool)$event->getOption('remember_me_login');
- // If the user wants to be remembered, create Rememberme cookie.
- $username = $user->get('username');
- if ($event->getCredential('rememberme')) {
- $login->rememberMe()->createCookie($username);
- }
- }
- }
- public function userLogout(UserLoginEvent $event)
- {
- if ($event->getOption('remember_me')) {
- /** @var Login $login */
- $login = $this->grav['login'];
- if (!$login->rememberMe()->login()) {
- $login->rememberMe()->getStorage()->cleanAllTriplets($event->getUser()->get('username'));
- }
- $login->rememberMe()->clearCookie();
- }
- /** @var SessionInterface $session */
- $session = $this->grav['session'];
- // Clear all session data.
- $session->invalidate()->start();
- }
- public static function defaultRedirectAfterLogin()
- {
- $config = Grav::instance()['config'];
- $redirect_after_login = $config->get('plugins.login.redirect_after_login');
- $route_after_login = $config->get('plugins.login.route_after_login');
- return is_bool($redirect_after_login) && $redirect_after_login == true ? $route_after_login : $redirect_after_login;
- }
- public static function defaultRedirectAfterLogout()
- {
- $config = Grav::instance()['config'];
- $redirect_after_logout = $config->get('plugins.logout.redirect_after_logout');
- $route_after_logout = $config->get('plugins.logout.route_after_logout');
- return is_bool($redirect_after_logout) && $redirect_after_logout == true ? $route_after_logout : $redirect_after_logout;
- }
- }
|