Source for file dispatcher.php

Documentation is available at dispatcher.php

  1. <?php
  2. /**
  3.  * @package     FrameworkOnFramework
  4.  * @subpackage  dispatcher
  5.  * @copyright   Copyright (C) 2010 - 2012 Akeeba Ltd. All rights reserved.
  6.  * @license     GNU General Public License version 2 or later; see LICENSE.txt
  7.  */
  8. // Protect from unauthorized access
  9. defined('_JEXEC'or die;
  10.  
  11. /**
  12.  * FrameworkOnFramework dispatcher class
  13.  *
  14.  * FrameworkOnFramework is a set of classes whcih extend Joomla! 1.5 and later's
  15.  * MVC framework with features making maintaining complex software much easier,
  16.  * without tedious repetitive copying of the same code over and over again.
  17.  *
  18.  * @package  FrameworkOnFramework
  19.  * @since    1.0
  20.  */
  21. class FOFDispatcher extends JObject
  22. {
  23.     /** @var array Configuration variables */
  24.     protected $config = array();
  25.  
  26.     /** @var FOFInput Input variables */
  27.     protected $input = array();
  28.  
  29.     /** @var string The name of the default view, in case none is specified */
  30.     public $defaultView = 'cpanel';
  31.  
  32.     // Variables for FOF's transparent user authentication. You can override them
  33.     // in your Dispatcher's __construct() method.
  34.  
  35.     /** @var int The Time Step for the TOTP used in FOF's transparent user authentication */
  36.     protected $fofAuth_timeStep = 6;
  37.  
  38.     /** @var string The key for the TOTP, Base32 encoded (watch out; Base32, NOT Base64!) */
  39.     protected $fofAuth_Key = null;
  40.  
  41.     /** @var array Which formats to be handled by transparent authentication */
  42.     protected $fofAuth_Formats = array('json''csv''xml''raw');
  43.  
  44.     /**
  45.      * Should I logout the transparently authenticated user on logout?
  46.      * Recommended to leave it on in order to avoid crashing the sessions table.
  47.      *
  48.      * @var boolean 
  49.      */
  50.     protected $fofAuth_LogoutOnReturn = true;
  51.  
  52.     /** @var array Which methods to use to fetch authentication credentials and in which order */
  53.     protected $fofAuth_AuthMethods = array(
  54.         /* HTTP Basic Authentication using encrypted information protected
  55.          * with a TOTP (the username must be "_fof_auth") */
  56.         'HTTPBasicAuth_TOTP',
  57.         /* Encrypted information protected with a TOTP passed in the
  58.          * _fofauthentication query string parameter */
  59.         'QueryString_TOTP',
  60.         /* HTTP Basic Authentication using a username and password pair in plain text */
  61.         'HTTPBasicAuth_Plaintext',
  62.         /* Plaintext, JSON-encoded username and password pair passed in the
  63.          * _fofauthentication query string parameter */
  64.         'QueryString_Plaintext',
  65.         /* Plaintext username and password in the _fofauthentication_username
  66.          * and _fofauthentication_username query string parameters */
  67.         'SplitQueryString_Plaintext',
  68.     );
  69.  
  70.     /** @var bool Did we successfully and transparently logged in a user? */
  71.     private $_fofAuth_isLoggedIn false;
  72.  
  73.     /** @var string The calculated encryption key for the _TOTP methods, used if we have to encrypt the reply */
  74.     private $_fofAuth_CryptoKey '';
  75.  
  76.     /**
  77.      * Get a static (Singleton) instance of a particular Dispatcher
  78.      *
  79.      * @param   string  $option  The component name
  80.      * @param   string  $view    The View name
  81.      * @param   array   $config  Configuration data
  82.      *
  83.      * @staticvar  array  $instances  Holds the array of Dispatchers FOF knows about
  84.      *
  85.      * @return  FOFDispatcher 
  86.      */
  87.     public static function &getAnInstance($option null$view null$config array())
  88.     {
  89.         static $instances array();
  90.  
  91.         $hash $option $view;
  92.  
  93.         if (!array_key_exists($hash$instances))
  94.         {
  95.             $instances[$hashself::getTmpInstance($option$view$config);
  96.         }
  97.  
  98.         return $instances[$hash];
  99.     }
  100.  
  101.     /**
  102.      * Gets a temporary instance of a Dispatcher
  103.      *
  104.      * @param   string  $option  The component name
  105.      * @param   string  $view    The View name
  106.      * @param   array   $config  Configuration data
  107.      *
  108.      * @return FOFDispatcher 
  109.      */
  110.     public static function &getTmpInstance($option null$view null$config array())
  111.     {
  112.         if (array_key_exists('input'$config))
  113.         {
  114.             if ($config['input'instanceof FOFInput)
  115.             {
  116.                 $input $config['input'];
  117.             }
  118.             else
  119.             {
  120.                 if (!is_array($config['input']))
  121.                 {
  122.                     $config['input'= (array) $config['input'];
  123.                 }
  124.  
  125.                 $config['input'array_merge($_REQUEST$config['input']);
  126.                 $input new FOFInput($config['input']);
  127.             }
  128.         }
  129.         else
  130.         {
  131.             $input new FOFInput;
  132.         }
  133.  
  134.         $config['option'!is_null($option$option $input->getCmd('option''com_foobar');
  135.         $config['view'!is_null($view$view $input->getCmd('view''');
  136.         $input->set('option'$config['option']);
  137.         $input->set('view'$config['view']);
  138.         $config['input'$input;
  139.  
  140.         $className ucfirst(str_replace('com_'''$config['option'])) 'Dispatcher';
  141.  
  142.         if (!class_exists($className))
  143.         {
  144.             $componentPaths FOFPlatform::getInstance()->getComponentBaseDirs($config['option']);
  145.  
  146.             $searchPaths array(
  147.                 $componentPaths['main'],
  148.                 $componentPaths['main''/dispatchers',
  149.                 $componentPaths['admin'],
  150.                 $componentPaths['admin''/dispatchers'
  151.             );
  152.  
  153.             if (array_key_exists('searchpath'$config))
  154.             {
  155.                 array_unshift($searchPaths$config['searchpath']);
  156.             }
  157.  
  158.             JLoader::import('joomla.filesystem.path');
  159.  
  160.             $path JPath::find(
  161.                     $searchPaths'dispatcher.php'
  162.             );
  163.  
  164.             if ($path)
  165.             {
  166.                 require_once $path;
  167.             }
  168.         }
  169.  
  170.         if (!class_exists($className))
  171.         {
  172.             $className 'FOFDispatcher';
  173.         }
  174.  
  175.         $instance new $className($config);
  176.  
  177.         return $instance;
  178.     }
  179.  
  180.     /**
  181.      * Public constructor
  182.      *
  183.      * @param   array  $config  The configuration variables
  184.      */
  185.     public function __construct($config array())
  186.     {
  187.         // Cache the config
  188.         $this->config $config;
  189.  
  190.         // Get the input for this MVC triad
  191.         if (array_key_exists('input'$config))
  192.         {
  193.             $this->input $config['input'];
  194.         }
  195.         else
  196.         {
  197.             $this->input new FOFInput;
  198.         }
  199.  
  200.         // Get the default values for the component name
  201.         $this->component $this->input->getCmd('option''com_foobar');
  202.  
  203.         // Load the component's fof.xml configuration file
  204.         $configProvider new FOFConfigProvider;
  205.         $this->defaultView $configProvider->get($this->component '.dispatcher.default_view'$this->defaultView);
  206.  
  207.         // Get the default values for the view name
  208.         $this->view $this->input->getCmd('view'null);
  209.  
  210.         if (empty($this->view))
  211.         {
  212.             // Do we have a task formatted as controller.task?
  213.             $task $this->input->getCmd('task''');
  214.  
  215.             if (!empty($task&& (strstr($task'.'!== false))
  216.             {
  217.                 list($this->view$taskexplode('.'$task2);
  218.                 $this->input->set('task'$task);
  219.             }
  220.         }
  221.  
  222.         if (empty($this->view))
  223.         {
  224.             $this->view $this->defaultView;
  225.         }
  226.  
  227.         $this->layout $this->input->getCmd('layout'null);
  228.  
  229.         // Overrides from the config
  230.         if (array_key_exists('option'$config))
  231.         {
  232.             $this->component $config['option'];
  233.         }
  234.  
  235.         if (array_key_exists('view'$config))
  236.         {
  237.             $this->view empty($config['view']$this->view $config['view'];
  238.         }
  239.  
  240.         if (array_key_exists('layout'$config))
  241.         {
  242.             $this->layout $config['layout'];
  243.         }
  244.  
  245.         $this->input->set('option'$this->component);
  246.         $this->input->set('view'$this->view);
  247.         $this->input->set('layout'$this->layout);
  248.     }
  249.  
  250.     /**
  251.      * The main code of the Dispatcher. It spawns the necessary controller and
  252.      * runs it.
  253.      *
  254.      * @return  null|Exception
  255.      */
  256.     public function dispatch()
  257.     {
  258.         if (!FOFPlatform::getInstance()->authorizeAdmin($this->input->getCmd('option''com_foobar')))
  259.         {
  260.             if (FOFPlatform::getInstance()->checkVersion(JVERSION'3.0''ge'))
  261.             {
  262.                 throw new Exception(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN')403);
  263.             }
  264.             else
  265.             {
  266.                 return JError::raiseError('403'JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'));
  267.             }
  268.         }
  269.  
  270.         $this->transparentAuthentication();
  271.  
  272.         // Merge English and local translations
  273.         FOFPlatform::getInstance()->loadTranslations($this->component);
  274.  
  275.         $canDispatch true;
  276.  
  277.         if (FOFPlatform::getInstance()->isCli())
  278.         {
  279.             $canDispatch $canDispatch && $this->onBeforeDispatchCLI();
  280.         }
  281.  
  282.         $canDispatch $canDispatch && $this->onBeforeDispatch();
  283.  
  284.         if (!$canDispatch)
  285.         {
  286.             JResponse::setHeader('Status''403 Forbidden'true);
  287.  
  288.             if (FOFPlatform::getInstance()->checkVersion(JVERSION'3.0''ge'))
  289.             {
  290.                 throw new Exception(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN')403);
  291.             }
  292.             else
  293.             {
  294.                 return JError::raiseError('403'JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'));
  295.             }
  296.         }
  297.  
  298.         // Get and execute the controller
  299.         $option $this->input->getCmd('option''com_foobar');
  300.         $view $this->input->getCmd('view'$this->defaultView);
  301.         $task $this->input->getCmd('task'null);
  302.  
  303.         if (empty($task))
  304.         {
  305.             $task $this->getTask($view);
  306.         }
  307.  
  308.         // Pluralise/sungularise the view name for typical tasks
  309.  
  310.         if (in_array($taskarray('edit''add''read')))
  311.         {
  312.             $view FOFInflector::singularize($view);
  313.         }
  314.         elseif (in_array($taskarray('browse')))
  315.         {
  316.             $view FOFInflector::pluralize($view);
  317.         }
  318.  
  319.         $this->input->set('view'$view);
  320.         $this->input->set('task'$task);
  321.  
  322.         $config $this->config;
  323.         $config['input'$this->input;
  324.  
  325.         $controller FOFController::getTmpInstance($option$view$config);
  326.         $status $controller->execute($task);
  327.  
  328.         if (!$this->onAfterDispatch())
  329.         {
  330.             JResponse::setHeader('Status''403 Forbidden'true);
  331.  
  332.             if (FOFPlatform::getInstance()->checkVersion(JVERSION'3.0''ge'))
  333.             {
  334.                 throw new Exception(JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN')403);
  335.             }
  336.             else
  337.             {
  338.                 return JError::raiseError('403'JText::_('JLIB_APPLICATION_ERROR_ACCESS_FORBIDDEN'));
  339.             }
  340.         }
  341.  
  342.         $format $this->input->get('format''html''cmd');
  343.         $format empty($format'html' $format;
  344.  
  345.         if ($format == 'html')
  346.         {
  347.             // In HTML views perform a redirection
  348.             if ($controller->redirect())
  349.             {
  350.                 return;
  351.             }
  352.         }
  353.         else
  354.         {
  355.             // In non-HTML views just exit the application with the proper HTTP headers
  356.             if ($controller->hasRedirect())
  357.             {
  358.                 $headers JResponse::sendHeaders();
  359.                 jexit();
  360.             }
  361.         }
  362.     }
  363.  
  364.     /**
  365.      * Tries to guess the controller task to execute based on the view name and
  366.      * the HTTP request method.
  367.      *
  368.      * @param   string  $view  The name of the view
  369.      *
  370.      * @return  string  The best guess of the task to execute
  371.      */
  372.     protected function getTask($view)
  373.     {
  374.         // Get a default task based on plural/singular view
  375.         $request_task $this->input->getCmd('task'null);
  376.         $task FOFInflector::isPlural($view'browse' 'edit';
  377.  
  378.         // Get a potential ID, we might need it later
  379.         $id $this->input->get('id'null'int');
  380.  
  381.         if ($id == 0)
  382.         {
  383.             $ids $this->input->get('ids'array()'array');
  384.  
  385.             if (!empty($ids))
  386.             {
  387.                 $id array_shift($ids);
  388.             }
  389.         }
  390.  
  391.         // Check the request method
  392.  
  393.         if (!isset($_SERVER['REQUEST_METHOD']))
  394.         {
  395.             $_SERVER['REQUEST_METHOD''GET';
  396.         }
  397.  
  398.         $requestMethod strtoupper($_SERVER['REQUEST_METHOD']);
  399.  
  400.         switch ($requestMethod)
  401.         {
  402.             case 'POST':
  403.             case 'PUT':
  404.                 if (!is_null($id))
  405.                 {
  406.                     $task 'save';
  407.                 }
  408.                 break;
  409.  
  410.             case 'DELETE':
  411.                 if ($id != 0)
  412.                 {
  413.                     $task 'delete';
  414.                 }
  415.                 break;
  416.  
  417.             case 'GET':
  418.             default:
  419.                 // If it's an edit without an ID or ID=0, it's really an add
  420.                 if (($task == 'edit'&& ($id == 0))
  421.                 {
  422.                     $task 'add';
  423.                 }
  424.  
  425.                 // If it's an edit in the frontend, it's really a read
  426.                 elseif (($task == 'edit'&& FOFPlatform::getInstance()->isFrontend())
  427.                 {
  428.                     $task 'read';
  429.                 }
  430.                 break;
  431.         }
  432.  
  433.         return $task;
  434.     }
  435.  
  436.     /**
  437.      * Executes right before the dispatcher tries to instantiate and run the
  438.      * controller.
  439.      *
  440.      * @return  boolean  Return false to abort
  441.      */
  442.     public function onBeforeDispatch()
  443.     {
  444.         return true;
  445.     }
  446.  
  447.     /**
  448.      * Sets up some environment variables, so we can work as usually on CLI, too.
  449.      *
  450.      * @return  boolean  Return false to abort
  451.      */
  452.     public function onBeforeDispatchCLI()
  453.     {
  454.         JLoader::import('joomla.environment.uri');
  455.         JLoader::import('joomla.application.component.helper');
  456.  
  457.         // Trick to create a valid url used by JURI
  458.         $this->_originalPhpScript '';
  459.  
  460.         // We have no Application Helper (there is no Application!), so I have to define these constants manually
  461.         $option $this->input->get('option''''cmd');
  462.  
  463.         if ($option)
  464.         {
  465.             $componentPaths FOFPlatform::getInstance()->getComponentBaseDirs($option);
  466.  
  467.             if (!defined('JPATH_COMPONENT'))
  468.             {
  469.                 define('JPATH_COMPONENT'$componentPaths['main']);
  470.             }
  471.  
  472.             if (!defined('JPATH_COMPONENT_SITE'))
  473.             {
  474.                 define('JPATH_COMPONENT_SITE'$componentPaths['site']);
  475.             }
  476.  
  477.             if (!defined('JPATH_COMPONENT_ADMINISTRATOR'))
  478.             {
  479.                 define('JPATH_COMPONENT_ADMINISTRATOR'$componentPaths['admin']);
  480.             }
  481.         }
  482.  
  483.         return true;
  484.     }
  485.  
  486.     /**
  487.      * Executes right after the dispatcher runs the controller.
  488.      *
  489.      * @return  boolean  Return false to abort
  490.      */
  491.     public function onAfterDispatch()
  492.     {
  493.         // If we have to log out the user, please do so now
  494.  
  495.         if ($this->fofAuth_LogoutOnReturn && $this->_fofAuth_isLoggedIn)
  496.         {
  497.             FOFPlatform::getInstance()->logoutUser();
  498.         }
  499.  
  500.         return true;
  501.     }
  502.  
  503.     /**
  504.      * Transparently authenticates a user
  505.      *
  506.      * @return  void 
  507.      */
  508.     public function transparentAuthentication()
  509.     {
  510.         // Only run when there is no logged in user
  511.  
  512.         if (!FOFPlatform::getInstance()->getUser()->guest)
  513.         {
  514.             return;
  515.         }
  516.  
  517.         // @todo Check the format
  518.         $format $this->input->getCmd('format''html');
  519.  
  520.         if (!in_array($format$this->fofAuth_Formats))
  521.         {
  522.             return;
  523.         }
  524.  
  525.         foreach ($this->fofAuth_AuthMethods as $method)
  526.         {
  527.             // If we're already logged in, don't bother
  528.  
  529.             if ($this->_fofAuth_isLoggedIn)
  530.             {
  531.                 continue;
  532.             }
  533.  
  534.             // This will hold our authentication data array (username, password)
  535.             $authInfo null;
  536.  
  537.             switch ($method)
  538.             {
  539.                 case 'HTTPBasicAuth_TOTP':
  540.  
  541.                     if (empty($this->fofAuth_Key))
  542.                     {
  543.                         continue;
  544.                     }
  545.  
  546.                     if (!isset($_SERVER['PHP_AUTH_USER']))
  547.                     {
  548.                         continue;
  549.                     }
  550.  
  551.                     if (!isset($_SERVER['PHP_AUTH_PW']))
  552.                     {
  553.                         continue;
  554.                     }
  555.  
  556.                     if ($_SERVER['PHP_AUTH_USER'!= '_fof_auth')
  557.                     {
  558.                         continue;
  559.                     }
  560.  
  561.                     $encryptedData $_SERVER['PHP_AUTH_PW'];
  562.  
  563.                     $authInfo $this->_decryptWithTOTP($encryptedData);
  564.                     break;
  565.  
  566.                 case 'QueryString_TOTP':
  567.                     $encryptedData $this->input->get('_fofauthentication''''raw');
  568.  
  569.                     if (empty($encryptedData))
  570.                     {
  571.                         continue;
  572.                     }
  573.  
  574.                     $authInfo $this->_decryptWithTOTP($encryptedData);
  575.                     break;
  576.  
  577.                 case 'HTTPBasicAuth_Plaintext':
  578.                     if (!isset($_SERVER['PHP_AUTH_USER']))
  579.                     {
  580.                         continue;
  581.                     }
  582.  
  583.                     if (!isset($_SERVER['PHP_AUTH_PW']))
  584.                     {
  585.                         continue;
  586.                     }
  587.  
  588.                     $authInfo array(
  589.                         'username'     => $_SERVER['PHP_AUTH_USER'],
  590.                         'password'     => $_SERVER['PHP_AUTH_PW']
  591.                     );
  592.                     break;
  593.  
  594.                 case 'QueryString_Plaintext':
  595.                     $jsonencoded $this->input->get('_fofauthentication''''raw');
  596.  
  597.                     if (empty($jsonencoded))
  598.                     {
  599.                         continue;
  600.                     }
  601.  
  602.                     $authInfo json_decode($jsonencodedtrue);
  603.  
  604.                     if (!is_array($authInfo))
  605.                     {
  606.                         $authInfo null;
  607.                     }
  608.                     elseif (!array_key_exists('username'$authInfo|| !array_key_exists('password'$authInfo))
  609.                     {
  610.                         $authInfo null;
  611.                     }
  612.                     break;
  613.  
  614.                 case 'SplitQueryString_Plaintext':
  615.                     $authInfo array(
  616.                         'username'     => $this->input->get('_fofauthentication_username''''raw'),
  617.                         'password'     => $this->input->get('_fofauthentication_password''''raw'),
  618.                     );
  619.  
  620.                     if (empty($authInfo['username']))
  621.                     {
  622.                         $authInfo null;
  623.                     }
  624.  
  625.                     break;
  626.  
  627.                 default:
  628.                     continue;
  629.  
  630.                     break;
  631.             }
  632.  
  633.             // No point trying unless we have a username and password
  634.             if (!is_array($authInfo))
  635.             {
  636.                 continue;
  637.             }
  638.  
  639.             $this->_fofAuth_isLoggedIn FOFPlatform::getInstance()->loginUser($authInfo);
  640.         }
  641.     }
  642.  
  643.     /**
  644.      * Decrypts a transparent authentication message using a TOTP
  645.      *
  646.      * @param   string  $encryptedData  The encrypted data
  647.      *
  648.      * @return  array  The decrypted data
  649.      */
  650.     private function _decryptWithTOTP($encryptedData)
  651.     {
  652.         if (empty($this->fofAuth_Key))
  653.         {
  654.             $this->_fofAuth_CryptoKey null;
  655.  
  656.             return null;
  657.         }
  658.  
  659.         $totp new FOFEncryptTotp($this->fofAuth_timeStep);
  660.         $period $totp->getPeriod();
  661.         $period--;
  662.  
  663.         for ($i 0$i <= 2$i++)
  664.         {
  665.             $time ($period $i$this->fofAuth_timeStep;
  666.             $otp $totp->getCode($this->fofAuth_Key$time);
  667.             $this->_fofAuth_CryptoKey hash('sha256'$this->fofAuth_Key $otp);
  668.  
  669.             $aes new FOFEncryptAes($this->_fofAuth_CryptoKey);
  670.             $ret $aes->decryptString($encryptedData);
  671.             $ret rtrim($ret"\000");
  672.  
  673.             $ret json_decode($rettrue);
  674.  
  675.             if (!is_array($ret))
  676.             {
  677.                 continue;
  678.             }
  679.  
  680.             if (!array_key_exists('username'$ret))
  681.             {
  682.                 continue;
  683.             }
  684.  
  685.             if (!array_key_exists('password'$ret))
  686.             {
  687.                 continue;
  688.             }
  689.  
  690.             // Successful decryption!
  691.             return $ret;
  692.         }
  693.  
  694.         // Obviously if we're here we could not decrypt anything. Bail out.
  695.         $this->_fofAuth_CryptoKey null;
  696.  
  697.         return null;
  698.     }
  699.  
  700.     /**
  701.      * Creates a decryption key for use with the TOTP decryption method
  702.      *
  703.      * @param   integer  $time  The timestamp used for TOTP calculation, leave empty to use current timestamp
  704.      *
  705.      * @return  string  THe encryption key
  706.      */
  707.     private function _createDecryptionKey($time null)
  708.     {
  709.         $totp new FOFEncryptTotp($this->fofAuth_timeStep);
  710.         $otp $totp->getCode($this->fofAuth_Key$time);
  711.  
  712.         $key hash('sha256'$this->fofAuth_Key $otp);
  713.  
  714.         return $key;
  715.     }
  716.  
  717.     /**
  718.      * Main function to detect if we're running in a CLI environment and we're admin
  719.      *
  720.      * @return  array  isCLI and isAdmin. It's not an associtive array, so we can use list.
  721.      */
  722.     public static function isCliAdmin()
  723.     {
  724.         static $isCLI   null;
  725.         static $isAdmin null;
  726.  
  727.         if (is_null($isCLI&& is_null($isAdmin))
  728.         {
  729.             $isCLI   FOFPlatform::getInstance()->isCli();
  730.             $isAdmin FOFPlatform::getInstance()->isBackend();
  731.         }
  732.  
  733.         return array($isCLI$isAdmin);
  734.     }
  735. }

Documentation generated on Tue, 19 Nov 2013 15:01:34 +0100 by phpDocumentor 1.4.3