PageRenderTime 50ms CodeModel.GetById 27ms RepoModel.GetById 0ms app.codeStats 0ms

/typo3/sysext/openid/sv1/class.tx_openid_sv1.php

https://github.com/foxsoft/typo3v4core
PHP | 580 lines | 263 code | 50 blank | 267 comment | 67 complexity | 36a4d9260dc10e1978538f693d074241 MD5 | raw file
Possible License(s): Apache-2.0
  1. <?php
  2. /***************************************************************
  3. * Copyright notice
  4. *
  5. * (c) 2008-2010 Dmitry Dulepov <dmitry@typo3.org>
  6. * All rights reserved
  7. *
  8. * This script is part of the TYPO3 project. The TYPO3 project is
  9. * free software; you can redistribute it and/or modify
  10. * it under the terms of the GNU General Public License as published by
  11. * the Free Software Foundation; either version 2 of the License, or
  12. * (at your option) any later version.
  13. *
  14. * The GNU General Public License can be found at
  15. * http://www.gnu.org/copyleft/gpl.html.
  16. *
  17. * This script is distributed in the hope that it will be useful,
  18. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  19. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  20. * GNU General Public License for more details.
  21. *
  22. * This copyright notice MUST APPEAR in all copies of the script!
  23. ***************************************************************/
  24. /**
  25. * [CLASS/FUNCTION INDEX of SCRIPT]
  26. *
  27. *
  28. *
  29. * 57: class tx_openid_sv1 extends t3lib_svbase
  30. * 92: public function init()
  31. * 119: public function initAuth($subType, array $loginData, array $authenticationInformation, t3lib_userAuth &$parentObject)
  32. * 139: public function getUser()
  33. * 176: public function authUser(array $userRecord)
  34. * 221: protected function includePHPOpenIDLibrary()
  35. * 250: protected function getUserRecord($openIDIdentifier)
  36. * 273: protected function getOpenIDConsumer()
  37. * 300: protected function sendOpenIDRequest()
  38. * 368: protected function getReturnURL()
  39. * 414: protected function writeLog($message)
  40. *
  41. * TOTAL FUNCTIONS: 10
  42. * (This index is automatically created/updated by the extension "extdeveval")
  43. *
  44. */
  45. require_once(PATH_t3lib . 'class.t3lib_svbase.php');
  46. require_once(t3lib_extMgm::extPath('openid', 'sv1/class.tx_openid_store.php'));
  47. /**
  48. * Service "OpenID Authentication" for the "openid" extension.
  49. *
  50. * $Id$
  51. *
  52. * @author Dmitry Dulepov <dmitry@typo3.org>
  53. * @package TYPO3
  54. * @subpackage tx_openid
  55. */
  56. class tx_openid_sv1 extends t3lib_svbase {
  57. /** Class name */
  58. public $prefixId = 'tx_openid_sv1'; // Same as class name
  59. /** Path to this script relative to the extension directory */
  60. public $scriptRelPath = 'sv1/class.tx_openid_sv1.php';
  61. /** The extension key */
  62. public $extKey = 'openid';
  63. /** Login data as passed to initAuth() */
  64. protected $loginData = array();
  65. /**
  66. * Additional authentication information provided by t3lib_userAuth. We use
  67. * it to decide what database table contains user records.
  68. */
  69. protected $authenticationInformation = array();
  70. /**
  71. * OpenID response object. It is initialized when OpenID provider returns
  72. * with success/failure response to us.
  73. *
  74. * @var Auth_OpenID_ConsumerResponse
  75. */
  76. protected $openIDResponse = null;
  77. /**
  78. * A reference to the calling object
  79. *
  80. * @var t3lib_userAuth
  81. */
  82. protected $parentObject;
  83. /**
  84. * If set to true, than libraries are already included.
  85. */
  86. protected static $openIDLibrariesIncluded = false;
  87. /**
  88. * Contructs the OpenID authentication service.
  89. */
  90. public function __construct() {
  91. // Auth_Yadis_Yadis::getHTTPFetcher() will use a cURL fetcher if the functionality
  92. // is available in PHP, however the TYPO3 setting is not considered here:
  93. if (!defined('Auth_Yadis_CURL_OVERRIDE')) {
  94. if (!$GLOBALS['TYPO3_CONF_VARS']['SYS']['curlUse']) {
  95. define('Auth_Yadis_CURL_OVERRIDE', true);
  96. }
  97. }
  98. }
  99. /**
  100. * Checks if service is available,. In case of this service we check that
  101. * prerequesties for "PHP OpenID" libraries are fulfilled:
  102. * - GMP or BCMATH PHP extensions are installed and functional
  103. * - set_include_path() PHP function is available
  104. *
  105. * @return boolean true if service is available
  106. */
  107. public function init() {
  108. $available = false;
  109. if (extension_loaded('gmp')) {
  110. $available = is_callable('gmp_init');
  111. } elseif (extension_loaded('bcmath')) {
  112. $available = is_callable('bcadd');
  113. } else {
  114. $this->writeLog('Neither bcmath, nor gmp PHP extension found. OpenID authentication will not be available.');
  115. }
  116. // We also need set_include_path() PHP function
  117. if (!is_callable('set_include_path')) {
  118. $available = false;
  119. $this->writeDevLog('set_include_path() PHP function is not available. OpenID authentication is disabled.');
  120. }
  121. return $available ? parent::init() : false;
  122. }
  123. /**
  124. * Initializes authentication for this service.
  125. *
  126. * @param string $subType: Subtype for authentication (either "getUserFE" or "getUserBE")
  127. * @param array $loginData: Login data submitted by user and preprocessed by t3lib/class.t3lib_userauth.php
  128. * @param array $authenticationInformation: Additional TYPO3 information for authentication services (unused here)
  129. * @param t3lib_userAuth $parentObject: Calling object
  130. * @return void
  131. */
  132. public function initAuth($subType, array $loginData, array $authenticationInformation, t3lib_userAuth &$parentObject) {
  133. // Store login and authetication data
  134. $this->loginData = $loginData;
  135. $this->authenticationInformation = $authenticationInformation;
  136. // If we are here after authentication by the OpenID server, get its response.
  137. if (t3lib_div::_GP('tx_openid_mode') == 'finish' && $this->openIDResponse == null) {
  138. $this->includePHPOpenIDLibrary();
  139. $openIDConsumer = $this->getOpenIDConsumer();
  140. $this->openIDResponse = $openIDConsumer->complete($this->getReturnURL());
  141. }
  142. $this->parentObject = $parentObject;
  143. }
  144. /**
  145. * This function returns the user record back to the t3lib_userAuth. it does not
  146. * mean that user is authenticated, it means only that user is found. This
  147. * function makes sure that user cannot be authenticated by any other service
  148. * if user tries to use OpenID to authenticate.
  149. *
  150. * @return mixed User record (content of fe_users/be_users as appropriate for the current mode)
  151. */
  152. public function getUser() {
  153. $userRecord = null;
  154. if ($this->loginData['status'] == 'login') {
  155. if ($this->openIDResponse instanceof Auth_OpenID_ConsumerResponse) {
  156. $GLOBALS['BACK_PATH'] = $this->getBackPath();
  157. // We are running inside the OpenID return script
  158. // Note: we cannot use $this->openIDResponse->getDisplayIdentifier()
  159. // because it may return a different identifier. For example,
  160. // LiveJournal server converts all underscore characters in the
  161. // original identfier to dashes.
  162. if ($this->openIDResponse->status == Auth_OpenID_SUCCESS) {
  163. $openIDIdentifier = $this->getFinalOpenIDIdentifier();
  164. if ($openIDIdentifier) {
  165. $userRecord = $this->getUserRecord($openIDIdentifier);
  166. if ($userRecord != null) {
  167. $this->writeLog('User \'%s\' logged in with OpenID \'%s\'',
  168. $userRecord[$this->parentObject->formfield_uname], $openIDIdentifier);
  169. } else {
  170. $this->writeLog('Failed to login user using OpenID \'%s\'',
  171. $openIDIdentifier);
  172. }
  173. }
  174. }
  175. } else {
  176. // Here if user just started authentication
  177. $userRecord = $this->getUserRecord($this->loginData['uname']);
  178. }
  179. // The above function will return user record from the OpenID. It means that
  180. // user actually tried to authenticate using his OpenID. In this case
  181. // we must change the password in the record to a long random string so
  182. // that this user cannot be authenticated with other service.
  183. if (is_array($userRecord)) {
  184. $userRecord[$this->authenticationInformation['db_user']['userident_column']] = uniqid($this->prefixId . LF, true);
  185. }
  186. }
  187. return $userRecord;
  188. }
  189. /**
  190. * Authenticates user using OpenID.
  191. *
  192. * @param array $userRecord User record
  193. * @return int Code that shows if user is really authenticated.
  194. * @see t3lib_userAuth::checkAuthentication()
  195. */
  196. public function authUser(array $userRecord) {
  197. $result = 0; // 0 means authentication failure
  198. if ($userRecord['tx_openid_openid'] == '') {
  199. // If user does not have OpenID, let other services to try (code 100)
  200. $result = 100;
  201. } else {
  202. // Check if user is identified by the OpenID
  203. if ($this->openIDResponse instanceof Auth_OpenID_ConsumerResponse) {
  204. // If we have a response, it means OpenID server tried to authenticate
  205. // the user. Now we just look what is the status and provide
  206. // corresponding response to the caller
  207. if ($this->openIDResponse->status == Auth_OpenID_SUCCESS) {
  208. // Success (code 200)
  209. $result = 200;
  210. } else {
  211. $this->writeDevLog('OpenID authentication failed with code \'%s\'.',
  212. $this->openIDResponse->status);
  213. }
  214. } else {
  215. // We may need to send a request to the OpenID server.
  216. // Check if the user identifier looks like OpenID user identifier first.
  217. // Prevent PHP warning in case if identifiers is not an OpenID identifier
  218. // (not an URL).
  219. $urlParts = @parse_url($this->loginData['uname']);
  220. if (is_array($urlParts) && $urlParts['scheme'] != '' && $urlParts['host']) {
  221. // Yes, this looks like a good OpenID. Ask OpenID server (should not return)
  222. $this->sendOpenIDRequest();
  223. // If we are here, it means we have a valid OpenID but failed to
  224. // contact the server. We stop authentication process.
  225. // Alternatively it may mean that OpenID format is not correct.
  226. // In both cases we return code 0 (complete failure)
  227. } else {
  228. $result = 100;
  229. }
  230. }
  231. }
  232. return $result;
  233. }
  234. /**
  235. * Includes necessary files for the PHP OpenID library
  236. *
  237. * @return void
  238. */
  239. protected function includePHPOpenIDLibrary() {
  240. if (!self::$openIDLibrariesIncluded) {
  241. // Prevent further calls
  242. self::$openIDLibrariesIncluded = true;
  243. // PHP OpenID libraries requires adjustments of path settings
  244. $oldIncludePath = get_include_path();
  245. $phpOpenIDLibPath = t3lib_extMgm::extPath('openid') . 'lib/php-openid';
  246. @set_include_path($phpOpenIDLibPath . PATH_SEPARATOR .
  247. $phpOpenIDLibPath . PATH_SEPARATOR . 'Auth' .
  248. PATH_SEPARATOR . $oldIncludePath);
  249. // Make sure that random generator is properly set up. Constant could be
  250. // defined by the previous inclusion of the file
  251. if (!defined('Auth_OpenID_RAND_SOURCE')) {
  252. if (TYPO3_OS == 'WIN') {
  253. // No random generator on Windows!
  254. define('Auth_OpenID_RAND_SOURCE', null);
  255. } elseif (!is_readable('/dev/urandom')) {
  256. if (is_readable('/dev/random')) {
  257. define('Auth_OpenID_RAND_SOURCE', '/dev/random');
  258. } else {
  259. define('Auth_OpenID_RAND_SOURCE', null);
  260. }
  261. }
  262. }
  263. // Include files
  264. require_once($phpOpenIDLibPath . '/Auth/OpenID/Consumer.php');
  265. // Restore path
  266. @set_include_path($oldIncludePath);
  267. if (!is_array($_SESSION)) {
  268. // Yadis requires session but session is not initialized when
  269. // processing Backend authentication
  270. @session_start();
  271. $this->writeLog('Session is initialized');
  272. }
  273. }
  274. }
  275. /**
  276. * Gets user record for the user with the OpenID provided by the user
  277. *
  278. * @param string $openIDIdentifier OpenID identifier to search for
  279. * @return array Database fields from the table that corresponds to the current login mode (FE/BE)
  280. */
  281. protected function getUserRecord($openIDIdentifier) {
  282. $record = null;
  283. if ($openIDIdentifier) {
  284. list($record) = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('*',
  285. $this->authenticationInformation['db_user']['table'],
  286. 'tx_openid_openid=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($openIDIdentifier, $this->authenticationInformation['db_user']['table']) .
  287. $this->authenticationInformation['db_user']['check_pid_clause'] .
  288. $this->authenticationInformation['db_user']['enable_clause']);
  289. } else {
  290. // This should never happen and generally means hack attempt.
  291. // We just log it and do not return any records.
  292. $this->writeLog('getUserRecord is called with the empty OpenID');
  293. }
  294. return $record;
  295. }
  296. /**
  297. * Creates OpenID Consumer object with a TYPO3-specific store. This function
  298. * is almost identical to the example from the PHP OpenID library.
  299. * @todo use DB (or the caching framework) instead of the filesystem to store OpenID data
  300. * @return Auth_OpenID_Consumer Consumer instance
  301. */
  302. protected function getOpenIDConsumer() {
  303. $openIDStore = t3lib_div::makeInstance('tx_openid_store');
  304. /* @var $openIDStore tx_openid_store */
  305. $openIDStore->cleanup();
  306. return new Auth_OpenID_Consumer($openIDStore);
  307. }
  308. /**
  309. * Sends request to the OpenID server to authenticate the user with the
  310. * given ID. This function is almost identical to the example from the PHP
  311. * OpenID library. Due to the OpenID specification we cannot do a slient login.
  312. * Sometimes we have to redirect to the OpenID provider web site so that
  313. * user can enter his password there. In this case we will redirect and provide
  314. * a return adress to the special script inside this directory, which will
  315. * handle the result appropriately.
  316. *
  317. * This function does not return on success. If it returns, it means something
  318. * went totally wrong with OpenID.
  319. *
  320. * @return void
  321. */
  322. protected function sendOpenIDRequest() {
  323. $this->includePHPOpenIDLibrary();
  324. $openIDIdentifier = $this->loginData['uname'];
  325. // Initialize OpenID client system, get the consumer
  326. $openIDConsumer = $this->getOpenIDConsumer();
  327. // Begin the OpenID authentication process
  328. $authenticationRequest = $openIDConsumer->begin($openIDIdentifier);
  329. if (!$authenticationRequest) {
  330. // Not a valid OpenID. Since it can be some other ID, we just return
  331. // and let other service handle it.
  332. $this->writeLog('Could not create authentication request for OpenID identifier \'%s\'', $openIDIdentifier);
  333. return;
  334. }
  335. // Redirect the user to the OpenID server for authentication.
  336. // Store the token for this authentication so we can verify the
  337. // response.
  338. // For OpenID version 1, we *should* send a redirect. For OpenID version 2,
  339. // we should use a Javascript form to send a POST request to the server.
  340. $returnURL = $this->getReturnURL();
  341. $trustedRoot = t3lib_div::getIndpEnv('TYPO3_SITE_URL');
  342. if ($authenticationRequest->shouldSendRedirect()) {
  343. $redirectURL = $authenticationRequest->redirectURL($trustedRoot, $returnURL);
  344. // If the redirect URL can't be built, return. We can only return.
  345. if (Auth_OpenID::isFailure($redirectURL)) {
  346. $this->writeLog('Authentication request could not create redirect URL for OpenID identifier \'%s\'', $openIDIdentifier);
  347. return;
  348. }
  349. // Send redirect. We use 303 code because it allows to redirect POST
  350. // requests without resending the form. This is exactly what we need here.
  351. // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
  352. @ob_end_clean();
  353. t3lib_utility_Http::redirect($redirectURL, t3lib_utility_Http::HTTP_STATUS_303);
  354. } else {
  355. $formHtml = $authenticationRequest->htmlMarkup($trustedRoot,
  356. $returnURL, false, array('id' => 'openid_message'));
  357. // Display an error if the form markup couldn't be generated;
  358. // otherwise, render the HTML.
  359. if (Auth_OpenID::isFailure($formHtml)) {
  360. // Form markup cannot be generated
  361. $this->writeLog('Could not create form markup for OpenID identifier \'%s\'', $openIDIdentifier);
  362. return;
  363. } else {
  364. @ob_end_clean();
  365. echo $formHtml;
  366. }
  367. }
  368. // If we reached this point, we must not return!
  369. exit;
  370. }
  371. /**
  372. * Creates return URL for the OpenID server. When a user is authenticated by
  373. * the OpenID server, the user will be sent to this URL to complete
  374. * authentication process with the current site. We send it to our script.
  375. *
  376. * @return string Return URL
  377. */
  378. protected function getReturnURL() {
  379. if ($this->authenticationInformation['loginType'] == 'FE') {
  380. // We will use eID to send user back, create session data and
  381. // return to the calling page.
  382. // Notice: 'pid' and 'logintype' parameter names cannot be changed!
  383. // They are essential for FE user authentication.
  384. $returnURL = 'index.php?eID=tx_openid&' .
  385. 'pid=' . $this->authenticationInformation['db_user']['checkPidList'] . '&' .
  386. 'logintype=login&';
  387. } else {
  388. // In the Backend we will use dedicated script to create session.
  389. // It is much easier for the Backend to manage users.
  390. // Notice: 'login_status' parameter name cannot be changed!
  391. // It is essential for BE user authentication.
  392. $absoluteSiteURL = substr(t3lib_div::getIndpEnv('TYPO3_SITE_URL'), strlen(t3lib_div::getIndpEnv('TYPO3_REQUEST_HOST')));
  393. $returnURL = $absoluteSiteURL . TYPO3_mainDir . 'sysext/' . $this->extKey . '/class.tx_openid_return.php?login_status=login&';
  394. }
  395. if (t3lib_div::_GP('tx_openid_mode') == 'finish') {
  396. $requestURL = t3lib_div::_GP('tx_openid_location');
  397. $claimedIdentifier = t3lib_div::_GP('tx_openid_claimed');
  398. } else {
  399. $requestURL = t3lib_div::getIndpEnv('TYPO3_REQUEST_URL');
  400. $claimedIdentifier = $this->loginData['uname'];
  401. }
  402. $returnURL .= 'tx_openid_location=' . rawurlencode($requestURL) . '&' .
  403. 'tx_openid_mode=finish&' .
  404. 'tx_openid_claimed=' . rawurlencode($claimedIdentifier) . '&' .
  405. 'tx_openid_signature=' . $this->getSignature($claimedIdentifier);
  406. return t3lib_div::locationHeaderUrl($returnURL);
  407. }
  408. /**
  409. * Signs claimed id.
  410. *
  411. * @return void
  412. */
  413. protected function getSignature($claimedIdentifier) {
  414. // You can also increase security by using sha1 (beware of too long URLs!)
  415. return md5(implode('/', array(
  416. $claimedIdentifier,
  417. strval(strlen($claimedIdentifier)),
  418. $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']
  419. )));
  420. }
  421. /**
  422. * Calculates the path to the TYPO3 directory from the current directory
  423. *
  424. * @return string
  425. */
  426. protected function getBackPath() {
  427. $extPath = t3lib_extMgm::siteRelPath('openid');
  428. $segmentCount = count(explode('/', $extPath));
  429. $path = str_pad('', $segmentCount*3, '../') . TYPO3_mainDir;
  430. return $path;
  431. }
  432. /**
  433. * Obtains a real identifier for the user
  434. *
  435. * @return string
  436. */
  437. protected function getFinalOpenIDIdentifier() {
  438. $result = $this->getSignedParameter('openid_identity');
  439. if (!$result) {
  440. $result = $this->getSignedParameter('openid_claimed_id');
  441. }
  442. if (!$result) {
  443. $result = $this->getSignedClaimedOpenIDIdentifier();
  444. }
  445. $result = $this->getAdjustedOpenIDIdentifier($result);
  446. return $result;
  447. }
  448. /**
  449. * Gets the signed OpenID that was sent back to this service.
  450. *
  451. * @return string The signed OpenID, if signature did not match this is empty
  452. */
  453. protected function getSignedClaimedOpenIDIdentifier() {
  454. $result = t3lib_div::_GP('tx_openid_claimed');
  455. $signature = $this->getSignature($result);
  456. if ($signature !== t3lib_div::_GP('tx_openid_signature')) {
  457. $result = '';
  458. }
  459. return $result;
  460. }
  461. /**
  462. * Adjusts the OpenID identifier to to claimed OpenID, if the only difference
  463. * is in normalizing the URLs. Example:
  464. * + OpenID returned from provider: https://account.provider.net/
  465. * + OpenID used in TYPO3: https://account.provider.net (not normalized)
  466. *
  467. * @param string $openIDIdentifier The OpenID returned by the OpenID provider
  468. * @return string Adjusted OpenID identifier
  469. */
  470. protected function getAdjustedOpenIDIdentifier($openIDIdentifier) {
  471. $result = '';
  472. $claimedOpenIDIdentifier = $this->getSignedClaimedOpenIDIdentifier();
  473. $pattern = '#^' . preg_quote($claimedOpenIDIdentifier, '#') . '/?$#';
  474. if (preg_match($pattern, $openIDIdentifier)) {
  475. $result = $claimedOpenIDIdentifier;
  476. }
  477. return $result;
  478. }
  479. /**
  480. * Obtains a value of the parameter if it is signed. If not signed, then
  481. * empty string is returned.
  482. *
  483. * @param string $parameterName Must start with 'openid_'
  484. * @return string
  485. */
  486. protected function getSignedParameter($parameterName) {
  487. $signedParametersList = t3lib_div::_GP('openid_signed');
  488. if (t3lib_div::inList($signedParametersList, substr($parameterName, 7))) {
  489. $result = t3lib_div::_GP($parameterName);
  490. } else {
  491. $result = '';
  492. }
  493. return $result;
  494. }
  495. /**
  496. * Writes log message. Destination log depends on the current system mode.
  497. * For FE the function writes to the admin panel log. For BE messages are
  498. * sent to the system log. If developer log is enabled, messages are also
  499. * sent there.
  500. *
  501. * This function accepts variable number of arguments and can format
  502. * parameters. The syntax is the same as for sprintf()
  503. *
  504. * @param string $message Message to output
  505. * @return void
  506. * @see sprintf()
  507. * @see t3lib::divLog()
  508. * @see t3lib_div::sysLog()
  509. * @see t3lib_timeTrack::setTSlogMessage()
  510. */
  511. protected function writeLog($message) {
  512. if (func_num_args() > 1) {
  513. $params = func_get_args();
  514. array_shift($params);
  515. $message = vsprintf($message, $params);
  516. }
  517. if (TYPO3_MODE == 'BE') {
  518. t3lib_div::sysLog($message, $this->extKey, 1);
  519. } else {
  520. $GLOBALS['TT']->setTSlogMessage($message);
  521. }
  522. if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['enable_DLOG']) {
  523. t3lib_div::devLog($message, $this->extKey, 1);
  524. }
  525. }
  526. }
  527. if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/openid/sv1/class.tx_openid_sv1.php']) {
  528. include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/openid/sv1/class.tx_openid_sv1.php']);
  529. }
  530. ?>