From 49d0348b4a874779f3a4b74e3cd49feb6ee81d4f Mon Sep 17 00:00:00 2001 From: Ryan Cramer Date: Fri, 21 Jun 2019 11:09:53 -0400 Subject: [PATCH] Fix issue processwire/processwire-issues#902 --- .../Process/ProcessLogin/ProcessLogin.module | 71 +++++++++++++++---- 1 file changed, 57 insertions(+), 14 deletions(-) diff --git a/wire/modules/Process/ProcessLogin/ProcessLogin.module b/wire/modules/Process/ProcessLogin/ProcessLogin.module index 5de18f0a..6ed7f326 100644 --- a/wire/modules/Process/ProcessLogin/ProcessLogin.module +++ b/wire/modules/Process/ProcessLogin/ProcessLogin.module @@ -8,7 +8,7 @@ * For more details about how Process modules work, please see: * /wire/core/Process.php * - * ProcessWire 3.x, Copyright 2018 by Ryan Cramer + * ProcessWire 3.x, Copyright 2019 by Ryan Cramer * https://processwire.com * * @property bool $allowForgot Whether the ProcessForgotPassword module is installed. @@ -25,6 +25,7 @@ * @method void login($name, $pass) #pw-hooker * @method void loginFailed($name) #pw-hooker * @method void loginSuccess(User $user) #pw-hooker + * @method array getBeforeLoginVars() #pw-hooker * * */ @@ -35,7 +36,7 @@ class ProcessLogin extends Process implements ConfigurableModule { return array( 'title' => 'Login', 'summary' => 'Login to ProcessWire', - 'version' => 106, + 'version' => 107, 'permanent' => true, 'permission' => 'page-view', ); @@ -66,6 +67,8 @@ class ProcessLogin extends Process implements ConfigurableModule { protected $form; /** + * Requested page edit ID (no longer used, but kept in case anything else monitoring it) + * * @var int * */ @@ -118,7 +121,7 @@ class ProcessLogin extends Process implements ConfigurableModule { */ public function init() { - $this->id = isset($_GET['id']) ? (int) $_GET['id'] : ''; + $this->id = isset($_GET['id']) ? (int) $_GET['id'] : ''; // id no longer used as anything but a toggle (on/off) $this->allowForgot = $this->modules->isInstalled('ProcessForgotPassword'); $this->isAdmin = $this->wire('page')->template == 'admin'; @@ -199,6 +202,9 @@ class ProcessLogin extends Process implements ConfigurableModule { } // fallback if nothing set return $this->afterLoginOutput(); + + } else if($this->wire('input')->urlSegmentStr() === 'logout') { + $session->redirect('../'); } $tfa = null; @@ -230,7 +236,7 @@ class ProcessLogin extends Process implements ConfigurableModule { if($loginSubmit) { $this->form->processInput($input->post); } else { - if($this->isAdmin) $this->beforeLogin(); + $this->beforeLogin(); return $this->renderLoginForm(); } @@ -289,7 +295,7 @@ class ProcessLogin extends Process implements ConfigurableModule { public function ___executeLogout() { if($this->logoutURL) { $url = $this->logoutURL; - } else if($this->isAdmin) { + } else if($this->isAdmin || $this->wire('page')->template == 'admin') { $url = $this->config->urls->admin; $this->message($this->_("You have logged out")); } else { @@ -308,8 +314,17 @@ class ProcessLogin extends Process implements ConfigurableModule { */ protected function ___beforeLogin() { + /** @var Session $session */ + $session = $this->wire('session'); + + $beforeLoginVars = $this->getBeforeLoginVars(); + $session->setFor($this, 'beforeLoginVars', $beforeLoginVars); + + // any remaining checks only if currently in the admin + if(!$this->isAdmin) return; + // if checks already completed don't run them again - if($this->wire('session')->get($this, 'beforeLoginChecks')) return; + if($session->getFor($this, 'beforeLoginChecks')) return; if( ini_get('session.save_handler') == 'files' && !$this->wire('modules')->isInstalled('SessionHandlerDB') @@ -356,7 +371,7 @@ class ProcessLogin extends Process implements ConfigurableModule { } } - $this->wire('session')->set($this, 'beforeLoginChecks', 1); + $session->setFor($this, 'beforeLoginChecks', 1); } /** @@ -490,7 +505,7 @@ class ProcessLogin extends Process implements ConfigurableModule { $this->form = $this->modules->get('InputfieldForm'); - // we'll retain an ID field in the GET url, if it was there + // we'll retain an ID field in the GET url, if it was there (note: no longer used as anything but a toggle on/off) $this->form->attr('action', "./" . ($this->id ? "?id={$this->id}" : '')); $this->form->addClass('InputfieldFormFocusFirst'); @@ -599,7 +614,10 @@ class ProcessLogin extends Process implements ConfigurableModule { */ protected function ___afterLoginRedirect($url = '') { $url = $this->afterLoginURL($url); - $this->wire('session')->redirect($url, false); + $session = $this->wire('session'); + $session->removeFor($this, 'beforeLoginVars'); + $session->removeFor($this, 'beforeLoginChecks'); + $session->redirect($url, false); } /** @@ -613,6 +631,7 @@ class ProcessLogin extends Process implements ConfigurableModule { * */ public function ___afterLoginURL($url = '') { + if(empty($url)) { $user = $this->wire('user'); if($this->loginURL) { @@ -627,15 +646,39 @@ class ProcessLogin extends Process implements ConfigurableModule { $url = './'; } } - if($this->id && !preg_match('/[?&]id=/', $url)) { - $url .= (strpos($url, '?') ? '&' : '?') . 'id=' . $this->id; - } - if(strpos($url, 'login=1') === false) { - $url .= (strpos($url, '?') ? '&' : '?') . 'login=1'; + + $beforeLoginVars = $this->wire('session')->getFor($this, 'beforeLoginVars'); + if(!is_array($beforeLoginVars)) $beforeLoginVars = array(); + if(!isset($beforeLoginVars['login'])) $beforeLoginVars['login'] = 1; + $url .= (strpos($url, '?') ? '&' : '?'); + foreach($beforeLoginVars as $name => $value) { + if(strpos($url, "?$name=") !== false || strpos($url, "&$name=") !== false) continue; // skip if overridden + if(!is_int($value)) $value = $this->wire('sanitizer')->entities($value); + $url .= "$name=$value&"; } + $url = rtrim($url, '&'); + return $url; } + /** + * Get validated/sanitized variables in the query string for not logged-in user to retain after login + * + * Hook this if you need to add more than 'id' but make sure anything populated + * to the return value is fully validated and sanitized. + * + * @return array Associative array of variables + * + */ + public function ___getBeforeLoginVars() { + $session = $this->wire('session'); + $vars = $session->getFor($this, 'beforeLoginVars'); + if(!is_array($vars)) $vars = array(); + $id = $this->wire('input')->get('id'); + if($id !== null) $vars['id'] = $this->wire('sanitizer')->intUnsigned($id); + return $vars; + } + /** * Hook called on login fail *