PageRenderTime 30ms CodeModel.GetById 19ms RepoModel.GetById 0ms app.codeStats 0ms

/plugins/system/admintools/feature/superuserslist.php

https://bitbucket.org/saltwaterdev/offshorefinancial.com
PHP | 612 lines | 393 code | 107 blank | 112 comment | 57 complexity | e310e39950cac6dda1860b94a2f3f440 MD5 | raw file
Possible License(s): GPL-2.0, GPL-3.0, 0BSD, MIT, Apache-2.0, LGPL-2.1
  1. <?php
  2. /**
  3. * @package AdminTools
  4. * @copyright Copyright (c)2010-2017 Nicholas K. Dionysopoulos
  5. * @license GNU General Public License version 3, or later
  6. */
  7. defined('_JEXEC') or die;
  8. /**
  9. * Keep track of Super Users on the site and send an email when users are added. Optionally automatically block these
  10. * new Super Users.
  11. */
  12. class AtsystemFeatureSuperuserslist extends AtsystemFeatureAbstract
  13. {
  14. protected $loadOrder = 998;
  15. /**
  16. * Is this feature enabled?
  17. *
  18. * @return bool
  19. */
  20. public function isEnabled()
  21. {
  22. return ($this->cparams->getValue('superuserslist', 1) == 1);
  23. }
  24. /**
  25. * Checks if a backend Super User is saving another Super User account. We have to run this check onAfterRoute since
  26. * com_users will perform an immediate redirect upon saving, without hitting onAfterRender. For the same reason the
  27. * detected ID of the Super User being saved has to be saved in the session to persist the successive page loads.
  28. */
  29. public function onAfterRoute()
  30. {
  31. if (!$this->isBackendSuperUser())
  32. {
  33. return;
  34. }
  35. // Do I already have session data?
  36. $safeIDs = $this->container->platform->getSessionVar('superuserslist.safeids', [], 'com_admintools');
  37. $isUserSaveOrApply = $this->container->platform->getSessionVar('superuserslist.createnew', null, 'com_admintools');
  38. if (!is_null($isUserSaveOrApply))
  39. {
  40. // Yeah. Let's not overwrite the session data. We shall do that onAfterRender.
  41. return;
  42. }
  43. $safeIDs = $this->getSafeIDs();
  44. // Get the option and task parameters
  45. $app = JFactory::getApplication();
  46. $option = $app->input->getCmd('option', 'com_foobar');
  47. $task = $app->input->getCmd('task');
  48. $isUserSaveOrApply = false;
  49. // Are we using com_user to Save or Save & Close a user?
  50. if ($option == 'com_users')
  51. {
  52. if (in_array($task, ['user.apply', 'user.save']))
  53. {
  54. $isUserSaveOrApply = true;
  55. }
  56. }
  57. $this->container->platform->setSessionVar('superuserslist.safeids', $safeIDs, 'com_admintools');
  58. $this->container->platform->setSessionVar('superuserslist.createnew', $isUserSaveOrApply, 'com_admintools');
  59. }
  60. public function onAfterRender()
  61. {
  62. // Only run if the current user is a Super User AND we haven't already set a flag
  63. $currentUser = $this->container->platform->getUser();
  64. if ($currentUser->guest)
  65. {
  66. return;
  67. }
  68. if (!$currentUser->authorise('core.admin'))
  69. {
  70. return;
  71. }
  72. $flag = $this->container->platform->getSessionVar('allowedsuperuser', null, 'com_admintools');
  73. if ($flag === true)
  74. {
  75. return;
  76. }
  77. // Get temporary session variables
  78. $safeIDs = $this->container->platform->getSessionVar('superuserslist.safeids', [], 'com_admintools');
  79. $isUserSaveOrApply = $this->container->platform->getSessionVar('superuserslist.createnew', null, 'com_admintools');
  80. $this->container->platform->unsetSessionVar('superuserslist.safeids', 'com_admintools');
  81. $this->container->platform->unsetSessionVar('superuserslist.createnew', 'com_admintools');
  82. // Normalize
  83. if (empty($safeIDs))
  84. {
  85. $safeIDs = [];
  86. }
  87. if (empty($isUserSaveOrApply))
  88. {
  89. $isUserSaveOrApply = false;
  90. }
  91. // If it's not a backend Super User we are going to ignore session variables (they are forged!)
  92. if (!$this->isBackendSuperUser())
  93. {
  94. $safeIDs = [];
  95. $isUserSaveOrApply = false;
  96. }
  97. // Get the Super User IDs
  98. $savedSuperUserIDs = $this->load();
  99. $superUserGroups = $this->getSuperUserGroups();
  100. $currentSuperUserIDs = $this->getUsersInGroups($superUserGroups);
  101. // Oh, we never had a list of Super Users. Let's fix that.
  102. if (empty($savedSuperUserIDs))
  103. {
  104. $this->save($currentSuperUserIDs);
  105. return;
  106. }
  107. // Do we have new Super Users?
  108. $newSuperUsers = array_diff($currentSuperUserIDs, $savedSuperUserIDs);
  109. // Do NOT remove this variable! It catches the case were Super Users are added BUT THEN REMOVED FROM $newSuperUsers WITH array_diff. WE MUST SAVE IN THIS CASE!
  110. $hasNewSuperUsers = !empty($newSuperUsers);
  111. $newSuperUsers = array_diff($newSuperUsers, $safeIDs);
  112. $removedSuperUsers = array_diff($savedSuperUserIDs, $currentSuperUserIDs);
  113. // Detect the case where we have to simply save the list of Super Users and quit (no new or removed SUs)
  114. $saveListAndQuit = empty($newSuperUsers) && empty($removedSuperUsers);
  115. /**
  116. * Special case: Super User logged in backend creates a new user account that is also a Super User.
  117. *
  118. * In this case we do not have any safeIDs because the JForm is being submitted with user ID 0. This is normal
  119. * since we are creating a new user record, therefore we do not have a user ID yet. We can distinguish this
  120. * case from the generic "third party backend extension creates a new user account" by checking the option and
  121. * task parameters. If the option is com_users (the Joomla! user management core component) and the task
  122. * indicates applying or saving a user we have the special case we need to avoid blocking.
  123. */
  124. if ($this->isBackendSuperUser() && empty($safeIDs) && $isUserSaveOrApply)
  125. {
  126. $saveListAndQuit = true;
  127. }
  128. if ($saveListAndQuit)
  129. {
  130. // In case Super Users ARE added BUT are in the safe IDs list THEN we MUST save the new list!
  131. if ($hasNewSuperUsers)
  132. {
  133. $this->save($currentSuperUserIDs);
  134. }
  135. return;
  136. }
  137. // If we're here a new Super User was added through means unknown. Notify the admins and block the user.
  138. $this->sendEmail($newSuperUsers);
  139. $flag = true;
  140. foreach ($newSuperUsers as $id)
  141. {
  142. $user = $this->container->platform->getUser($id);
  143. $user->block = 1;
  144. $user->save();
  145. if ($currentUser->id == $id)
  146. {
  147. $flag = false;
  148. }
  149. }
  150. $this->container->platform->setSessionVar('allowedsuperuser', $flag, 'com_admintools');
  151. $currentSuperUserIDs = array_diff($currentSuperUserIDs, $newSuperUsers);
  152. $newSuperUsers = [];
  153. if (!empty($newSuperUsers) || !empty($removedSuperUsers))
  154. {
  155. $this->save($currentSuperUserIDs);
  156. }
  157. // Is the current user one of the new, bad admins? If so, try to log the out
  158. if ($flag === false)
  159. {
  160. $app = JFactory::getApplication();
  161. // Try being nice about it
  162. if (!$app->logout())
  163. {
  164. // If being nice about logging you out doesn't work I'm gonna terminate you, with extreme prejudice.
  165. $app->getSession()->set('user', null);
  166. $app->getSession()->destroy();
  167. }
  168. }
  169. }
  170. /**
  171. * Save the list of users to the database
  172. *
  173. * @param array $userList The list of User IDs
  174. *
  175. * @return void
  176. */
  177. private function save(array $userList)
  178. {
  179. $db = $this->container->db;
  180. $data = json_encode($userList);
  181. $query = $db->getQuery(true)
  182. ->delete($db->quoteName('#__admintools_storage'))
  183. ->where($db->quoteName('key') . ' = ' . $db->quote('superuserslist'));
  184. $db->setQuery($query);
  185. $db->execute();
  186. $object = (object) array(
  187. 'key' => 'superuserslist',
  188. 'value' => $data
  189. );
  190. $db->insertObject('#__admintools_storage', $object);
  191. }
  192. /**
  193. * Load the saved list of Super User IDs from the database
  194. *
  195. * @return array
  196. */
  197. private function load()
  198. {
  199. $db = $this->container->db;
  200. $query = $db->getQuery(true)
  201. ->select($db->quoteName('value'))
  202. ->from($db->quoteName('#__admintools_storage'))
  203. ->where($db->quoteName('key') . ' = ' . $db->quote('superuserslist'));
  204. $db->setQuery($query);
  205. $error = 0;
  206. try
  207. {
  208. $jsonData = $db->loadResult();
  209. }
  210. catch (Exception $e)
  211. {
  212. $error = $e->getCode();
  213. }
  214. if (method_exists($db, 'getErrorNum') && $db->getErrorNum())
  215. {
  216. $error = $db->getErrorNum();
  217. }
  218. if ($error)
  219. {
  220. $jsonData = null;
  221. }
  222. if (empty($jsonData))
  223. {
  224. return [];
  225. }
  226. return json_decode($jsonData, true);
  227. }
  228. /**
  229. * Sends a warning email to the addresses set up to receive security exception emails
  230. *
  231. * @param array $superUsers The IDs of Super Users added
  232. *
  233. * @return void
  234. */
  235. private function sendEmail(array $superUsers)
  236. {
  237. if (empty($superUsers))
  238. {
  239. // What are you doing here?
  240. return;
  241. }
  242. // Load the component's administrator translation files
  243. $jlang = JFactory::getLanguage();
  244. $jlang->load('com_admintools', JPATH_ADMINISTRATOR, 'en-GB', true);
  245. $jlang->load('com_admintools', JPATH_ADMINISTRATOR, $jlang->getDefault(), true);
  246. $jlang->load('com_admintools', JPATH_ADMINISTRATOR, null, true);
  247. // Convert the list of added Super Users
  248. $htmlUsersList = <<< HTML
  249. <ul>
  250. HTML;
  251. foreach ($superUsers as $id)
  252. {
  253. $user = $this->container->platform->getUser($id);
  254. $htmlUsersList .= <<< HTML
  255. <li>
  256. #$id &ndash; <b>{$user->username}</b> &ndash; {$user->name} &lt;{$user->email}&gt;
  257. </li>
  258. HTML;
  259. }
  260. $htmlUsersList .= <<< HTML
  261. </ul>
  262. HTML;
  263. // Construct the replacement table
  264. $substitutions = $this->exceptionsHandler->getEmailVariables('', [
  265. '[INFO]' => $htmlUsersList
  266. ]);
  267. // Let's get the most suitable email template
  268. $template = $this->exceptionsHandler->getEmailTemplate('superuserslist', true);
  269. // Got no template, the user didn't published any email template, or the template doesn't want us to
  270. // send a notification email. Anyway, let's stop here.
  271. if (!$template)
  272. {
  273. return;
  274. }
  275. $subject = $template[0];
  276. $body = $template[1];
  277. foreach ($substitutions as $k => $v)
  278. {
  279. $subject = str_replace($k, $v, $subject);
  280. $body = str_replace($k, $v, $body);
  281. }
  282. try
  283. {
  284. $config = $this->container->platform->getConfig();
  285. $mailer = JFactory::getMailer();
  286. $mailfrom = $config->get('mailfrom');
  287. $fromname = $config->get('fromname');
  288. $recipients = explode(',', $this->cparams->getValue('emailbreaches', ''));
  289. $recipients = array_map('trim', $recipients);
  290. foreach ($recipients as $recipient)
  291. {
  292. if (empty($recipient))
  293. {
  294. continue;
  295. }
  296. // This line is required because SpamAssassin is BROKEN
  297. $mailer->Priority = 3;
  298. $mailer->isHtml(true);
  299. $mailer->setSender(array($mailfrom, $fromname));
  300. // Resets the recipients, otherwise they will pile up
  301. $mailer->clearAllRecipients();
  302. if ($mailer->addRecipient($recipient) === false)
  303. {
  304. // Failed to add a recipient?
  305. continue;
  306. }
  307. $mailer->setSubject($subject);
  308. $mailer->setBody($body);
  309. $mailer->Send();
  310. }
  311. }
  312. catch (\Exception $e)
  313. {
  314. // Joomla! 3.5 and later throw an exception when crap happens instead of suppressing it and returning false
  315. }
  316. }
  317. /**
  318. * Get the user groups with Super User privileges
  319. *
  320. * @return array
  321. */
  322. private function getSuperUserGroups()
  323. {
  324. static $ret = null;
  325. if (!is_array($ret))
  326. {
  327. $db = $this->container->db;
  328. $ret = [];
  329. try
  330. {
  331. $query = $db->getQuery(true)
  332. ->select($db->qn('rules'))
  333. ->from($db->qn('#__assets'))
  334. ->where($db->qn('parent_id') . ' = ' . $db->q(0));
  335. $db->setQuery($query, 0, 1);
  336. $rulesJSON = $db->loadResult();
  337. }
  338. catch (Exception $exc)
  339. {
  340. return $ret;
  341. }
  342. $rules = json_decode($rulesJSON, true);
  343. $rawGroups = $rules['core.admin'];
  344. if (empty($rawGroups))
  345. {
  346. return $ret;
  347. }
  348. foreach ($rawGroups as $g => $enabled)
  349. {
  350. if (!$enabled)
  351. {
  352. continue;
  353. }
  354. $ret[] = $g;
  355. }
  356. }
  357. return $ret;
  358. }
  359. /**
  360. * Get the IDs of users who are members of one or more groups in the $groups list
  361. *
  362. * @param array $groups The users must be a member of at least one of these groups
  363. *
  364. * @return array
  365. */
  366. private function getUsersInGroups(array $groups)
  367. {
  368. $db = $this->container->db;
  369. $ret = [];
  370. $groups = array_map(array($db, 'q'), $groups);
  371. try
  372. {
  373. $query = $db->getQuery(true)
  374. ->select($db->qn('user_id'))
  375. ->from($db->qn('#__user_usergroup_map') . ' AS ' . $db->qn('m'))
  376. ->innerJoin($db->qn('#__users') . ' AS ' . $db->qn('u') . 'ON(' .
  377. $db->qn('u.id') . ' = ' . $db->qn('m.user_id')
  378. . ')')
  379. ->where($db->qn('group_id') . ' IN(' . implode(',', $groups) . ')' )
  380. ->where($db->qn('block') . ' = ' . $db->q('0') )
  381. // Don't look only for empty string. Joomla! considers '' and '0' identical and will let you log in!
  382. ->where('(' .
  383. '(' . $db->qn('activation') . ' = ' . $db->q('0') . ') OR ' .
  384. '(' . $db->qn('activation') . ' = ' . $db->q('') . ')' .
  385. ')')
  386. ;
  387. $db->setQuery($query);
  388. $rawUserIDs = $db->loadColumn(0);
  389. }
  390. catch (Exception $exc)
  391. {
  392. return $ret;
  393. }
  394. if (empty($rawUserIDs))
  395. {
  396. return $ret;
  397. }
  398. return array_unique($rawUserIDs);
  399. }
  400. /**
  401. * Returns a list of safe Super User IDs. These are the IDs of the Super Users being saved by another Super User in
  402. * the backend of the site through com_users.
  403. *
  404. * @return array
  405. */
  406. public function getSafeIDs()
  407. {
  408. $app = JFactory::getApplication();
  409. if (!$this->isBackendSuperUser())
  410. {
  411. return [];
  412. }
  413. // Get the option and task parameters
  414. $option = $app->input->getCmd('option', 'com_foobar');
  415. $task = $app->input->getCmd('task');
  416. // Not com_users?
  417. if ($option != 'com_users')
  418. {
  419. return [];
  420. }
  421. // Special case: unblock with one click. There's no jform here, the ID is passed in the 'cid' query string parameter
  422. if ($task == 'users.unblock')
  423. {
  424. $cid = $app->input->get('cid', [], 'array');
  425. if (empty($cid))
  426. {
  427. return [];
  428. }
  429. if (!is_array($cid))
  430. {
  431. $cid = [$cid];
  432. }
  433. return $cid;
  434. }
  435. // Note Save or Save & Close?
  436. if (!in_array($task, ['user.apply', 'user.save']))
  437. {
  438. return [];
  439. }
  440. // Get the user IDs from the form
  441. $jForm = $app->input->get('jform', [], 'array');
  442. if (!is_array($jForm) || empty($jForm))
  443. {
  444. return [];
  445. }
  446. // No user ID or group information?
  447. if (!isset($jForm['groups']) || !isset($jForm['id']))
  448. {
  449. return [];
  450. }
  451. // Is it a Super User?
  452. $superUserGroups = $this->getSuperUserGroups();
  453. $groups = $jForm['groups'];
  454. $isSuperUser = false;
  455. if (empty($groups))
  456. {
  457. return [];
  458. }
  459. foreach ($groups as $group)
  460. {
  461. if (in_array($group, $superUserGroups))
  462. {
  463. $isSuperUser = true;
  464. break;
  465. }
  466. }
  467. if (!$isSuperUser)
  468. {
  469. return [];
  470. }
  471. // Get the user ID being saved and return it
  472. $id = $jForm['id'];
  473. if (empty($id))
  474. {
  475. return [];
  476. }
  477. return [$id];
  478. }
  479. /**
  480. * Are we currently in the backend, with a logged in Super User?
  481. *
  482. * @return bool
  483. */
  484. private function isBackendSuperUser()
  485. {
  486. $app = JFactory::getApplication();
  487. // Not a valid application object?
  488. if (!is_object($app) || !($app instanceof JApplicationCms))
  489. {
  490. return false;
  491. }
  492. // Are we in the backend?
  493. $isAdmin = method_exists($app, 'isAdmin') ? $app->isAdmin() : $app->isClient('administrator');
  494. if (!$isAdmin)
  495. {
  496. return false;
  497. }
  498. // Not a Super User?
  499. if (!$this->container->platform->getUser()->authorise('core.admin'))
  500. {
  501. return false;
  502. }
  503. return true;
  504. }
  505. }