Login.php 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. <?php
  2. /**
  3. * Login
  4. *
  5. * Copyright 2010 by Shaun McCormick <shaun+login@modx.com>
  6. *
  7. * Login is free software; you can redistribute it and/or modify it under the
  8. * terms of the GNU General Public License as published by the Free Software
  9. * Foundation; either version 2 of the License, or (at your option) any later
  10. * version.
  11. *
  12. * Login is distributed in the hope that it will be useful, but WITHOUT ANY
  13. * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  14. * A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU General Public License along with
  17. * Login; if not, write to the Free Software Foundation, Inc., 59 Temple
  18. * Place, Suite 330, Boston, MA 02111-1307 USA
  19. *
  20. * @package login
  21. */
  22. /**
  23. * Handles logging in and out of users
  24. *
  25. * @package login
  26. * @subpackage controllers
  27. */
  28. class LoginLoginController extends LoginController {
  29. public $isAuthenticated = false;
  30. /** @var LoginDictionary $dictionary */
  31. public $dictionary;
  32. public function initialize() {
  33. $this->setDefaultProperties(array(
  34. 'loginViaEmail' => false,
  35. 'loginTpl' => 'lgnLoginTpl',
  36. 'logoutTpl' => 'lgnLogoutTpl',
  37. 'loggedinResourceId' => '',
  38. 'loggedoutResourceId' => '',
  39. 'loginMsg' => '',
  40. 'logoutMsg' => '',
  41. 'preHooks' => '',
  42. 'tplType' => 'modChunk',
  43. 'actionKey' => 'service',
  44. 'loginKey' => 'login',
  45. 'logoutKey' => 'logout',
  46. 'errorPrefix' => 'error',
  47. 'errTpl' => 'lgnErrTpl',
  48. 'errTplType' => 'modChunk',
  49. 'rememberMeKey' => 'rememberme',
  50. 'loginContext' => $this->modx->context->get('key'),
  51. 'contexts' => '',
  52. 'jsonResponse' => false,
  53. ));
  54. if (!empty($_REQUEST['login_context'])) {
  55. $this->setProperty('loginContext',$_REQUEST['login_context']);
  56. }
  57. if (!empty($_REQUEST['add_contexts'])) {
  58. $this->setProperty('contexts',$_REQUEST['add_contexts']);
  59. }
  60. $this->isAuthenticated = $this->modx->user->isAuthenticated($this->getProperty('loginContext'));
  61. }
  62. /**
  63. * Process the controller
  64. * @return string
  65. */
  66. public function process() {
  67. if (!empty($_REQUEST[$this->getProperty('actionKey','service')])) {
  68. $this->handleRequest();
  69. }
  70. $content = $this->renderForm();
  71. return $this->output($content);
  72. }
  73. /**
  74. * Render the logout or login form
  75. * @return string
  76. */
  77. public function renderForm() {
  78. $redirectToPrior = $this->getProperty('redirectToPrior',false,'isset');
  79. $tpl = $this->isAuthenticated ? $this->getProperty('logoutTpl') : $this->getProperty('loginTpl');
  80. $actionMsg = $this->isAuthenticated
  81. ? $this->getProperty('logoutMsg',$this->modx->lexicon('login.logout'))
  82. : $this->getProperty('loginMsg',$this->modx->lexicon('login'));
  83. $this->modx->setPlaceholder('actionMsg', $actionMsg);
  84. $phs = $this->isAuthenticated ? $this->getProperties() : array_merge($this->getProperties(), $_POST);
  85. /* make sure to strip out logout GET parameter to prevent ghost logout */
  86. $logoutKey = $this->getProperty('logoutKey','logout');
  87. if (!$redirectToPrior) {
  88. $phs['request_uri'] = str_replace(array('?service='.$logoutKey,'&service='.$logoutKey,'&amp;service='.$logoutKey),'',$_SERVER['REQUEST_URI']);
  89. } else {
  90. $phs['request_uri'] = str_replace(array('?service='.$logoutKey,'&service='.$logoutKey,'&amp;service='.$logoutKey),'',$_SERVER['HTTP_REFERER']);
  91. }
  92. $phs = $this->escapePlaceholders($phs);
  93. /* properly build logout url */
  94. if ($this->isAuthenticated) {
  95. $phs['logoutUrl'] = $phs['request_uri'];
  96. $phs['logoutUrl'] .= strpos($phs['logoutUrl'],'?') ? ($this->modx->getOption('xhtml_urls',null,false) ? '&amp;' : '&') : '?';
  97. $phs['logoutUrl'] .= $phs['actionKey'].'='.$phs['logoutKey'];
  98. $phs['logoutUrl'] = str_replace(array('?=', '&=', '&amp;='), '', $phs['logoutUrl']);
  99. }
  100. $this->loadReCaptcha();
  101. if ($this->isAuthenticated && $this->getProperty('loggedinResourceId') && $this->modx->resource->get('id') != $this->getProperty('loggedinResourceId')) {
  102. $url = $this->modx->makeUrl($this->getProperty('loggedinResourceId'), '', '', 'full');
  103. $this->modx->sendRedirect($url);
  104. }
  105. if (!$this->isAuthenticated && $this->getProperty('loggedoutResourceId') && $this->modx->resource->get('id') != $this->getProperty('loggedoutResourceId')) {
  106. $url = $this->modx->makeUrl($this->getProperty('loggedoutResourceId'), '', '', 'full');
  107. $this->modx->sendRedirect($url);
  108. }
  109. return $this->login->getChunk($tpl,$phs,$this->getProperty('tplType','modChunk'));
  110. }
  111. /**
  112. * Either output the content or set it as a placeholder
  113. * @param string $content
  114. * @return string
  115. */
  116. public function output($content = '') {
  117. $toPlaceholder = $this->getProperty('toPlaceholder','');
  118. if (!empty($toPlaceholder)) {
  119. $this->modx->setPlaceholder($toPlaceholder,$content);
  120. return '';
  121. }
  122. return $content;
  123. }
  124. /**
  125. * Check for and load reCaptcha
  126. * @return boolean
  127. */
  128. public function loadReCaptcha() {
  129. $loaded = false;
  130. $preHooks = $this->getProperty('preHooks','');
  131. /* if using recaptcha, load recaptcha html */
  132. if (strpos($preHooks,'recaptcha') !== false && !$this->isAuthenticated) {
  133. /** @var reCaptcha $reCaptcha */
  134. $reCaptcha = $this->modx->getService('recaptcha','reCaptcha',$this->login->config['modelPath'].'recaptcha/');
  135. if ($reCaptcha instanceof reCaptcha) {
  136. $this->modx->lexicon->load('login:recaptcha');
  137. $recaptchaTheme = $this->getProperty('recaptchaTheme','clean');
  138. $recaptchaWidth = $this->getProperty('recaptchaWidth',500);
  139. $recaptchaHeight = $this->getProperty('recaptchaHeight',300);
  140. $html = $reCaptcha->getHtml($recaptchaTheme,$recaptchaWidth,$recaptchaHeight);
  141. $this->modx->setPlaceholder('login.recaptcha_html',$html);
  142. $loaded = true;
  143. } else {
  144. $this->modx->log(modX::LOG_LEVEL_ERROR,'[Login] '.$this->modx->lexicon('login.recaptcha_err_load'));
  145. }
  146. }
  147. return $loaded;
  148. }
  149. /**
  150. * Handle any POST request
  151. *
  152. * @return void
  153. */
  154. public function handleRequest() {
  155. $this->loadDictionary();
  156. $actionKey = $this->getProperty('actionKey','service');
  157. if (!empty($_POST) && isset($_POST[$actionKey]) && !$this->isAuthenticated) {
  158. if ($_POST[$actionKey] == $this->getProperty('loginKey','login')) {
  159. $this->login();
  160. } else {
  161. $this->modx->log(modX::LOG_LEVEL_ERROR,$this->modx->lexicon('login.invalid_post',array(
  162. 'action' => $_POST[$actionKey],
  163. )));
  164. }
  165. } elseif ($_REQUEST[$actionKey] == $this->getProperty('logoutKey','logout') && $this->isAuthenticated) {
  166. $this->logout();
  167. }
  168. }
  169. /**
  170. * Handle a Login submission
  171. *
  172. * @return void
  173. */
  174. public function login() {
  175. /* set default POST vars if not in form */
  176. if (empty($_POST['login_context'])) $_POST['login_context'] = $this->getProperty('loginContext');
  177. if ($this->runPreLoginHooks()) {
  178. $response = $this->runLoginProcessor();
  179. /* if we've got a good response, proceed */
  180. if (!empty($response) && !$response->isError()) {
  181. $this->runPostLoginHooks($response);
  182. /* process posthooks for login */
  183. if ($this->postHooks->hasErrors()) {
  184. $errors = $this->postHooks->getErrors();
  185. $errorMsg = $this->postHooks->getErrorMessage();
  186. // Return JSON posthook errors if requested.
  187. if ($this->getProperty('jsonResponse')) {
  188. $jsonErrorOutput = array(
  189. 'success' => false,
  190. 'message' => $errorMsg,
  191. 'errors' => $errors
  192. );
  193. header('Content-Type: application/json;charset=utf-8');
  194. exit($this->modx->toJSON($jsonErrorOutput));
  195. }
  196. $errorPrefix = $this->getProperty('errorPrefix','error');
  197. $this->modx->toPlaceholders($errors,$errorPrefix);
  198. $this->modx->toPlaceholder('message',$errorMsg,$errorPrefix);
  199. } else {
  200. // Return JSON success response if requested.
  201. if ($this->getProperty('jsonResponse')) {
  202. $jsonSuccessOutput = array(
  203. 'success' => true,
  204. 'message' => $this->getProperty('loginMsg')
  205. );
  206. header('Content-Type: application/json;charset=utf-8');
  207. exit($this->modx->toJSON($jsonSuccessOutput));
  208. }
  209. $this->redirectAfterLogin($response);
  210. }
  211. /* login failed, output error */
  212. } else {
  213. $this->checkForRedirectOnFailedAuth($response);
  214. $errorOutput = $this->prepareFailureMessage($response,$this->modx->lexicon('login.login_err'));
  215. $this->modx->setPlaceholder('errors', $errorOutput);
  216. }
  217. }
  218. }
  219. /**
  220. * Run any preHooks before logging in
  221. *
  222. * @return boolean
  223. */
  224. public function runPreLoginHooks() {
  225. $success = true;
  226. /* do pre-login hooks */
  227. $this->loadHooks('preHooks');
  228. $this->preHooks->loadMultiple($this->getProperty('preHooks',''),$this->dictionary->toArray(),array(
  229. 'mode' => Login::MODE_LOGIN,
  230. ));
  231. /* process prehooks */
  232. if ($this->preHooks->hasErrors()) {
  233. $errors = $this->preHooks->getErrors();
  234. // Return JSON prehook errors if requested.
  235. if ($this->getProperty('jsonResponse')) {
  236. $jsonErrorOutput = array(
  237. 'success' => false,
  238. 'errors' => $errors
  239. );
  240. header('Content-Type: application/json;charset=utf-8');
  241. exit($this->modx->toJSON($jsonErrorOutput));
  242. }
  243. $this->modx->toPlaceholders($errors,$this->getProperty('errorPrefix','error'));
  244. $errorMsg = $this->preHooks->getErrorMessage();
  245. $errorOutput = $this->modx->getChunk($this->getProperty('errTpl'), array('msg' => $errorMsg));
  246. $this->modx->setPlaceholder('errors',$errorOutput);
  247. $success = false;
  248. }
  249. return $success;
  250. }
  251. /**
  252. * @return modProcessorResponse
  253. */
  254. public function runLoginProcessor() {
  255. $loginViaEmail = $this->getProperty('loginViaEmail', false);
  256. $fields = $this->dictionary->toArray();
  257. /* send to login processor and handle response */
  258. $properties = array(
  259. 'login_context' => $this->getProperty('loginContext'),
  260. 'add_contexts' => $this->getProperty('contexts',''),
  261. 'username' => $fields['username'],
  262. 'password' => $fields['password'],
  263. 'returnUrl' => $fields['returnUrl'],
  264. 'rememberme' => !empty($fields[$this->getProperty('rememberMeKey','rememberme')]) ? true : false,
  265. );
  266. if ($loginViaEmail) {
  267. $processorsPath = $this->config['processorsPath'];
  268. return $this->modx->runProcessor('customlogin', $properties, array('processors_path' => $processorsPath));
  269. } else {
  270. return $this->modx->runProcessor('security/login', $properties);
  271. }
  272. }
  273. /**
  274. * @param modProcessorResponse $response
  275. * @param string $defaultErrorMessage
  276. * @return string
  277. */
  278. public function prepareFailureMessage(modProcessorResponse $response,$defaultErrorMessage = '') {
  279. $errorOutput = '';
  280. $errTpl = $this->getProperty('errTpl');
  281. $errors = $response->getFieldErrors();
  282. $message = $response->getMessage();
  283. if (!empty($errors)) {
  284. // Return JSON login errors if requested.
  285. if ($this->getProperty('jsonResponse')) {
  286. $jsonErrorOutput = array(
  287. 'success' => false,
  288. 'message' => $message,
  289. 'errors' => $errors
  290. );
  291. header('Content-Type: application/json;charset=utf-8');
  292. exit($this->modx->toJSON($jsonErrorOutput));
  293. }
  294. foreach ($errors as $error) {
  295. $errorOutput .= $this->modx->getChunk($errTpl, $error);
  296. }
  297. } elseif (!empty($message)) {
  298. // Return JSON error message in response if requested.
  299. if ($this->getProperty('jsonResponse')) {
  300. $jsonErrorOutput = array(
  301. 'success' => false,
  302. 'message' => $message
  303. );
  304. header('Content-Type: application/json;charset=utf-8');
  305. exit($this->modx->toJSON($jsonErrorOutput));
  306. }
  307. $errorOutput = $this->modx->getChunk($errTpl, array('msg' => $message));
  308. } else {
  309. // Return JSON default error if requested.
  310. if ($this->getProperty('jsonResponse')) {
  311. $jsonErrorOutput = array(
  312. 'success' => false,
  313. 'message' => $defaultErrorMessage
  314. );
  315. header('Content-Type: application/json;charset=utf-8');
  316. exit($this->modx->toJSON($jsonErrorOutput));
  317. }
  318. $errorOutput = $this->modx->getChunk($errTpl, array('msg' => $defaultErrorMessage));
  319. }
  320. return $errorOutput;
  321. }
  322. /**
  323. * Check to see if the user wants to redirect to a separ
  324. * @param modProcessorResponse $response
  325. * @return void
  326. */
  327. public function checkForRedirectOnFailedAuth(modProcessorResponse $response) {
  328. $redirectToOnFailedAuth = $this->getProperty('redirectToOnFailedAuth',false,'isset');
  329. if ($redirectToOnFailedAuth && $redirectToOnFailedAuth != $this->modx->resource->get('id')) {
  330. $p = array(
  331. 'u' => $this->dictionary->get('username'),
  332. );
  333. $message = $response->getMessage();
  334. if (!empty($message)) $params['m'] = $message;
  335. $url = $this->modx->makeUrl($redirectToOnFailedAuth,'',$p,'full');
  336. $this->modx->sendRedirect($url);
  337. }
  338. }
  339. /**
  340. * Run any postHooks specified after the user has logged in
  341. *
  342. * @param modProcessorResponse $response
  343. * @return void
  344. */
  345. public function runPostLoginHooks(modProcessorResponse $response) {
  346. $responseArray = $response->getObject();
  347. /* do post hooks */
  348. $postHooks = $this->getProperty('postHooks','');
  349. $this->loadHooks('postHooks');
  350. $fields = $_POST;
  351. $fields['response'] =& $responseArray;
  352. $fields['contexts'] =& $contexts;
  353. $fields['loginContext'] =& $loginContext;
  354. $fields['loginResourceId'] =& $loginResourceId;
  355. $this->postHooks->loadMultiple($postHooks,$fields,array(
  356. 'mode' => Login::MODE_LOGIN,
  357. ));
  358. }
  359. /**
  360. * Redirect the user after logging them in
  361. *
  362. * @param modProcessorResponse $response
  363. * @return void
  364. */
  365. public function redirectAfterLogin(modProcessorResponse $response) {
  366. $responseArray = $response->getObject();
  367. /* allow dynamic redirection handling */
  368. $redirectBack = $this->modx->getOption('redirectBack',$_REQUEST,$this->getProperty('redirectBack',''));
  369. $redirectBackParams = $this->modx->getOption('redirectBackParams',$_REQUEST,$this->getProperty('redirectBackParams',''));
  370. if (!empty($redirectBackParams)) {
  371. $redirectBackParams = $this->login->decodeParams($redirectBackParams);
  372. }
  373. /* otherwise specify a specific resource to redirect to */
  374. $loginResourceId = $this->getProperty('loginResourceId',$redirectBack);
  375. /* login posthooks succeeded, now redirect */
  376. if (!empty($loginResourceId)) {
  377. $loginResourceParams = $this->getProperty('loginResourceParams',$redirectBackParams);
  378. if (!empty($loginResourceParams) && !is_array($loginResourceParams)) {
  379. $loginResourceParams = $this->modx->fromJSON($loginResourceParams);
  380. }
  381. $url = $this->modx->makeUrl($loginResourceId,'',$loginResourceParams,'full');
  382. $this->modx->sendRedirect($url);
  383. } elseif (!empty($responseArray) && !empty($responseArray['url'])) {
  384. $this->modx->sendRedirect($responseArray['url']);
  385. } else {
  386. $this->modx->sendRedirect($this->modx->getOption('site_url'));
  387. }
  388. }
  389. public function logout() {
  390. /* set default REQUEST vars if not provided */
  391. if (empty($_REQUEST['login_context'])) $_REQUEST['login_context'] = $this->getProperty('loginContext');
  392. if ($this->runPreLogoutHooks()) {
  393. /* send to logout processor and handle response for each context */
  394. /** @var modProcessorResponse $response */
  395. $response = $this->modx->runProcessor('security/logout',array(
  396. 'login_context' => $this->getProperty('loginContext',$this->modx->context->get('key')),
  397. 'add_contexts' => $this->getProperty('contexts',''),
  398. ));
  399. /* if successful logout */
  400. if (!empty($response) && !$response->isError()) {
  401. $this->runPostLogoutHooks($response);
  402. // Return JSON logout success
  403. if ($this->getProperty('jsonResponse')) {
  404. $jsonSuccessOutput = array(
  405. 'success' => true,
  406. 'message' => $this->getProperty('logoutMsg')
  407. );
  408. header('Content-Type: application/json;charset=utf-8');
  409. exit($this->modx->toJSON($jsonSuccessOutput));
  410. }
  411. $this->redirectAfterLogout($response);
  412. /* logout failed, output error */
  413. } else {
  414. $errorOutput = $this->prepareFailureMessage($response,$this->modx->lexicon('login.logout_err'));
  415. $this->modx->setPlaceholder('errors', $errorOutput);
  416. }
  417. }
  418. }
  419. /**
  420. * @return boolean
  421. */
  422. public function runPreLogoutHooks() {
  423. $success = true;
  424. $this->loadHooks('preHooks');
  425. $this->preHooks->loadMultiple($this->getProperty('preHooks',''),$this->dictionary->toArray(),array(
  426. 'mode' => Login::MODE_LOGOUT,
  427. ));
  428. if ($this->preHooks->hasErrors()) {
  429. $errors = $this->preHooks->getErrors();
  430. $errorMsg = $this->preHooks->getErrorMessage();
  431. // Return JSON login errors if requested.
  432. if ($this->getProperty('jsonResponse')) {
  433. $jsonErrorOutput = array(
  434. 'success' => false,
  435. 'message' => $errorMsg,
  436. 'errors' => $errors
  437. );
  438. header('Content-Type: application/json;charset=utf-8');
  439. exit($this->modx->toJSON($jsonErrorOutput));
  440. }
  441. $this->modx->toPlaceholders($errors,$this->getProperty('errorPrefix','error'));
  442. $errorOutput = $this->modx->getChunk($this->getProperty('errTpl'), array('msg' => $errorMsg));
  443. $this->modx->setPlaceholder('errors',$errorOutput);
  444. $success = false;
  445. }
  446. return $success;
  447. }
  448. /**
  449. * Run any post-logout hooks
  450. *
  451. * @param modProcessorResponse $response
  452. * @return boolean
  453. */
  454. public function runPostLogoutHooks(modProcessorResponse $response) {
  455. $success = true;
  456. /* do post hooks for logout */
  457. $postHooks = $this->getProperty('postHooks','');
  458. $this->loadHooks('postHooks');
  459. $fields = $this->dictionary->toArray();
  460. $fields['response'] = $response->getObject();
  461. $fields['contexts'] =& $contexts;
  462. $fields['loginContext'] =& $loginContext;
  463. $fields['logoutResourceId'] =& $logoutResourceId;
  464. $this->postHooks->loadMultiple($postHooks,$fields,array(
  465. 'mode' => Login::MODE_LOGOUT,
  466. ));
  467. /* log posthooks errors */
  468. if ($this->postHooks->hasErrors()) {
  469. $errors = $this->postHooks->getErrors();
  470. $this->modx->log(modX::LOG_LEVEL_ERROR,'[Login] Post-Hook errors: '.print_r($errors,true));
  471. $errorMsg = $this->postHooks->getErrorMessage();
  472. if (!empty($errorMsg)) {
  473. $this->modx->log(modX::LOG_LEVEL_ERROR,'[Login] Post-Hook error: '.$errorMsg);
  474. }
  475. $success = false;
  476. // Return JSON posthook logout errors if requested.
  477. if ($this->getProperty('jsonResponse')) {
  478. $jsonErrorOutput = array(
  479. 'success' => false,
  480. 'message' => $errorMsg,
  481. 'errors' => $errors
  482. );
  483. header('Content-Type: application/json;charset=utf-8');
  484. exit($this->modx->toJSON($jsonErrorOutput));
  485. }
  486. }
  487. return $success;
  488. }
  489. /**
  490. * Redirect the user after they logout
  491. *
  492. * @param modProcessorResponse $response
  493. * @return void
  494. */
  495. public function redirectAfterLogout(modProcessorResponse $response) {
  496. $responseArray = $response->getObject();
  497. /* redirect */
  498. $logoutResourceId = $this->getProperty('logoutResourceId',0);
  499. if (!empty($responseArray) && !empty($responseArray['url'])) {
  500. $this->modx->sendRedirect($responseArray['url']);
  501. } elseif (!empty($logoutResourceId)) {
  502. $logoutResourceParams = $this->getProperty('logoutResourceParams','');
  503. if (!empty($logoutResourceParams)) {
  504. $logoutResourceParams = $this->modx->fromJSON($logoutResourceParams);
  505. }
  506. $url = $this->modx->makeUrl($logoutResourceId,'',$logoutResourceParams,'full');
  507. $this->modx->sendRedirect($url);
  508. } else {
  509. $this->modx->sendRedirect($_SERVER['REQUEST_URI']);
  510. }
  511. }
  512. }
  513. return 'LoginLoginController';