PageRenderTime 26ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/home/plugins/twofactorauth/yubikey/yubikey.php

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