Source for file yubikey.php

Documentation is available at yubikey.php

  1. <?php
  2. /**
  3.  * @package     Joomla.Plugin
  4.  * @subpackage  Twofactorauth.yubikey
  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.  * Joomla! Two Factor Authentication using Yubikey Plugin
  14.  *
  15.  * @package     Joomla.Plugin
  16.  * @subpackage  Twofactorauth.yubikey
  17.  * @since       3.2
  18.  */
  19. {
  20.     /**
  21.      * Affects constructor behavior. If true, language files will be loaded automatically.
  22.      *
  23.      * @var    boolean 
  24.      * @since  3.2
  25.      */
  26.     protected $autoloadLanguage = true;
  27.  
  28.     /**
  29.      * Method name
  30.      *
  31.      * @var    string 
  32.      * @since  3.2
  33.      */
  34.     protected $methodName = 'yubikey';
  35.  
  36.     /**
  37.      * Constructor
  38.      *
  39.      * @param   object  &$subject  The object to observe
  40.      * @param   array   $config    An optional associative array of configuration settings.
  41.      *                              Recognized key values include 'name', 'group', 'params', 'language'
  42.      *                              (this list is not meant to be comprehensive).
  43.      *
  44.      * @since   3.2
  45.      */
  46.     public function __construct(&$subject$config array())
  47.     {
  48.         parent::__construct($subject$config);
  49.  
  50.         // Load the Joomla! RAD layer
  51.         if (!defined('FOF_INCLUDED'))
  52.         {
  53.             include_once JPATH_LIBRARIES '/fof/include.php';
  54.         }
  55.     }
  56.  
  57.     /**
  58.      * This method returns the identification object for this two factor
  59.      * authentication plugin.
  60.      *
  61.      * @return  stdClass  An object with public properties method and title
  62.      *
  63.      * @since   3.2
  64.      */
  65.     public function onUserTwofactorIdentify()
  66.     {
  67.         $section = (int) $this->params->get('section'3);
  68.  
  69.         $current_section 0;
  70.  
  71.         try
  72.         {
  73.             $app JFactory::getApplication();
  74.  
  75.             if ($app->isAdmin())
  76.             {
  77.                 $current_section 2;
  78.             }
  79.             elseif ($app->isSite())
  80.             {
  81.                 $current_section 1;
  82.             }
  83.         }
  84.         catch (Exception $exc)
  85.         {
  86.             $current_section 0;
  87.         }
  88.  
  89.         if (!($current_section $section))
  90.         {
  91.             return false;
  92.         }
  93.  
  94.         return (object) array(
  95.             'method' => $this->methodName,
  96.             'title'  => JText::_('PLG_TWOFACTORAUTH_YUBIKEY_METHOD_TITLE'),
  97.         );
  98.     }
  99.  
  100.     /**
  101.      * Shows the configuration page for this two factor authentication method.
  102.      *
  103.      * @param   object   $otpConfig  The two factor auth configuration object
  104.      * @param   integer  $user_id    The numeric user ID of the user whose form we'll display
  105.      *
  106.      * @return  boolean|string False if the method is not ours, the HTML of the configuration page otherwise
  107.      *
  108.      * @see     UsersModelUser::getOtpConfig
  109.      * @since   3.2
  110.      */
  111.     public function onUserTwofactorShowConfiguration($otpConfig$user_id null)
  112.     {
  113.         if ($otpConfig->method == $this->methodName)
  114.         {
  115.             // This method is already activated. Reuse the same Yubikey ID.
  116.             $yubikey $otpConfig->config['yubikey'];
  117.         }
  118.         else
  119.         {
  120.             // This methods is not activated yet. We'll need a Yubikey TOTP to setup this Yubikey.
  121.             $yubikey '';
  122.         }
  123.  
  124.         // Is this a new TOTP setup? If so, we'll have to show the code
  125.         // validation field.
  126.         $new_totp $otpConfig->method != $this->methodName;
  127.  
  128.         // Start output buffering
  129.         @ob_start();
  130.  
  131.         // Include the form.php from a template override. If none is found use the default.
  132.         $path FOFPlatform::getInstance()->getTemplateOverridePath('plg_twofactorauth_yubikey'true);
  133.  
  134.         JLoader::import('joomla.filesystem.file');
  135.  
  136.         if (JFile::exists($path 'form.php'))
  137.         {
  138.             include_once $path 'form.php';
  139.         }
  140.         else
  141.         {
  142.             include_once __DIR__ . '/tmpl/form.php';
  143.         }
  144.  
  145.         // Stop output buffering and get the form contents
  146.         $html @ob_get_clean();
  147.  
  148.         // Return the form contents
  149.         return array(
  150.             'method' => $this->methodName,
  151.             'form'   => $html,
  152.         );
  153.     }
  154.  
  155.     /**
  156.      * The save handler of the two factor configuration method's configuration
  157.      * page.
  158.      *
  159.      * @param   string  $method  The two factor auth method for which we'll show the config page
  160.      *
  161.      * @return  boolean|stdClass False if the method doesn't match or we have an error, OTP config object if it succeeds
  162.      *
  163.      * @see     UsersModelUser::setOtpConfig
  164.      * @since   3.2
  165.      */
  166.     public function onUserTwofactorApplyConfiguration($method)
  167.     {
  168.         if ($method != $this->methodName)
  169.         {
  170.             return false;
  171.         }
  172.  
  173.         // Get a reference to the input data object
  174.         $input JFactory::getApplication()->input;
  175.  
  176.         // Load raw data
  177.         $rawData $input->get('jform'array()'array');
  178.         $data $rawData['twofactor']['yubikey'];
  179.  
  180.         // Warn if the securitycode is empty
  181.         if (array_key_exists('securitycode'$data&& empty($data['securitycode']))
  182.         {
  183.             try
  184.             {
  185.                 $app JFactory::getApplication();
  186.                 $app->enqueueMessage(JText::_('PLG_TWOFACTORAUTH_YUBIKEY_ERR_VALIDATIONFAILED')'error');
  187.             }
  188.             catch (Exception $exc)
  189.             {
  190.                 // This only happens when we are in a CLI application. We cannot
  191.                 // enqueue a message, so just do nothing.
  192.             }
  193.  
  194.             return false;
  195.         }
  196.  
  197.         // Validate the Yubikey OTP
  198.         $check $this->validateYubikeyOTP($data['securitycode']);
  199.  
  200.         if (!$check)
  201.         {
  202.             $app JFactory::getApplication();
  203.             $app->enqueueMessage(JText::_('PLG_TWOFACTORAUTH_YUBIKEY_ERR_VALIDATIONFAILED')'error');
  204.  
  205.             // Check failed. Do not change two factor authentication settings.
  206.             return false;
  207.         }
  208.  
  209.         // Remove the last 32 digits and store the rest in the user configuration parameters
  210.         $yubikey substr($data['securitycode']0-32);
  211.  
  212.         // Check succeedeed; return an OTP configuration object
  213.         $otpConfig = (object )array(
  214.             'method'    => $this->methodName,
  215.             'config'    => array(
  216.                 'yubikey'    => $yubikey
  217.             ),
  218.             'otep'        => array()
  219.         );
  220.  
  221.         return $otpConfig;
  222.     }
  223.  
  224.     /**
  225.      * This method should handle any two factor authentication and report back
  226.      * to the subject.
  227.      *
  228.      * @param   array   $credentials  Array holding the user credentials
  229.      * @param   array   $options      Array of extra options
  230.      *
  231.      * @return  boolean  True if the user is authorised with this two-factor authentication method
  232.      *
  233.      * @since   3.2
  234.      */
  235.     public function onUserTwofactorAuthenticate($credentials$options)
  236.     {
  237.         // Get the OTP configuration object
  238.         $otpConfig $options['otp_config'];
  239.  
  240.         // Make sure it's an object
  241.         if (empty($otpConfig|| !is_object($otpConfig))
  242.         {
  243.             return false;
  244.         }
  245.  
  246.         // Check if we have the correct method
  247.         if ($otpConfig->method != $this->methodName)
  248.         {
  249.             return false;
  250.         }
  251.  
  252.         // Check if there is a security code
  253.         if (empty($credentials['secretkey']))
  254.         {
  255.             return false;
  256.         }
  257.  
  258.         // Check if the Yubikey starts with the configured Yubikey user string
  259.         $yubikey_valid $otpConfig->config['yubikey'];
  260.         $yubikey substr($credentials['secretkey']0-32);
  261.  
  262.         $check $yubikey == $yubikey_valid;
  263.  
  264.         if ($check)
  265.         {
  266.             $check $this->validateYubikeyOTP($credentials['secretkey']);
  267.         }
  268.  
  269.         return $check;
  270.     }
  271.  
  272.     /**
  273.      * Validates a Yubikey OTP against the Yubikey servers
  274.      *
  275.      * @param   string  $otp  The OTP generated by your Yubikey
  276.      *
  277.      * @return  boolean  True if it's a valid OTP
  278.      *
  279.      * @since   3.2
  280.      */
  281.     public function validateYubikeyOTP($otp)
  282.     {
  283.         $server_queue array(
  284.             'api.yubico.com''api2.yubico.com''api3.yubico.com',
  285.             'api4.yubico.com''api5.yubico.com'
  286.         );
  287.  
  288.         shuffle($server_queue);
  289.  
  290.         $gotResponse false;
  291.         $check false;
  292.  
  293.         $http JHttpFactory::getHttp();
  294.  
  295.         $token JSession::getFormToken();
  296.         $nonce md5($token uniqid(rand()));
  297.  
  298.         while (!$gotResponse && !empty($server_queue))
  299.         {
  300.             $server array_shift($server_queue);
  301.  
  302.             $uri new JUri('https://' $server '/wsapi/2.0/verify');
  303.  
  304.             // I don't see where this ID is used?
  305.             $uri->setVar('id'1);
  306.  
  307.             // The OTP we read from the user
  308.             $uri->setVar('otp'$otp);
  309.  
  310.             // This prevents a REPLAYED_OTP status of the token doesn't change
  311.             // after a user submits an invalid OTP
  312.             $uri->setVar('nonce'$nonce);
  313.  
  314.             // Minimum service level required: 50% (at least 50% of the YubiCloud
  315.             // servers must reply positively for the OTP to validate)
  316.             $uri->setVar('sl'50);
  317.  
  318.             // Timeou waiting for YubiCloud servers to reply: 5 seconds.
  319.             $uri->setVar('timeout'5);
  320.  
  321.             try
  322.             {
  323.                 $response $http->get($uri->toString()null6);
  324.  
  325.                 if (!empty($response))
  326.                 {
  327.                     $gotResponse true;
  328.                 }
  329.                 else
  330.                 {
  331.                     continue;
  332.                 }
  333.             }
  334.             catch (Exception $exc)
  335.             {
  336.                 // No response, continue with the next server
  337.                 continue;
  338.             }
  339.         }
  340.  
  341.         // No server replied; we can't validate this OTP
  342.         if (!$gotResponse)
  343.         {
  344.             return false;
  345.         }
  346.  
  347.         // Parse response
  348.         $lines explode("\n"$response->body);
  349.         $data array();
  350.  
  351.         foreach ($lines as $line)
  352.         {
  353.             $line trim($line);
  354.  
  355.             $parts explode('='$line2);
  356.  
  357.             if (count($parts2)
  358.             {
  359.                 continue;
  360.             }
  361.  
  362.             $data[$parts[0]] $parts[1];
  363.         }
  364.  
  365.         // Validate the response - We need an OK message reply
  366.         if ($data['status'!= 'OK')
  367.         {
  368.             return false;
  369.         }
  370.  
  371.         // Validate the response - We need a confidence level over 50%
  372.         if ($data['sl'50)
  373.         {
  374.             return false;
  375.         }
  376.  
  377.         // Validate the response - The OTP must match
  378.         if ($data['otp'!= $otp)
  379.         {
  380.             return false;
  381.         }
  382.  
  383.         // Validate the response - The token must match
  384.         if ($data['nonce'!= $nonce)
  385.         {
  386.             return false;
  387.         }
  388.  
  389.         return true;
  390.     }
  391. }

Documentation generated on Tue, 19 Nov 2013 15:18:38 +0100 by phpDocumentor 1.4.3