TYPO3 API  SVNRelease
class.tx_openid_sv1.php
Go to the documentation of this file.
00001 <?php
00002 /***************************************************************
00003 *  Copyright notice
00004 *
00005 *  (c) 2008-2011 Dmitry Dulepov <dmitry@typo3.org>
00006 *  All rights reserved
00007 *
00008 *  This script is part of the TYPO3 project. The TYPO3 project is
00009 *  free software; you can redistribute it and/or modify
00010 *  it under the terms of the GNU General Public License as published by
00011 *  the Free Software Foundation; either version 2 of the License, or
00012 *  (at your option) any later version.
00013 *
00014 *  The GNU General Public License can be found at
00015 *  http://www.gnu.org/copyleft/gpl.html.
00016 *
00017 *  This script is distributed in the hope that it will be useful,
00018 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
00019 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
00020 *  GNU General Public License for more details.
00021 *
00022 *  This copyright notice MUST APPEAR in all copies of the script!
00023 ***************************************************************/
00024 /**
00025  * [CLASS/FUNCTION INDEX of SCRIPT]
00026  *
00027  *
00028  *
00029  *   57: class tx_openid_sv1 extends t3lib_svbase
00030  *   92:     public function init()
00031  *  119:     public function initAuth($subType, array $loginData, array $authenticationInformation, t3lib_userAuth &$parentObject)
00032  *  139:     public function getUser()
00033  *  176:     public function authUser(array $userRecord)
00034  *  221:     protected function includePHPOpenIDLibrary()
00035  *  250:     protected function getUserRecord($openIDIdentifier)
00036  *  273:     protected function getOpenIDConsumer()
00037  *  300:     protected function sendOpenIDRequest()
00038  *  368:     protected function getReturnURL()
00039  *  414:     protected function writeLog($message)
00040  *
00041  * TOTAL FUNCTIONS: 10
00042  * (This index is automatically created/updated by the extension "extdeveval")
00043  *
00044  */
00045 
00046 require_once(PATH_t3lib . 'class.t3lib_svbase.php');
00047 require_once(t3lib_extMgm::extPath('openid', 'sv1/class.tx_openid_store.php'));
00048 
00049 /**
00050  * Service "OpenID Authentication" for the "openid" extension.
00051  *
00052  * $Id: class.tx_openid_sv1.php 10120 2011-01-18 20:03:36Z ohader $
00053  *
00054  * @author  Dmitry Dulepov <dmitry@typo3.org>
00055  * @package TYPO3
00056  * @subpackage  tx_openid
00057  */
00058 class tx_openid_sv1 extends t3lib_svbase {
00059     /** Class name */
00060     public $prefixId = 'tx_openid_sv1';     // Same as class name
00061 
00062     /** Path to this script relative to the extension directory */
00063     public $scriptRelPath = 'sv1/class.tx_openid_sv1.php';
00064 
00065     /** The extension key */
00066     public $extKey = 'openid';
00067 
00068     /** Login data as passed to initAuth() */
00069     protected $loginData = array();
00070 
00071     /**
00072      * Additional authentication information provided by t3lib_userAuth. We use
00073      * it to decide what database table contains user records.
00074      */
00075     protected $authenticationInformation = array();
00076 
00077     /**
00078      * OpenID response object. It is initialized when OpenID provider returns
00079      * with success/failure response to us.
00080      *
00081      * @var Auth_OpenID_ConsumerResponse
00082      */
00083     protected $openIDResponse = null;
00084 
00085     /**
00086      * A reference to the calling object
00087      *
00088      * @var t3lib_userAuth
00089      */
00090     protected $parentObject;
00091 
00092     /**
00093      * If set to true, than libraries are already included.
00094      */
00095     protected static $openIDLibrariesIncluded = false;
00096 
00097     /**
00098      * Contructs the OpenID authentication service.
00099      */
00100     public function __construct() {
00101         // Auth_Yadis_Yadis::getHTTPFetcher() will use a cURL fetcher if the functionality
00102         // is available in PHP, however the TYPO3 setting is not considered here:
00103         if (!defined('Auth_Yadis_CURL_OVERRIDE')) {
00104             if (!$GLOBALS['TYPO3_CONF_VARS']['SYS']['curlUse']) {
00105                 define('Auth_Yadis_CURL_OVERRIDE', true);
00106             }
00107         }
00108     }
00109 
00110     /**
00111      * Checks if service is available,. In case of this service we check that
00112      * prerequesties for "PHP OpenID" libraries are fulfilled:
00113      * - GMP or BCMATH PHP extensions are installed and functional
00114      * - set_include_path() PHP function is available
00115      *
00116      * @return  boolean     true if service is available
00117      */
00118     public function init() {
00119         $available = false;
00120         if (extension_loaded('gmp')) {
00121             $available = is_callable('gmp_init');
00122         } elseif (extension_loaded('bcmath')) {
00123             $available = is_callable('bcadd');
00124         } else {
00125             $this->writeLog('Neither bcmath, nor gmp PHP extension found. OpenID authentication will not be available.');
00126         }
00127         // We also need set_include_path() PHP function
00128         if (!is_callable('set_include_path')) {
00129             $available = false;
00130             $this->writeDevLog('set_include_path() PHP function is not available. OpenID authentication is disabled.');
00131         }
00132         return $available ? parent::init() : false;
00133     }
00134 
00135     /**
00136      * Initializes authentication for this service.
00137      *
00138      * @param   string          $subType: Subtype for authentication (either "getUserFE" or "getUserBE")
00139      * @param   array           $loginData: Login data submitted by user and preprocessed by t3lib/class.t3lib_userauth.php
00140      * @param   array           $authenticationInformation: Additional TYPO3 information for authentication services (unused here)
00141      * @param   t3lib_userAuth  $parentObject: Calling object
00142      * @return  void
00143      */
00144     public function initAuth($subType, array $loginData, array $authenticationInformation, t3lib_userAuth &$parentObject) {
00145         // Store login and authetication data
00146         $this->loginData = $loginData;
00147         $this->authenticationInformation = $authenticationInformation;
00148         // If we are here after authentication by the OpenID server, get its response.
00149         if (t3lib_div::_GP('tx_openid_mode') == 'finish' && $this->openIDResponse == null) {
00150             $this->includePHPOpenIDLibrary();
00151             $openIDConsumer = $this->getOpenIDConsumer();
00152             $this->openIDResponse = $openIDConsumer->complete($this->getReturnURL());
00153         }
00154         $this->parentObject = $parentObject;
00155     }
00156 
00157     /**
00158      * This function returns the user record back to the t3lib_userAuth. it does not
00159      * mean that user is authenticated, it means only that user is found. This
00160      * function makes sure that user cannot be authenticated by any other service
00161      * if user tries to use OpenID to authenticate.
00162      *
00163      * @return  mixed       User record (content of fe_users/be_users as appropriate for the current mode)
00164      */
00165     public function getUser() {
00166         $userRecord = null;
00167         if ($this->loginData['status'] == 'login') {
00168             if ($this->openIDResponse instanceof Auth_OpenID_ConsumerResponse) {
00169                 $GLOBALS['BACK_PATH'] = $this->getBackPath();
00170                 // We are running inside the OpenID return script
00171                 // Note: we cannot use $this->openIDResponse->getDisplayIdentifier()
00172                 // because it may return a different identifier. For example,
00173                 // LiveJournal server converts all underscore characters in the
00174                 // original identfier to dashes.
00175                 if ($this->openIDResponse->status == Auth_OpenID_SUCCESS) {
00176                     $openIDIdentifier = $this->getFinalOpenIDIdentifier();
00177                     if ($openIDIdentifier) {
00178                         $userRecord = $this->getUserRecord($openIDIdentifier);
00179                         if ($userRecord != null) {
00180                             $this->writeLog('User \'%s\' logged in with OpenID \'%s\'',
00181                                 $userRecord[$this->parentObject->formfield_uname], $openIDIdentifier);
00182                         } else {
00183                             $this->writeLog('Failed to login user using OpenID \'%s\'',
00184                                 $openIDIdentifier);
00185                         }
00186                     }
00187                 }
00188             } else {
00189                 // Here if user just started authentication
00190                 $userRecord = $this->getUserRecord($this->loginData['uname']);
00191             }
00192             // The above function will return user record from the OpenID. It means that
00193             // user actually tried to authenticate using his OpenID. In this case
00194             // we must change the password in the record to a long random string so
00195             // that this user cannot be authenticated with other service.
00196             if (is_array($userRecord)) {
00197                 $userRecord[$this->authenticationInformation['db_user']['userident_column']] = uniqid($this->prefixId . LF, true);
00198             }
00199         }
00200         return $userRecord;
00201     }
00202 
00203     /**
00204      * Authenticates user using OpenID.
00205      *
00206      * @param   array       $userRecord User record
00207      * @return  int     Code that shows if user is really authenticated.
00208      * @see t3lib_userAuth::checkAuthentication()
00209      */
00210     public function authUser(array $userRecord) {
00211         $result = 0;    // 0 means authentication failure
00212 
00213         if ($userRecord['tx_openid_openid'] == '') {
00214             // If user does not have OpenID, let other services to try (code 100)
00215             $result = 100;
00216         } else {
00217             // Check if user is identified by the OpenID
00218             if ($this->openIDResponse instanceof Auth_OpenID_ConsumerResponse) {
00219                 // If we have a response, it means OpenID server tried to authenticate
00220                 // the user. Now we just look what is the status and provide
00221                 // corresponding response to the caller
00222                 if ($this->openIDResponse->status == Auth_OpenID_SUCCESS) {
00223                     // Success (code 200)
00224                     $result = 200;
00225                 } else {
00226                     $this->writeDevLog('OpenID authentication failed with code \'%s\'.',
00227                             $this->openIDResponse->status);
00228                 }
00229             } else {
00230                 // We may need to send a request to the OpenID server.
00231                 // Check if the user identifier looks like OpenID user identifier first.
00232                 // Prevent PHP warning in case if identifiers is not an OpenID identifier
00233                 // (not an URL).
00234                 $urlParts = @parse_url($this->loginData['uname']);
00235                 if (is_array($urlParts) && $urlParts['scheme'] != '' && $urlParts['host']) {
00236                     // Yes, this looks like a good OpenID. Ask OpenID server (should not return)
00237                     $this->sendOpenIDRequest();
00238                     // If we are here, it means we have a valid OpenID but failed to
00239                     // contact the server. We stop authentication process.
00240                     // Alternatively it may mean that OpenID format is not correct.
00241                     // In both cases we return code 0 (complete failure)
00242                 } else {
00243                     $result = 100;
00244                 }
00245             }
00246         }
00247 
00248         return $result;
00249     }
00250 
00251     /**
00252      * Includes necessary files for the PHP OpenID library
00253      *
00254      * @return  void
00255      */
00256     protected function includePHPOpenIDLibrary() {
00257         if (!self::$openIDLibrariesIncluded) {
00258 
00259             // Prevent further calls
00260             self::$openIDLibrariesIncluded = true;
00261 
00262             // PHP OpenID libraries requires adjustments of path settings
00263             $oldIncludePath = get_include_path();
00264             $phpOpenIDLibPath = t3lib_extMgm::extPath('openid') . 'lib/php-openid';
00265             @set_include_path($phpOpenIDLibPath . PATH_SEPARATOR .
00266                             $phpOpenIDLibPath . PATH_SEPARATOR . 'Auth' .
00267                             PATH_SEPARATOR . $oldIncludePath);
00268 
00269             // Make sure that random generator is properly set up. Constant could be
00270             // defined by the previous inclusion of the file
00271             if (!defined('Auth_OpenID_RAND_SOURCE')) {
00272                 if (TYPO3_OS == 'WIN') {
00273                     // No random generator on Windows!
00274                     define('Auth_OpenID_RAND_SOURCE', null);
00275                 } elseif (!is_readable('/dev/urandom')) {
00276                     if (is_readable('/dev/random')) {
00277                         define('Auth_OpenID_RAND_SOURCE', '/dev/random');
00278                     } else {
00279                         define('Auth_OpenID_RAND_SOURCE', null);
00280                     }
00281                 }
00282             }
00283 
00284             // Include files
00285             require_once($phpOpenIDLibPath . '/Auth/OpenID/Consumer.php');
00286 
00287             // Restore path
00288             @set_include_path($oldIncludePath);
00289 
00290             if (!is_array($_SESSION)) {
00291                 // Yadis requires session but session is not initialized when
00292                 // processing Backend authentication
00293                 @session_start();
00294                 $this->writeLog('Session is initialized');
00295             }
00296         }
00297     }
00298 
00299     /**
00300      * Gets user record for the user with the OpenID provided by the user
00301      *
00302      * @param   string      $openIDIdentifier   OpenID identifier to search for
00303      * @return  array       Database fields from the table that corresponds to the current login mode (FE/BE)
00304      */
00305     protected function getUserRecord($openIDIdentifier) {
00306         $record = null;
00307         if ($openIDIdentifier) {
00308             $record = $GLOBALS['TYPO3_DB']->exec_SELECTgetSingleRow('*',
00309                 $this->authenticationInformation['db_user']['table'],
00310                 'tx_openid_openid=' . $GLOBALS['TYPO3_DB']->fullQuoteStr($openIDIdentifier, $this->authenticationInformation['db_user']['table']) .
00311                     $this->authenticationInformation['db_user']['check_pid_clause'] .
00312                     $this->authenticationInformation['db_user']['enable_clause']);
00313         } else {
00314             // This should never happen and generally means hack attempt.
00315             // We just log it and do not return any records.
00316             $this->writeLog('getUserRecord is called with the empty OpenID');
00317         }
00318         return $record;
00319     }
00320 
00321     /**
00322      * Creates OpenID Consumer object with a TYPO3-specific store. This function
00323      * is almost identical to the example from the PHP OpenID library.
00324      * @todo use DB (or the caching framework) instead of the filesystem to store OpenID data
00325      * @return  Auth_OpenID_Consumer        Consumer instance
00326      */
00327     protected function getOpenIDConsumer() {
00328         $openIDStore = t3lib_div::makeInstance('tx_openid_store');
00329         /* @var $openIDStore tx_openid_store */
00330         $openIDStore->cleanup();
00331 
00332         return new Auth_OpenID_Consumer($openIDStore);
00333     }
00334 
00335     /**
00336      * Sends request to the OpenID server to authenticate the user with the
00337      * given ID. This function is almost identical to the example from the PHP
00338      * OpenID library. Due to the OpenID specification we cannot do a slient login.
00339      * Sometimes we have to redirect to the OpenID provider web site so that
00340      * user can enter his password there. In this case we will redirect and provide
00341      * a return adress to the special script inside this directory, which will
00342      * handle the result appropriately.
00343      *
00344      * This function does not return on success. If it returns, it means something
00345      * went totally wrong with OpenID.
00346      *
00347      * @return  void
00348      */
00349     protected function sendOpenIDRequest() {
00350         $this->includePHPOpenIDLibrary();
00351 
00352         $openIDIdentifier = $this->loginData['uname'];
00353 
00354         // Initialize OpenID client system, get the consumer
00355         $openIDConsumer = $this->getOpenIDConsumer();
00356 
00357         // Begin the OpenID authentication process
00358         $authenticationRequest = $openIDConsumer->begin($openIDIdentifier);
00359         if (!$authenticationRequest) {
00360             // Not a valid OpenID. Since it can be some other ID, we just return
00361             // and let other service handle it.
00362             $this->writeLog('Could not create authentication request for OpenID identifier \'%s\'', $openIDIdentifier);
00363             return;
00364         }
00365 
00366         // Redirect the user to the OpenID server for authentication.
00367         // Store the token for this authentication so we can verify the
00368         // response.
00369 
00370         // For OpenID version 1, we *should* send a redirect. For OpenID version 2,
00371         // we should use a Javascript form to send a POST request to the server.
00372         $returnURL = $this->getReturnURL();
00373         $trustedRoot = t3lib_div::getIndpEnv('TYPO3_SITE_URL');
00374 
00375         if ($authenticationRequest->shouldSendRedirect()) {
00376             $redirectURL = $authenticationRequest->redirectURL($trustedRoot, $returnURL);
00377 
00378             // If the redirect URL can't be built, return. We can only return.
00379             if (Auth_OpenID::isFailure($redirectURL)) {
00380                 $this->writeLog('Authentication request could not create redirect URL for OpenID identifier \'%s\'', $openIDIdentifier);
00381                 return;
00382             }
00383 
00384             // Send redirect. We use 303 code because it allows to redirect POST
00385             // requests without resending the form. This is exactly what we need here.
00386             // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
00387             @ob_end_clean();
00388             t3lib_utility_Http::redirect($redirectURL, t3lib_utility_Http::HTTP_STATUS_303);
00389         } else {
00390             $formHtml = $authenticationRequest->htmlMarkup($trustedRoot,
00391                             $returnURL, false, array('id' => 'openid_message'));
00392 
00393             // Display an error if the form markup couldn't be generated;
00394             // otherwise, render the HTML.
00395             if (Auth_OpenID::isFailure($formHtml)) {
00396                 // Form markup cannot be generated
00397                 $this->writeLog('Could not create form markup for OpenID identifier \'%s\'', $openIDIdentifier);
00398                 return;
00399             } else {
00400                 @ob_end_clean();
00401                 echo $formHtml;
00402             }
00403         }
00404         // If we reached this point, we must not return!
00405         exit;
00406     }
00407 
00408     /**
00409      * Creates return URL for the OpenID server. When a user is authenticated by
00410      * the OpenID server, the user will be sent to this URL to complete
00411      * authentication process with the current site. We send it to our script.
00412      *
00413      * @return  string      Return URL
00414      */
00415     protected function getReturnURL() {
00416         if ($this->authenticationInformation['loginType'] == 'FE') {
00417             // We will use eID to send user back, create session data and
00418             // return to the calling page.
00419             // Notice: 'pid' and 'logintype' parameter names cannot be changed!
00420             // They are essential for FE user authentication.
00421             $returnURL = 'index.php?eID=tx_openid&' .
00422                         'pid=' . $this->authenticationInformation['db_user']['checkPidList'] . '&' .
00423                         'logintype=login&';
00424         } else {
00425             // In the Backend we will use dedicated script to create session.
00426             // It is much easier for the Backend to manage users.
00427             // Notice: 'login_status' parameter name cannot be changed!
00428             // It is essential for BE user authentication.
00429             $absoluteSiteURL = substr(t3lib_div::getIndpEnv('TYPO3_SITE_URL'), strlen(t3lib_div::getIndpEnv('TYPO3_REQUEST_HOST')));
00430             $returnURL = $absoluteSiteURL . TYPO3_mainDir . 'sysext/' . $this->extKey . '/class.tx_openid_return.php?login_status=login&';
00431         }
00432         if (t3lib_div::_GP('tx_openid_mode') == 'finish') {
00433             $requestURL = t3lib_div::_GP('tx_openid_location');
00434             $claimedIdentifier = t3lib_div::_GP('tx_openid_claimed');
00435         } else {
00436             $requestURL = t3lib_div::getIndpEnv('TYPO3_REQUEST_URL');
00437             $claimedIdentifier = $this->loginData['uname'];
00438         }
00439         $returnURL .= 'tx_openid_location=' . rawurlencode($requestURL) . '&' .
00440                         'tx_openid_mode=finish&' .
00441                         'tx_openid_claimed=' . rawurlencode($claimedIdentifier) . '&' .
00442                         'tx_openid_signature=' . $this->getSignature($claimedIdentifier);
00443         return t3lib_div::locationHeaderUrl($returnURL);
00444     }
00445 
00446     /**
00447      * Signs claimed id.
00448      *
00449      * @return void
00450      */
00451     protected function getSignature($claimedIdentifier) {
00452         // You can also increase security by using sha1 (beware of too long URLs!)
00453         return md5(implode('/', array(
00454             $claimedIdentifier,
00455             strval(strlen($claimedIdentifier)),
00456             $GLOBALS['TYPO3_CONF_VARS']['SYS']['encryptionKey']
00457         )));
00458     }
00459 
00460     /**
00461      * Calculates the path to the TYPO3 directory from the current directory
00462      *
00463      * @return string
00464      */
00465     protected function getBackPath() {
00466         $extPath = t3lib_extMgm::siteRelPath('openid');
00467         $segmentCount = count(explode('/', $extPath));
00468         $path = str_pad('', $segmentCount*3, '../') . TYPO3_mainDir;
00469 
00470         return $path;
00471     }
00472 
00473     /**
00474      * Obtains a real identifier for the user
00475      *
00476      * @return string
00477      */
00478     protected function getFinalOpenIDIdentifier() {
00479         $result = $this->getSignedParameter('openid_identity');
00480         if (!$result) {
00481             $result = $this->getSignedParameter('openid_claimed_id');
00482         }
00483         if (!$result) {
00484             $result = $this->getSignedClaimedOpenIDIdentifier();
00485         }
00486         $result = $this->getAdjustedOpenIDIdentifier($result);
00487         return $result;
00488     }
00489 
00490     /**
00491      * Gets the signed OpenID that was sent back to this service.
00492      *
00493      * @return string The signed OpenID, if signature did not match this is empty
00494      */
00495     protected function getSignedClaimedOpenIDIdentifier() {
00496         $result = t3lib_div::_GP('tx_openid_claimed');
00497         $signature = $this->getSignature($result);
00498         if ($signature !== t3lib_div::_GP('tx_openid_signature')) {
00499             $result = '';
00500         }
00501         return $result;
00502     }
00503 
00504     /**
00505      * Adjusts the OpenID identifier to to claimed OpenID, if the only difference
00506      * is in normalizing the URLs. Example:
00507      *  + OpenID returned from provider: https://account.provider.net/
00508      *  + OpenID used in TYPO3: https://account.provider.net (not normalized)
00509      *
00510      * @param string $openIDIdentifier The OpenID returned by the OpenID provider
00511      * @return string Adjusted OpenID identifier
00512      */
00513     protected function getAdjustedOpenIDIdentifier($openIDIdentifier) {
00514         $result = '';
00515 
00516         $claimedOpenIDIdentifier = $this->getSignedClaimedOpenIDIdentifier();
00517         $pattern = '#^' . preg_quote($claimedOpenIDIdentifier, '#') . '/?$#';
00518 
00519         if (preg_match($pattern, $openIDIdentifier)) {
00520             $result = $claimedOpenIDIdentifier;
00521         }
00522 
00523         return $result;
00524     }
00525 
00526     /**
00527      * Obtains a value of the parameter if it is signed. If not signed, then
00528      * empty string is returned.
00529      *
00530      * @param string $parameterName Must start with 'openid_'
00531      * @return string
00532      */
00533     protected function getSignedParameter($parameterName) {
00534         $signedParametersList = t3lib_div::_GP('openid_signed');
00535         if (t3lib_div::inList($signedParametersList, substr($parameterName, 7))) {
00536             $result = t3lib_div::_GP($parameterName);
00537         } else {
00538             $result = '';
00539         }
00540         return $result;
00541     }
00542 
00543     /**
00544      * Writes log message. Destination log depends on the current system mode.
00545      * For FE the function writes to the admin panel log. For BE messages are
00546      * sent to the system log. If developer log is enabled, messages are also
00547      * sent there.
00548      *
00549      * This function accepts variable number of arguments and can format
00550      * parameters. The syntax is the same as for sprintf()
00551      *
00552      * @param   string      $message    Message to output
00553      * @return  void
00554      * @see sprintf()
00555      * @see t3lib::divLog()
00556      * @see t3lib_div::sysLog()
00557      * @see t3lib_timeTrack::setTSlogMessage()
00558      */
00559     protected function writeLog($message) {
00560         if (func_num_args() > 1) {
00561             $params = func_get_args();
00562             array_shift($params);
00563             $message = vsprintf($message, $params);
00564         }
00565         if (TYPO3_MODE == 'BE') {
00566             t3lib_div::sysLog($message, $this->extKey, 1);
00567         } else {
00568             $GLOBALS['TT']->setTSlogMessage($message);
00569         }
00570         if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['enable_DLOG']) {
00571             t3lib_div::devLog($message, $this->extKey, 1);
00572         }
00573     }
00574 }
00575 
00576 if (defined('TYPO3_MODE') && isset($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/openid/sv1/class.tx_openid_sv1.php'])) {
00577     include_once($GLOBALS['TYPO3_CONF_VARS'][TYPO3_MODE]['XCLASS']['ext/openid/sv1/class.tx_openid_sv1.php']);
00578 }
00579 
00580 ?>