TYPO3 CMS  TYPO3_6-2
OpenidService.php
Go to the documentation of this file.
1 <?php
2 namespace TYPO3\CMS\Openid;
3 
19 
20 require_once \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('openid') . 'lib/php-openid/Auth/OpenID/Interface.php';
21 
28 
32  public $extKey = 'openid';
33 
37  protected $loginData = array();
38 
43  protected $authenticationInformation = array();
44 
51  protected $openIDResponse = NULL;
52 
58  protected $parentObject;
59 
64 
68  static protected $openIDLibrariesIncluded = FALSE;
69 
73  public function __construct() {
74  // Auth_Yadis_Yadis::getHTTPFetcher() will use a cURL fetcher if the functionality
75  // is available in PHP, however the TYPO3 setting is not considered here:
76  if (!defined('Auth_Yadis_CURL_OVERRIDE')) {
77  if (!$GLOBALS['TYPO3_CONF_VARS']['SYS']['curlUse']) {
78  define('Auth_Yadis_CURL_OVERRIDE', TRUE);
79  }
80  }
81 
82  $this->injectDatabaseConnection();
83  }
84 
88  protected function injectDatabaseConnection(\TYPO3\CMS\Core\Database\DatabaseConnection $databaseConnection = NULL) {
89  $this->databaseConnection = $databaseConnection ?: $GLOBALS['TYPO3_DB'];
90  }
91 
100  public function init() {
101  $available = FALSE;
102  if (extension_loaded('gmp')) {
103  $available = is_callable('gmp_init');
104  } elseif (extension_loaded('bcmath')) {
105  $available = is_callable('bcadd');
106  } else {
107  $this->writeLog('Neither bcmath, nor gmp PHP extension found. OpenID authentication will not be available.');
108  }
109  // We also need set_include_path() PHP function
110  if (!is_callable('set_include_path')) {
111  $available = FALSE;
112  $this->writeLog('set_include_path() PHP function is not available. OpenID authentication is disabled.');
113  }
114  return $available ? parent::init() : FALSE;
115  }
116 
127  // Store login and authetication data
128  $this->loginData = $loginData;
129  $this->authenticationInformation = $authenticationInformation;
130  // If we are here after authentication by the OpenID server, get its response.
131  if (\TYPO3\CMS\Core\Utility\GeneralUtility::_GP('tx_openid_mode') === 'finish' && $this->openIDResponse === NULL) {
132  $this->includePHPOpenIDLibrary();
133  $openIDConsumer = $this->getOpenIDConsumer();
134  $this->openIDResponse = $openIDConsumer->complete($this->getReturnURL(GeneralUtility::_GP('tx_openid_claimed')));
135  }
136  $this->parentObject = $parentObject;
137  }
138 
146  public function processLoginData(array &$loginData, $passwordTransmissionStrategy) {
147  $isProcessed = FALSE;
148  // Pre-process the login only if no password has been submitted
149  if (empty($loginData['uident_text'])) {
150  try {
151  $openIdUrl = GeneralUtility::_POST('openid_url');
152  if (!empty($openIdUrl)) {
153  $loginData['uident_openid'] = $this->normalizeOpenID($openIdUrl);
154  $isProcessed = TRUE;
155  } elseif (!empty($loginData['uname'])) {
156  // It might be the case that during frontend login the OpenID URL is submitted in the username field
157  // Since we are a low priority service, and no password has been submitted it is OK to just assume
158  // we might have gotten an OpenID URL
159  $loginData['uident_openid'] = $this->normalizeOpenID($loginData['uname']);
160  $isProcessed = TRUE;
161  }
162  } catch (Exception $e) {
163  $this->writeLog($e->getMessage());
164  }
165  }
166  return $isProcessed;
167  }
168 
177  public function getUser() {
178  if ($this->loginData['status'] !== 'login') {
179  return NULL;
180  }
181  $userRecord = NULL;
182  if ($this->openIDResponse instanceof \Auth_OpenID_ConsumerResponse) {
183  $GLOBALS['BACK_PATH'] = $this->getBackPath();
184  // We are running inside the OpenID return script
185  // Note: we cannot use $this->openIDResponse->getDisplayIdentifier()
186  // because it may return a different identifier. For example,
187  // LiveJournal server converts all underscore characters in the
188  // original identfier to dashes.
189  if ($this->openIDResponse->status === Auth_OpenID_SUCCESS) {
190  $openIDIdentifier = $this->getFinalOpenIDIdentifier();
191  if ($openIDIdentifier) {
192  $userRecord = $this->getUserRecord($openIDIdentifier);
193  if (!empty($userRecord) && is_array($userRecord)) {
194  // The above function will return user record from the OpenID. It means that
195  // user actually tried to authenticate using his OpenID. In this case
196  // we must change the password in the record to a long random string so
197  // that this user cannot be authenticated with other service.
198  $userRecord[$this->authenticationInformation['db_user']['userident_column']] = GeneralUtility::getRandomHexString(42);
199  $this->writeLog('User \'%s\' logged in with OpenID \'%s\'', $userRecord[$this->parentObject->formfield_uname], $openIDIdentifier);
200  } else {
201  $this->writeLog('Failed to login user using OpenID \'%s\'', $openIDIdentifier);
202  }
203  }
204  }
205  } elseif (!empty($this->loginData['uident_openid'])) {
206  $this->sendOpenIDRequest($this->loginData['uident_openid']);
207  }
208  return $userRecord;
209  }
210 
217  public function authUser(array $userRecord) {
218  $result = 100;
219  // 100 means "we do not know, continue"
220  if ($userRecord['tx_openid_openid'] !== '') {
221  // Check if user is identified by the OpenID
222  if ($this->openIDResponse instanceof \Auth_OpenID_ConsumerResponse) {
223  // If we have a response, it means OpenID server tried to authenticate
224  // the user. Now we just look what is the status and provide
225  // corresponding response to the caller
226  if ($this->openIDResponse->status === Auth_OpenID_SUCCESS) {
227  // Success (code 200)
228  $result = 200;
229  } else {
230  $this->writeLog('OpenID authentication failed with code \'%s\'.', $this->openIDResponse->status);
231  }
232  }
233  }
234  return $result;
235  }
236 
242  protected function includePHPOpenIDLibrary() {
243  if (!self::$openIDLibrariesIncluded) {
244  // Prevent further calls
245  self::$openIDLibrariesIncluded = TRUE;
246  // PHP OpenID libraries requires adjustments of path settings
247  $oldIncludePath = get_include_path();
248  $phpOpenIDLibPath = \TYPO3\CMS\Core\Utility\ExtensionManagementUtility::extPath('openid') . 'lib/php-openid';
249  @set_include_path(($phpOpenIDLibPath . PATH_SEPARATOR . $phpOpenIDLibPath . PATH_SEPARATOR . 'Auth' . PATH_SEPARATOR . $oldIncludePath));
250  // Make sure that random generator is properly set up. Constant could be
251  // defined by the previous inclusion of the file
252  if (!defined('Auth_OpenID_RAND_SOURCE')) {
253  if (TYPO3_OS === 'WIN') {
254  // No random generator on Windows!
255  define('Auth_OpenID_RAND_SOURCE', NULL);
256  } elseif (!is_readable('/dev/urandom')) {
257  if (is_readable('/dev/random')) {
258  define('Auth_OpenID_RAND_SOURCE', '/dev/random');
259  } else {
260  define('Auth_OpenID_RAND_SOURCE', NULL);
261  }
262  }
263  }
264  // Include files
265  require_once $phpOpenIDLibPath . '/Auth/OpenID/Consumer.php';
266  // Restore path
267  @set_include_path($oldIncludePath);
268  if (!is_array($_SESSION)) {
269  // Yadis requires session but session is not initialized when
270  // processing Backend authentication
271  @session_start();
272  $this->writeLog('Session is initialized');
273  }
274  }
275  }
276 
283  protected function getUserRecord($openIDIdentifier) {
284  $record = NULL;
285  try {
286  $openIDIdentifier = $this->normalizeOpenID($openIDIdentifier);
287  // $openIDIdentifier always has a trailing slash
288  // but tx_openid_openid field possibly not so check for both alternatives in database
289  $record = $this->databaseConnection->exec_SELECTgetSingleRow(
290  '*',
291  $this->authenticationInformation['db_user']['table'],
292  'tx_openid_openid IN ('
293  . $this->databaseConnection->fullQuoteStr($openIDIdentifier, $this->authenticationInformation['db_user']['table'])
294  . ',' . $this->databaseConnection->fullQuoteStr(rtrim($openIDIdentifier, '/'),
295  $this->authenticationInformation['db_user']['table']) . ')'
296  . $this->authenticationInformation['db_user']['check_pid_clause']
297  . $this->authenticationInformation['db_user']['enable_clause']
298  );
299  if ($record) {
300  // Make sure to work only with normalized OpenID during the whole process
301  $record['tx_openid_openid'] = $this->normalizeOpenID($record['tx_openid_openid']);
302  }
303  } catch (Exception $e) {
304  // This should never happen and generally means hack attempt.
305  // We just log it and do not return any records.
306  $this->writeLog($e->getMessage());
307  }
308  return $record;
309  }
310 
317  protected function getOpenIDConsumer() {
318  /* @var $openIDStore OpenidStore */
319  $openIDStore = \TYPO3\CMS\Core\Utility\GeneralUtility::makeInstance('TYPO3\\CMS\\Openid\\OpenidStore');
320  $openIDStore->cleanup();
321  return new \Auth_OpenID_Consumer($openIDStore);
322  }
323 
339  protected function sendOpenIDRequest($openIDIdentifier) {
340  $this->includePHPOpenIDLibrary();
341  // Initialize OpenID client system, get the consumer
342  $openIDConsumer = $this->getOpenIDConsumer();
343  // Begin the OpenID authentication process
344  $authenticationRequest = $openIDConsumer->begin($openIDIdentifier);
345  if (!$authenticationRequest) {
346  // Not a valid OpenID. Since it can be some other ID, we just return
347  // and let other service handle it.
348  $this->writeLog('Could not create authentication request for OpenID identifier \'%s\'', $openIDIdentifier);
349  return;
350  }
351  // Redirect the user to the OpenID server for authentication.
352  // Store the token for this authentication so we can verify the
353  // response.
354  // For OpenID version 1, we *should* send a redirect. For OpenID version 2,
355  // we should use a Javascript form to send a POST request to the server.
356  $returnURL = $this->getReturnURL($openIDIdentifier);
357  $trustedRoot = GeneralUtility::getIndpEnv('TYPO3_SITE_URL');
358  if ($authenticationRequest->shouldSendRedirect()) {
359  $redirectURL = $authenticationRequest->redirectURL($trustedRoot, $returnURL);
360  // If the redirect URL can't be built, return. We can only return.
361  if (\Auth_OpenID::isFailure($redirectURL)) {
362  $this->writeLog('Authentication request could not create redirect URL for OpenID identifier \'%s\'', $openIDIdentifier);
363  return;
364  }
365  // Send redirect. We use 303 code because it allows to redirect POST
366  // requests without resending the form. This is exactly what we need here.
367  // See http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.4
368  @ob_end_clean();
370  } else {
371  $formHtml = $authenticationRequest->htmlMarkup($trustedRoot, $returnURL, FALSE, array('id' => 'openid_message'));
372  // Display an error if the form markup couldn't be generated;
373  // otherwise, render the HTML.
374  if (\Auth_OpenID::isFailure($formHtml)) {
375  // Form markup cannot be generated
376  $this->writeLog('Could not create form markup for OpenID identifier \'%s\'', $openIDIdentifier);
377  return;
378  } else {
379  @ob_end_clean();
380  echo $formHtml;
381  }
382  }
383  // If we reached this point, we must not return!
384  die;
385  }
386 
395  protected function getReturnURL($claimedIdentifier) {
396  if ($this->authenticationInformation['loginType'] === 'FE') {
397  // We will use eID to send user back, create session data and
398  // return to the calling page.
399  // Notice: 'pid' and 'logintype' parameter names cannot be changed!
400  // They are essential for FE user authentication.
401  $returnURL = 'index.php?eID=tx_openid&' . 'pid=' . $this->authenticationInformation['db_user']['checkPidList'] . '&' . 'logintype=login&';
402  } else {
403  // In the Backend we will use dedicated script to create session.
404  // It is much easier for the Backend to manage users.
405  // Notice: 'login_status' parameter name cannot be changed!
406  // It is essential for BE user authentication.
407  $absoluteSiteURL = substr(GeneralUtility::getIndpEnv('TYPO3_SITE_URL'), strlen(GeneralUtility::getIndpEnv('TYPO3_REQUEST_HOST')));
408  $returnURL = $absoluteSiteURL . TYPO3_mainDir . 'sysext/' . $this->extKey . '/class.tx_openid_return.php?login_status=login&';
409  }
410  if (GeneralUtility::_GP('tx_openid_mode') === 'finish') {
411  $requestURL = GeneralUtility::_GP('tx_openid_location');
412  } else {
413  $requestURL = GeneralUtility::getIndpEnv('TYPO3_REQUEST_URL');
414  }
415  $returnURL .= 'tx_openid_location=' . rawurlencode($requestURL) . '&tx_openid_location_signature=' . $this->getSignature($requestURL) . '&tx_openid_mode=finish&tx_openid_claimed=' . rawurlencode($claimedIdentifier) . '&tx_openid_signature=' . $this->getSignature($claimedIdentifier);
416  return GeneralUtility::locationHeaderUrl($returnURL);
417  }
418 
425  protected function getSignature($parameter) {
426  return GeneralUtility::hmac($parameter, $this->extKey);
427  }
428 
437  protected function normalizeOpenID($openIDIdentifier) {
438  if (empty($openIDIdentifier)) {
439  throw new Exception('Empty OpenID Identifier given.', 1381922460);
440  }
441  // Strip everything with and behind the fragment delimiter character "#"
442  if (strpos($openIDIdentifier, '#') !== FALSE) {
443  $openIDIdentifier = preg_replace('/#.*$/', '', $openIDIdentifier);
444  }
445  // A URI with a missing scheme is normalized to a http URI
446  if (!preg_match('#^https?://#', $openIDIdentifier)) {
447  $escapedIdentifier = $this->databaseConnection->quoteStr($openIDIdentifier, $this->authenticationInformation['db_user']['table']);
448  $condition = 'tx_openid_openid IN ('
449  . '\'http://' . $escapedIdentifier . '\','
450  . '\'http://' . $escapedIdentifier . '/\','
451  . '\'https://' . $escapedIdentifier . '\','
452  . '\'https://' . $escapedIdentifier . '/\''
453  . ')';
454  $row = $this->databaseConnection->exec_SELECTgetSingleRow(
455  'tx_openid_openid',
456  $this->authenticationInformation['db_user']['table'],
457  $condition
458  );
459  if (is_array($row)) {
460  $openIDIdentifier = $row['tx_openid_openid'];
461  } else {
462  // This only happens when the OpenID provider will select the final OpenID identity
463  // In this case we require a valid URL as we cannot guess the scheme
464  // So we throw an Exception and do not start the OpenID handshake at all
465  throw new Exception('Trying to authenticate with OpenID but identifier is neither found in a user record nor it is a valid URL.', 1381922465);
466  }
467  }
468  // An empty path component is normalized to a slash
469  // (e.g. "http://domain.org" -> "http://domain.org/")
470  if (preg_match('#^https?://[^/]+$#', $openIDIdentifier)) {
471  $openIDIdentifier .= '/';
472  }
473  return $openIDIdentifier;
474  }
475 
481  protected function getBackPath() {
483  $segmentCount = count(explode('/', $extPath));
484  $path = str_pad('', $segmentCount * 3, '../') . TYPO3_mainDir;
485  return $path;
486  }
487 
493  protected function getFinalOpenIDIdentifier() {
494  $result = $this->getSignedParameter('openid_claimed_id');
495  if (!$result) {
496  $result = $this->getSignedParameter('openid_identity');
497  }
498  if (!$result) {
500  }
501  return $result;
502  }
503 
509  protected function getSignedClaimedOpenIDIdentifier() {
510  $result = GeneralUtility::_GP('tx_openid_claimed');
511  $signature = $this->getSignature($result);
512  if ($signature !== GeneralUtility::_GP('tx_openid_signature')) {
513  $result = '';
514  }
515  return $result;
516  }
517 
525  protected function getSignedParameter($parameterName) {
526  $signedParametersList = GeneralUtility::_GP('openid_signed');
527  if (GeneralUtility::inList($signedParametersList, substr($parameterName, 7))) {
528  $result = GeneralUtility::_GP($parameterName);
529  } else {
530  $result = '';
531  }
532  return $result;
533  }
534 
549  protected function writeLog($message) {
550  if (func_num_args() > 1) {
551  $params = func_get_args();
552  array_shift($params);
553  $message = vsprintf($message, $params);
554  }
555  if (TYPO3_MODE === 'BE') {
556  GeneralUtility::sysLog($message, $this->extKey, GeneralUtility::SYSLOG_SEVERITY_NOTICE);
557  } else {
558  $GLOBALS['TT']->setTSlogMessage($message);
559  }
560  if ($GLOBALS['TYPO3_CONF_VARS']['SYS']['enable_DLOG']) {
562  }
563  }
564 
565 }
processLoginData(array &$loginData, $passwordTransmissionStrategy)
const Auth_OpenID_SUCCESS
Definition: Consumer.php:180
getUserRecord($openIDIdentifier)
static devLog($msg, $extKey, $severity=0, $dataVar=FALSE)
sendOpenIDRequest($openIDIdentifier)
const TYPO3_MODE
Definition: init.php:40
static hmac($input, $additionalSecret='')
getReturnURL($claimedIdentifier)
normalizeOpenID($openIDIdentifier)
die
Definition: index.php:6
if($list_of_literals) if(!empty($literals)) if(!empty($literals)) $result
Analyse literals to prepend the N char to them if their contents aren&#39;t numeric.
injectDatabaseConnection(\TYPO3\CMS\Core\Database\DatabaseConnection $databaseConnection=NULL)
static redirect($url, $httpStatus=self::HTTP_STATUS_303)
Definition: HttpUtility.php:76
if(!defined('TYPO3_MODE')) $GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_userauth.php']['logoff_pre_processing'][]
initAuth($subType, array $loginData, array $authenticationInformation, AbstractUserAuthentication &$parentObject)
static isFailure($thing)
Definition: OpenID.php:118