PageRenderTime 60ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/administrator/components/com_users/models/user.php

https://github.com/J2MTecnologia/joomla-3.x
PHP | 1288 lines | 707 code | 212 blank | 369 comment | 104 complexity | 64257be99f18a2fb407f917b69f40fb6 MD5 | raw file
Possible License(s): GPL-2.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /**
  3. * @package Joomla.Administrator
  4. * @subpackage com_users
  5. *
  6. * @copyright Copyright (C) 2005 - 2014 Open Source Matters, Inc. All rights reserved.
  7. * @license GNU General Public License version 2 or later; see LICENSE.txt
  8. */
  9. defined('_JEXEC') or die;
  10. /**
  11. * User model.
  12. *
  13. * @package Joomla.Administrator
  14. * @subpackage com_users
  15. * @since 1.6
  16. */
  17. class UsersModelUser extends JModelAdmin
  18. {
  19. /**
  20. * Constructor.
  21. *
  22. * @param array $config An optional associative array of configuration settings.
  23. *
  24. * @since 3.2
  25. */
  26. public function __construct($config = array())
  27. {
  28. parent::__construct($config);
  29. // Load the Joomla! RAD layer
  30. if (!defined('FOF_INCLUDED'))
  31. {
  32. include_once JPATH_LIBRARIES . '/fof/include.php';
  33. }
  34. }
  35. /**
  36. * Returns a reference to the a Table object, always creating it.
  37. *
  38. * @param string $type The table type to instantiate
  39. * @param string $prefix A prefix for the table class name. Optional.
  40. * @param array $config Configuration array for model. Optional.
  41. *
  42. * @return JTable A database object
  43. *
  44. * @since 1.6
  45. */
  46. public function getTable($type = 'User', $prefix = 'JTable', $config = array())
  47. {
  48. $table = JTable::getInstance($type, $prefix, $config);
  49. return $table;
  50. }
  51. /**
  52. * Method to get a single record.
  53. *
  54. * @param integer $pk The id of the primary key.
  55. *
  56. * @return mixed Object on success, false on failure.
  57. *
  58. * @since 1.6
  59. */
  60. public function getItem($pk = null)
  61. {
  62. $result = parent::getItem($pk);
  63. $result->tags = new JHelperTags;
  64. $result->tags->getTagIds($result->id, 'com_users.user');
  65. // Get the dispatcher and load the users plugins.
  66. $dispatcher = JEventDispatcher::getInstance();
  67. JPluginHelper::importPlugin('user');
  68. // Trigger the data preparation event.
  69. $dispatcher->trigger('onContentPrepareData', array('com_users.user', $result));
  70. return $result;
  71. }
  72. /**
  73. * Method to get the record form.
  74. *
  75. * @param array $data An optional array of data for the form to interogate.
  76. * @param boolean $loadData True if the form is to load its own data (default case), false if not.
  77. *
  78. * @return mixed A JForm object on success, false on failure
  79. *
  80. * @since 1.6
  81. */
  82. public function getForm($data = array(), $loadData = true)
  83. {
  84. $plugin = JPluginHelper::getPlugin('user', 'joomla');
  85. $pluginParams = new JRegistry($plugin->params);
  86. // Get the form.
  87. $form = $this->loadForm('com_users.user', 'user', array('control' => 'jform', 'load_data' => $loadData));
  88. if (empty($form))
  89. {
  90. return false;
  91. }
  92. // Passwords fields are required when mail to user is set to No in joomla user plugin
  93. $userId = $form->getValue('id');
  94. if ($userId === 0 && $pluginParams->get('mail_to_user') === "0")
  95. {
  96. $form->setFieldAttribute('password', 'required', 'true');
  97. $form->setFieldAttribute('password2', 'required', 'true');
  98. }
  99. // If the user needs to change their password, mark the password fields as required
  100. if (JFactory::getUser()->requireReset)
  101. {
  102. $form->setFieldAttribute('password', 'required', 'true');
  103. $form->setFieldAttribute('password2', 'required', 'true');
  104. }
  105. // The user should not be able to set the requireReset value on their own account
  106. if ((int) $userId === (int) JFactory::getUser()->id)
  107. {
  108. $form->removeField('requireReset');
  109. }
  110. return $form;
  111. }
  112. /**
  113. * Method to get the data that should be injected in the form.
  114. *
  115. * @return mixed The data for the form.
  116. *
  117. * @since 1.6
  118. */
  119. protected function loadFormData()
  120. {
  121. // Check the session for previously entered form data.
  122. $data = JFactory::getApplication()->getUserState('com_users.edit.user.data', array());
  123. if (empty($data))
  124. {
  125. $data = $this->getItem();
  126. }
  127. JPluginHelper::importPlugin('user');
  128. $this->preprocessData('com_users.profile', $data);
  129. return $data;
  130. }
  131. /**
  132. * Override JModelAdmin::preprocessForm to ensure the correct plugin group is loaded.
  133. *
  134. * @param JForm $form A JForm object.
  135. * @param mixed $data The data expected for the form.
  136. * @param string $group The name of the plugin group to import (defaults to "content").
  137. *
  138. * @return void
  139. *
  140. * @since 1.6
  141. * @throws Exception if there is an error in the form event.
  142. */
  143. protected function preprocessForm(JForm $form, $data, $group = 'user')
  144. {
  145. parent::preprocessForm($form, $data, $group);
  146. }
  147. /**
  148. * Method to save the form data.
  149. *
  150. * @param array $data The form data.
  151. *
  152. * @return boolean True on success.
  153. *
  154. * @since 1.6
  155. */
  156. public function save($data)
  157. {
  158. $pk = (!empty($data['id'])) ? $data['id'] : (int) $this->getState('user.id');
  159. $user = JUser::getInstance($pk);
  160. $my = JFactory::getUser();
  161. if ($data['block'] && $pk == $my->id && !$my->block)
  162. {
  163. $this->setError(JText::_('COM_USERS_USERS_ERROR_CANNOT_BLOCK_SELF'));
  164. return false;
  165. }
  166. // Make sure that we are not removing ourself from Super Admin group
  167. $iAmSuperAdmin = $my->authorise('core.admin');
  168. if ($iAmSuperAdmin && $my->get('id') == $pk)
  169. {
  170. // Check that at least one of our new groups is Super Admin
  171. $stillSuperAdmin = false;
  172. $myNewGroups = $data['groups'];
  173. foreach ($myNewGroups as $group)
  174. {
  175. $stillSuperAdmin = ($stillSuperAdmin) ? ($stillSuperAdmin) : JAccess::checkGroup($group, 'core.admin');
  176. }
  177. if (!$stillSuperAdmin)
  178. {
  179. $this->setError(JText::_('COM_USERS_USERS_ERROR_CANNOT_DEMOTE_SELF'));
  180. return false;
  181. }
  182. }
  183. // Handle the two factor authentication setup
  184. if (array_key_exists('twofactor', $data))
  185. {
  186. $twoFactorMethod = $data['twofactor']['method'];
  187. // Get the current One Time Password (two factor auth) configuration
  188. $otpConfig = $this->getOtpConfig($pk);
  189. if ($twoFactorMethod != 'none')
  190. {
  191. // Run the plugins
  192. FOFPlatform::getInstance()->importPlugin('twofactorauth');
  193. $otpConfigReplies = FOFPlatform::getInstance()->runPlugins('onUserTwofactorApplyConfiguration', array($twoFactorMethod));
  194. // Look for a valid reply
  195. foreach ($otpConfigReplies as $reply)
  196. {
  197. if (!is_object($reply) || empty($reply->method) || ($reply->method != $twoFactorMethod))
  198. {
  199. continue;
  200. }
  201. $otpConfig->method = $reply->method;
  202. $otpConfig->config = $reply->config;
  203. break;
  204. }
  205. // Save OTP configuration.
  206. $this->setOtpConfig($pk, $otpConfig);
  207. // Generate one time emergency passwords if required (depleted or not set)
  208. if (empty($otpConfig->otep))
  209. {
  210. $oteps = $this->generateOteps($pk);
  211. }
  212. }
  213. else
  214. {
  215. $otpConfig->method = 'none';
  216. $otpConfig->config = array();
  217. $this->setOtpConfig($pk, $otpConfig);
  218. }
  219. // Unset the raw data
  220. unset($data['twofactor']);
  221. // Reload the user record with the updated OTP configuration
  222. $user->load($pk);
  223. }
  224. // Bind the data.
  225. if (!$user->bind($data))
  226. {
  227. $this->setError($user->getError());
  228. return false;
  229. }
  230. // Store the data.
  231. if (!$user->save())
  232. {
  233. $this->setError($user->getError());
  234. return false;
  235. }
  236. $this->setState('user.id', $user->id);
  237. return true;
  238. }
  239. /**
  240. * Method to delete rows.
  241. *
  242. * @param array &$pks An array of item ids.
  243. *
  244. * @return boolean Returns true on success, false on failure.
  245. *
  246. * @since 1.6
  247. */
  248. public function delete(&$pks)
  249. {
  250. $user = JFactory::getUser();
  251. $table = $this->getTable();
  252. $pks = (array) $pks;
  253. // Check if I am a Super Admin
  254. $iAmSuperAdmin = $user->authorise('core.admin');
  255. // Trigger the onUserBeforeSave event.
  256. JPluginHelper::importPlugin('user');
  257. $dispatcher = JEventDispatcher::getInstance();
  258. if (in_array($user->id, $pks))
  259. {
  260. $this->setError(JText::_('COM_USERS_USERS_ERROR_CANNOT_DELETE_SELF'));
  261. return false;
  262. }
  263. // Iterate the items to delete each one.
  264. foreach ($pks as $i => $pk)
  265. {
  266. if ($table->load($pk))
  267. {
  268. // Access checks.
  269. $allow = $user->authorise('core.delete', 'com_users');
  270. // Don't allow non-super-admin to delete a super admin
  271. $allow = (!$iAmSuperAdmin && JAccess::check($pk, 'core.admin')) ? false : $allow;
  272. if ($allow)
  273. {
  274. // Get users data for the users to delete.
  275. $user_to_delete = JFactory::getUser($pk);
  276. // Fire the onUserBeforeDelete event.
  277. $dispatcher->trigger('onUserBeforeDelete', array($table->getProperties()));
  278. if (!$table->delete($pk))
  279. {
  280. $this->setError($table->getError());
  281. return false;
  282. }
  283. else
  284. {
  285. // Trigger the onUserAfterDelete event.
  286. $dispatcher->trigger('onUserAfterDelete', array($user_to_delete->getProperties(), true, $this->getError()));
  287. }
  288. }
  289. else
  290. {
  291. // Prune items that you can't change.
  292. unset($pks[$i]);
  293. JError::raiseWarning(403, JText::_('JERROR_CORE_DELETE_NOT_PERMITTED'));
  294. }
  295. }
  296. else
  297. {
  298. $this->setError($table->getError());
  299. return false;
  300. }
  301. }
  302. return true;
  303. }
  304. /**
  305. * Method to block user records.
  306. *
  307. * @param array &$pks The ids of the items to publish.
  308. * @param integer $value The value of the published state
  309. *
  310. * @return boolean True on success.
  311. *
  312. * @since 1.6
  313. */
  314. public function block(&$pks, $value = 1)
  315. {
  316. $app = JFactory::getApplication();
  317. $dispatcher = JEventDispatcher::getInstance();
  318. $user = JFactory::getUser();
  319. // Check if I am a Super Admin
  320. $iAmSuperAdmin = $user->authorise('core.admin');
  321. $table = $this->getTable();
  322. $pks = (array) $pks;
  323. JPluginHelper::importPlugin('user');
  324. // Access checks.
  325. foreach ($pks as $i => $pk)
  326. {
  327. if ($value == 1 && $pk == $user->get('id'))
  328. {
  329. // Cannot block yourself.
  330. unset($pks[$i]);
  331. JError::raiseWarning(403, JText::_('COM_USERS_USERS_ERROR_CANNOT_BLOCK_SELF'));
  332. }
  333. elseif ($table->load($pk))
  334. {
  335. $old = $table->getProperties();
  336. $allow = $user->authorise('core.edit.state', 'com_users');
  337. // Don't allow non-super-admin to delete a super admin
  338. $allow = (!$iAmSuperAdmin && JAccess::check($pk, 'core.admin')) ? false : $allow;
  339. // Prepare the logout options.
  340. $options = array(
  341. 'clientid' => 0
  342. );
  343. if ($allow)
  344. {
  345. // Skip changing of same state
  346. if ($table->block == $value)
  347. {
  348. unset($pks[$i]);
  349. continue;
  350. }
  351. $table->block = (int) $value;
  352. // If unblocking, also change password reset count to zero to unblock reset
  353. if ($table->block === 0)
  354. {
  355. $table->resetCount = 0;
  356. }
  357. // Allow an exception to be thrown.
  358. try
  359. {
  360. if (!$table->check())
  361. {
  362. $this->setError($table->getError());
  363. return false;
  364. }
  365. // Trigger the onUserBeforeSave event.
  366. $result = $dispatcher->trigger('onUserBeforeSave', array($old, false, $table->getProperties()));
  367. if (in_array(false, $result, true))
  368. {
  369. // Plugin will have to raise its own error or throw an exception.
  370. return false;
  371. }
  372. // Store the table.
  373. if (!$table->store())
  374. {
  375. $this->setError($table->getError());
  376. return false;
  377. }
  378. // Trigger the onAftereStoreUser event
  379. $dispatcher->trigger('onUserAfterSave', array($table->getProperties(), false, true, null));
  380. }
  381. catch (Exception $e)
  382. {
  383. $this->setError($e->getMessage());
  384. return false;
  385. }
  386. // Log the user out.
  387. if ($value)
  388. {
  389. $app->logout($table->id, $options);
  390. }
  391. }
  392. else
  393. {
  394. // Prune items that you can't change.
  395. unset($pks[$i]);
  396. JError::raiseWarning(403, JText::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'));
  397. }
  398. }
  399. }
  400. return true;
  401. }
  402. /**
  403. * Method to activate user records.
  404. *
  405. * @param array &$pks The ids of the items to activate.
  406. *
  407. * @return boolean True on success.
  408. *
  409. * @since 1.6
  410. */
  411. public function activate(&$pks)
  412. {
  413. $dispatcher = JEventDispatcher::getInstance();
  414. $user = JFactory::getUser();
  415. // Check if I am a Super Admin
  416. $iAmSuperAdmin = $user->authorise('core.admin');
  417. $table = $this->getTable();
  418. $pks = (array) $pks;
  419. JPluginHelper::importPlugin('user');
  420. // Access checks.
  421. foreach ($pks as $i => $pk)
  422. {
  423. if ($table->load($pk))
  424. {
  425. $old = $table->getProperties();
  426. $allow = $user->authorise('core.edit.state', 'com_users');
  427. // Don't allow non-super-admin to delete a super admin
  428. $allow = (!$iAmSuperAdmin && JAccess::check($pk, 'core.admin')) ? false : $allow;
  429. if (empty($table->activation))
  430. {
  431. // Ignore activated accounts.
  432. unset($pks[$i]);
  433. }
  434. elseif ($allow)
  435. {
  436. $table->block = 0;
  437. $table->activation = '';
  438. // Allow an exception to be thrown.
  439. try
  440. {
  441. if (!$table->check())
  442. {
  443. $this->setError($table->getError());
  444. return false;
  445. }
  446. // Trigger the onUserBeforeSave event.
  447. $result = $dispatcher->trigger('onUserBeforeSave', array($old, false, $table->getProperties()));
  448. if (in_array(false, $result, true))
  449. {
  450. // Plugin will have to raise it's own error or throw an exception.
  451. return false;
  452. }
  453. // Store the table.
  454. if (!$table->store())
  455. {
  456. $this->setError($table->getError());
  457. return false;
  458. }
  459. // Fire the onAftereStoreUser event
  460. $dispatcher->trigger('onUserAfterSave', array($table->getProperties(), false, true, null));
  461. }
  462. catch (Exception $e)
  463. {
  464. $this->setError($e->getMessage());
  465. return false;
  466. }
  467. }
  468. else
  469. {
  470. // Prune items that you can't change.
  471. unset($pks[$i]);
  472. JError::raiseWarning(403, JText::_('JLIB_APPLICATION_ERROR_EDITSTATE_NOT_PERMITTED'));
  473. }
  474. }
  475. }
  476. return true;
  477. }
  478. /**
  479. * Method to perform batch operations on an item or a set of items.
  480. *
  481. * @param array $commands An array of commands to perform.
  482. * @param array $pks An array of item ids.
  483. * @param array $contexts An array of item contexts.
  484. *
  485. * @return boolean Returns true on success, false on failure.
  486. *
  487. * @since 2.5
  488. */
  489. public function batch($commands, $pks, $contexts)
  490. {
  491. // Sanitize user ids.
  492. $pks = array_unique($pks);
  493. JArrayHelper::toInteger($pks);
  494. // Remove any values of zero.
  495. if (array_search(0, $pks, true))
  496. {
  497. unset($pks[array_search(0, $pks, true)]);
  498. }
  499. if (empty($pks))
  500. {
  501. $this->setError(JText::_('COM_USERS_USERS_NO_ITEM_SELECTED'));
  502. return false;
  503. }
  504. $done = false;
  505. if (!empty($commands['group_id']))
  506. {
  507. $cmd = JArrayHelper::getValue($commands, 'group_action', 'add');
  508. if (!$this->batchUser((int) $commands['group_id'], $pks, $cmd))
  509. {
  510. return false;
  511. }
  512. $done = true;
  513. }
  514. if (!empty($commands['reset_id']))
  515. {
  516. if (!$this->batchReset($pks, $commands['reset_id']))
  517. {
  518. return false;
  519. }
  520. $done = true;
  521. }
  522. if (!$done)
  523. {
  524. $this->setError(JText::_('JLIB_APPLICATION_ERROR_INSUFFICIENT_BATCH_INFORMATION'));
  525. return false;
  526. }
  527. // Clear the cache
  528. $this->cleanCache();
  529. return true;
  530. }
  531. /**
  532. * Batch flag users as being required to reset their passwords
  533. *
  534. * @param array $user_ids An array of user IDs on which to operate
  535. * @param string $action The action to perform
  536. *
  537. * @return boolean True on success, false on failure
  538. *
  539. * @since 3.2
  540. */
  541. public function batchReset($user_ids, $action)
  542. {
  543. // Set the action to perform
  544. if ($action === 'yes')
  545. {
  546. $value = 1;
  547. }
  548. else
  549. {
  550. $value = 0;
  551. }
  552. // Prune out the current user if they are in the supplied user ID array
  553. $user_ids = array_diff($user_ids, array(JFactory::getUser()->id));
  554. // Get the DB object
  555. $db = $this->getDbo();
  556. JArrayHelper::toInteger($user_ids);
  557. $query = $db->getQuery(true);
  558. // Update the reset flag
  559. $query->update($db->quoteName('#__users'))
  560. ->set($db->quoteName('requireReset') . ' = ' . $value)
  561. ->where($db->quoteName('id') . ' IN (' . implode(',', $user_ids) . ')');
  562. $db->setQuery($query);
  563. try
  564. {
  565. $db->execute();
  566. }
  567. catch (RuntimeException $e)
  568. {
  569. $this->setError($e->getMessage());
  570. return false;
  571. }
  572. return true;
  573. }
  574. /**
  575. * Perform batch operations
  576. *
  577. * @param integer $group_id The group ID which assignments are being edited
  578. * @param array $user_ids An array of user IDs on which to operate
  579. * @param string $action The action to perform
  580. *
  581. * @return boolean True on success, false on failure
  582. *
  583. * @since 1.6
  584. */
  585. public function batchUser($group_id, $user_ids, $action)
  586. {
  587. // Get the DB object
  588. $db = $this->getDbo();
  589. JArrayHelper::toInteger($user_ids);
  590. // Non-super admin cannot work with super-admin group
  591. if ((!JFactory::getUser()->get('isRoot') && JAccess::checkGroup($group_id, 'core.admin')) || $group_id < 1)
  592. {
  593. $this->setError(JText::_('COM_USERS_ERROR_INVALID_GROUP'));
  594. return false;
  595. }
  596. switch ($action)
  597. {
  598. // Sets users to a selected group
  599. case 'set':
  600. $doDelete = 'all';
  601. $doAssign = true;
  602. break;
  603. // Remove users from a selected group
  604. case 'del':
  605. $doDelete = 'group';
  606. break;
  607. // Add users to a selected group
  608. case 'add':
  609. default:
  610. $doAssign = true;
  611. break;
  612. }
  613. // Remove the users from the group if requested.
  614. if (isset($doDelete))
  615. {
  616. $query = $db->getQuery(true);
  617. // Remove users from the group
  618. $query->delete($db->quoteName('#__user_usergroup_map'))
  619. ->where($db->quoteName('user_id') . ' IN (' . implode(',', $user_ids) . ')');
  620. // Only remove users from selected group
  621. if ($doDelete == 'group')
  622. {
  623. $query->where($db->quoteName('group_id') . ' = ' . (int) $group_id);
  624. }
  625. $db->setQuery($query);
  626. try
  627. {
  628. $db->execute();
  629. }
  630. catch (RuntimeException $e)
  631. {
  632. $this->setError($e->getMessage());
  633. return false;
  634. }
  635. }
  636. // Assign the users to the group if requested.
  637. if (isset($doAssign))
  638. {
  639. $query = $db->getQuery(true);
  640. // First, we need to check if the user is already assigned to a group
  641. $query->select($db->quoteName('user_id'))
  642. ->from($db->quoteName('#__user_usergroup_map'))
  643. ->where($db->quoteName('group_id') . ' = ' . (int) $group_id);
  644. $db->setQuery($query);
  645. $users = $db->loadColumn();
  646. // Build the values clause for the assignment query.
  647. $query->clear();
  648. $groups = false;
  649. foreach ($user_ids as $id)
  650. {
  651. if (!in_array($id, $users))
  652. {
  653. $query->values($id . ',' . $group_id);
  654. $groups = true;
  655. }
  656. }
  657. // If we have no users to process, throw an error to notify the user
  658. if (!$groups)
  659. {
  660. $this->setError(JText::_('COM_USERS_ERROR_NO_ADDITIONS'));
  661. return false;
  662. }
  663. $query->insert($db->quoteName('#__user_usergroup_map'))
  664. ->columns(array($db->quoteName('user_id'), $db->quoteName('group_id')));
  665. $db->setQuery($query);
  666. try
  667. {
  668. $db->execute();
  669. }
  670. catch (RuntimeException $e)
  671. {
  672. $this->setError($e->getMessage());
  673. return false;
  674. }
  675. }
  676. return true;
  677. }
  678. /**
  679. * Gets the available groups.
  680. *
  681. * @return array An array of groups
  682. *
  683. * @since 1.6
  684. */
  685. public function getGroups()
  686. {
  687. $user = JFactory::getUser();
  688. if ($user->authorise('core.edit', 'com_users') && $user->authorise('core.manage', 'com_users'))
  689. {
  690. $model = JModelLegacy::getInstance('Groups', 'UsersModel', array('ignore_request' => true));
  691. return $model->getItems();
  692. }
  693. else
  694. {
  695. return null;
  696. }
  697. }
  698. /**
  699. * Gets the groups this object is assigned to
  700. *
  701. * @param integer $userId The user ID to retrieve the groups for
  702. *
  703. * @return array An array of assigned groups
  704. *
  705. * @since 1.6
  706. */
  707. public function getAssignedGroups($userId = null)
  708. {
  709. $userId = (!empty($userId)) ? $userId : (int) $this->getState('user.id');
  710. if (empty($userId))
  711. {
  712. $result = array();
  713. $groupsIDs = $this->getForm()->getValue('groups');
  714. if (!empty($groupsIDs))
  715. {
  716. $result = $groupsIDs;
  717. }
  718. else
  719. {
  720. $config = JComponentHelper::getParams('com_users');
  721. if ($groupId = $config->get('new_usertype'))
  722. {
  723. $result[] = $groupId;
  724. }
  725. }
  726. }
  727. else
  728. {
  729. $result = JUserHelper::getUserGroups($userId);
  730. }
  731. return $result;
  732. }
  733. /**
  734. * Returns the one time password (OTP) – a.k.a. two factor authentication –
  735. * configuration for a particular user.
  736. *
  737. * @param integer $user_id The numeric ID of the user
  738. *
  739. * @return stdClass An object holding the OTP configuration for this user
  740. *
  741. * @since 3.2
  742. */
  743. public function getOtpConfig($user_id = null)
  744. {
  745. $user_id = (!empty($user_id)) ? $user_id : (int) $this->getState('user.id');
  746. // Initialise
  747. $otpConfig = (object) array(
  748. 'method' => 'none',
  749. 'config' => array(),
  750. 'otep' => array()
  751. );
  752. /**
  753. * Get the raw data, without going through JUser (required in order to
  754. * be able to modify the user record before logging in the user).
  755. */
  756. $db = $this->getDbo();
  757. $query = $db->getQuery(true)
  758. ->select('*')
  759. ->from($db->qn('#__users'))
  760. ->where($db->qn('id') . ' = ' . $db->q($user_id));
  761. $db->setQuery($query);
  762. $item = $db->loadObject();
  763. // Make sure this user does have OTP enabled
  764. if (empty($item->otpKey))
  765. {
  766. return $otpConfig;
  767. }
  768. // Get the encrypted data
  769. list($method, $encryptedConfig) = explode(':', $item->otpKey, 2);
  770. $encryptedOtep = $item->otep;
  771. // Create an encryptor class
  772. $key = $this->getOtpConfigEncryptionKey();
  773. $aes = new FOFEncryptAes($key, 256);
  774. // Decrypt the data
  775. $decryptedConfig = $aes->decryptString($encryptedConfig);
  776. $decryptedOtep = $aes->decryptString($encryptedOtep);
  777. // Remove the null padding added during encryption
  778. $decryptedConfig = rtrim($decryptedConfig, "\0");
  779. $decryptedOtep = rtrim($decryptedOtep, "\0");
  780. // Update the configuration object
  781. $otpConfig->method = $method;
  782. $otpConfig->config = @json_decode($decryptedConfig);
  783. $otpConfig->otep = @json_decode($decryptedOtep);
  784. /*
  785. * If the decryption failed for any reason we essentially disable the
  786. * two-factor authentication. This prevents impossible to log in sites
  787. * if the site admin changes the site secret for any reason.
  788. */
  789. if (is_null($otpConfig->config))
  790. {
  791. $otpConfig->config = array();
  792. }
  793. if (is_object($otpConfig->config))
  794. {
  795. $otpConfig->config = (array) $otpConfig->config;
  796. }
  797. if (is_null($otpConfig->otep))
  798. {
  799. $otpConfig->otep = array();
  800. }
  801. if (is_object($otpConfig->otep))
  802. {
  803. $otpConfig->otep = (array) $otpConfig->otep;
  804. }
  805. // Return the configuration object
  806. return $otpConfig;
  807. }
  808. /**
  809. * Sets the one time password (OTP) – a.k.a. two factor authentication –
  810. * configuration for a particular user. The $otpConfig object is the same as
  811. * the one returned by the getOtpConfig method.
  812. *
  813. * @param integer $user_id The numeric ID of the user
  814. * @param stdClass $otpConfig The OTP configuration object
  815. *
  816. * @return boolean True on success
  817. *
  818. * @since 3.2
  819. */
  820. public function setOtpConfig($user_id, $otpConfig)
  821. {
  822. $user_id = (!empty($user_id)) ? $user_id : (int) $this->getState('user.id');
  823. $updates = (object) array(
  824. 'id' => $user_id,
  825. 'otpKey' => '',
  826. 'otep' => ''
  827. );
  828. // Create an encryptor class
  829. $key = $this->getOtpConfigEncryptionKey();
  830. $aes = new FOFEncryptAes($key, 256);
  831. // Create the encrypted option strings
  832. if (!empty($otpConfig->method) && ($otpConfig->method != 'none'))
  833. {
  834. $decryptedConfig = json_encode($otpConfig->config);
  835. $decryptedOtep = json_encode($otpConfig->otep);
  836. $updates->otpKey = $otpConfig->method . ':' . $aes->encryptString($decryptedConfig);
  837. $updates->otep = $aes->encryptString($decryptedOtep);
  838. }
  839. $db = $this->getDbo();
  840. $result = $db->updateObject('#__users', $updates, 'id');
  841. return $result;
  842. }
  843. /**
  844. * Gets the symmetric encryption key for the OTP configuration data. It
  845. * currently returns the site's secret.
  846. *
  847. * @return string The encryption key
  848. *
  849. * @since 3.2
  850. */
  851. public function getOtpConfigEncryptionKey()
  852. {
  853. return JFactory::getConfig()->get('secret');
  854. }
  855. /**
  856. * Gets the configuration forms for all two-factor authentication methods
  857. * in an array.
  858. *
  859. * @param integer $user_id The user ID to load the forms for (optional)
  860. *
  861. * @return array
  862. *
  863. * @since 3.2
  864. */
  865. public function getTwofactorform($user_id = null)
  866. {
  867. $user_id = (!empty($user_id)) ? $user_id : (int) $this->getState('user.id');
  868. $otpConfig = $this->getOtpConfig($user_id);
  869. FOFPlatform::getInstance()->importPlugin('twofactorauth');
  870. return FOFPlatform::getInstance()->runPlugins('onUserTwofactorShowConfiguration', array($otpConfig, $user_id));
  871. }
  872. /**
  873. * Generates a new set of One Time Emergency Passwords (OTEPs) for a given user.
  874. *
  875. * @param integer $user_id The user ID
  876. * @param integer $count How many OTEPs to generate? Default: 10
  877. *
  878. * @return array The generated OTEPs
  879. *
  880. * @since 3.2
  881. */
  882. public function generateOteps($user_id, $count = 10)
  883. {
  884. $user_id = (!empty($user_id)) ? $user_id : (int) $this->getState('user.id');
  885. // Initialise
  886. $oteps = array();
  887. // Get the OTP configuration for the user
  888. $otpConfig = $this->getOtpConfig($user_id);
  889. // If two factor authentication is not enabled, abort
  890. if (empty($otpConfig->method) || ($otpConfig->method == 'none'))
  891. {
  892. return $oteps;
  893. }
  894. $salt = "0123456789";
  895. $base = strlen($salt);
  896. $length = 16;
  897. for ($i = 0; $i < $count; $i++)
  898. {
  899. $makepass = '';
  900. $random = JCrypt::genRandomBytes($length + 1);
  901. $shift = ord($random[0]);
  902. for ($j = 1; $j <= $length; ++$j)
  903. {
  904. $makepass .= $salt[($shift + ord($random[$j])) % $base];
  905. $shift += ord($random[$j]);
  906. }
  907. $oteps[] = $makepass;
  908. }
  909. $otpConfig->otep = $oteps;
  910. // Save the now modified OTP configuration
  911. $this->setOtpConfig($user_id, $otpConfig);
  912. return $oteps;
  913. }
  914. /**
  915. * Checks if the provided secret key is a valid two factor authentication
  916. * secret key. If not, it will check it against the list of one time
  917. * emergency passwords (OTEPs). If it's a valid OTEP it will also remove it
  918. * from the user's list of OTEPs.
  919. *
  920. * This method will return true in the following conditions:
  921. * - The two factor authentication is not enabled
  922. * - You have provided a valid secret key for
  923. * - You have provided a valid OTEP
  924. *
  925. * You can define the following options in the $options array:
  926. * otp_config The OTP (one time password, a.k.a. two factor auth)
  927. * configuration object. If not set we'll load it automatically.
  928. * warn_if_not_req Issue a warning if you are checking a secret key against
  929. * a user account which doesn't have any two factor
  930. * authentication method enabled.
  931. * warn_irq_msg The string to use for the warn_if_not_req warning
  932. *
  933. * @param integer $user_id The user's numeric ID
  934. * @param string $secretkey The secret key you want to check
  935. * @param array $options Options; see above
  936. *
  937. * @return boolean True if it's a valid secret key for this user.
  938. *
  939. * @since 3.2
  940. */
  941. public function isValidSecretKey($user_id, $secretkey, $options = array())
  942. {
  943. // Load the user's OTP (one time password, a.k.a. two factor auth) configuration
  944. if (!array_key_exists('otp_config', $options))
  945. {
  946. $otpConfig = $this->getOtpConfig($user_id);
  947. $options['otp_config'] = $otpConfig;
  948. }
  949. else
  950. {
  951. $otpConfig = $options['otp_config'];
  952. }
  953. // Check if the user has enabled two factor authentication
  954. if (empty($otpConfig->method) || ($otpConfig->method == 'none'))
  955. {
  956. // Load language
  957. $lang = JFactory::getLanguage();
  958. $extension = 'com_users';
  959. $source = JPATH_ADMINISTRATOR . '/components/' . $extension;
  960. $lang->load($extension, JPATH_ADMINISTRATOR, null, false, true)
  961. || $lang->load($extension, $source, null, false, true);
  962. $warn = true;
  963. $warnMessage = JText::_('COM_USERS_ERROR_SECRET_CODE_WITHOUT_TFA');
  964. if (array_key_exists('warn_if_not_req', $options))
  965. {
  966. $warn = $options['warn_if_not_req'];
  967. }
  968. if (array_key_exists('warn_irq_msg', $options))
  969. {
  970. $warnMessage = $options['warn_irq_msg'];
  971. }
  972. // Warn the user if he's using a secret code but he has not
  973. // enabled two factor auth in his account.
  974. if (!empty($secretkey) && $warn)
  975. {
  976. try
  977. {
  978. $app = JFactory::getApplication();
  979. $app->enqueueMessage($warnMessage, 'warning');
  980. }
  981. catch (Exception $exc)
  982. {
  983. // This happens when we are in CLI mode. In this case
  984. // no warning is issued
  985. return true;
  986. }
  987. }
  988. return true;
  989. }
  990. $credentials = array(
  991. 'secretkey' => $secretkey,
  992. );
  993. // Load the Joomla! RAD layer
  994. if (!defined('FOF_INCLUDED'))
  995. {
  996. include_once JPATH_LIBRARIES . '/fof/include.php';
  997. }
  998. // Try to validate the OTP
  999. FOFPlatform::getInstance()->importPlugin('twofactorauth');
  1000. $otpAuthReplies = FOFPlatform::getInstance()->runPlugins('onUserTwofactorAuthenticate', array($credentials, $options));
  1001. $check = false;
  1002. /*
  1003. * This looks like noob code but DO NOT TOUCH IT and do not convert
  1004. * to in_array(). During testing in_array() inexplicably returned
  1005. * null when the OTEP begins with a zero! o_O
  1006. */
  1007. if (!empty($otpAuthReplies))
  1008. {
  1009. foreach ($otpAuthReplies as $authReply)
  1010. {
  1011. $check = $check || $authReply;
  1012. }
  1013. }
  1014. // Fall back to one time emergency passwords
  1015. if (!$check)
  1016. {
  1017. $check = $this->isValidOtep($user_id, $secretkey, $otpConfig);
  1018. }
  1019. return $check;
  1020. }
  1021. /**
  1022. * Checks if the supplied string is a valid one time emergency password
  1023. * (OTEP) for this user. If it is it will be automatically removed from the
  1024. * user's list of OTEPs.
  1025. *
  1026. * @param integer $user_id The user ID against which you are checking
  1027. * @param string $otep The string you want to test for validity
  1028. * @param object $otpConfig Optional; the two factor authentication configuration (automatically fetched if not set)
  1029. *
  1030. * @return boolean True if it's a valid OTEP or if two factor auth is not
  1031. * enabled in this user's account.
  1032. *
  1033. * @since 3.2
  1034. */
  1035. public function isValidOtep($user_id, $otep, $otpConfig = null)
  1036. {
  1037. if (is_null($otpConfig))
  1038. {
  1039. $otpConfig = $this->getOtpConfig($user_id);
  1040. }
  1041. // Did the user use an OTEP instead?
  1042. if (empty($otpConfig->otep))
  1043. {
  1044. if (empty($otpConfig->method) || ($otpConfig->method == 'none'))
  1045. {
  1046. // Two factor authentication is not enabled on this account.
  1047. // Any string is assumed to be a valid OTEP.
  1048. return true;
  1049. }
  1050. else
  1051. {
  1052. /**
  1053. * Two factor authentication enabled and no OTEPs defined. The
  1054. * user has used them all up. Therefore anything he enters is
  1055. * an invalid OTEP.
  1056. */
  1057. return false;
  1058. }
  1059. }
  1060. // Clean up the OTEP (remove dashes, spaces and other funny stuff
  1061. // our beloved users may have unwittingly stuffed in it)
  1062. $otep = filter_var($otep, FILTER_SANITIZE_NUMBER_INT);
  1063. $otep = str_replace('-', '', $otep);
  1064. $check = false;
  1065. // Did we find a valid OTEP?
  1066. if (in_array($otep, $otpConfig->otep))
  1067. {
  1068. // Remove the OTEP from the array
  1069. $otpConfig->otep = array_diff($otpConfig->otep, array($otep));
  1070. $this->setOtpConfig($user_id, $otpConfig);
  1071. // Return true; the OTEP was a valid one
  1072. $check = true;
  1073. }
  1074. return $check;
  1075. }
  1076. }