Source for file user.php

Documentation is available at user.php

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

Documentation generated on Tue, 19 Nov 2013 15:16:28 +0100 by phpDocumentor 1.4.3