Fixed bug #9683: Store OpenID information in database instead of using the filesystem
authorDmitry Dulepov <dmitry.dulepov@gmail.com>
Sun, 29 Nov 2009 15:17:50 +0000 (15:17 +0000)
committerDmitry Dulepov <dmitry.dulepov@gmail.com>
Sun, 29 Nov 2009 15:17:50 +0000 (15:17 +0000)
git-svn-id: https://svn.typo3.org/TYPO3v4/Core/trunk@6580 709f56b5-9817-0410-a4d7-c38de5d9e867

ChangeLog
typo3/sysext/openid/TODO
typo3/sysext/openid/ext_emconf.php
typo3/sysext/openid/ext_tables.sql
typo3/sysext/openid/sv1/class.tx_openid_store.php [new file with mode: 0644]
typo3/sysext/openid/sv1/class.tx_openid_sv1.php

index 450a87c..27ebea2 100755 (executable)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+2009-11-29  Dmitry Dulepov  <dmitry.dulepov@gmail.com>
+
+       * Fixed bug #9683: Store OpenID information in database instead of using the filesystem
+
 2009-11-29  Steffen Kamper  <info@sk-typo3.de>
 
        * Fixed bug #12828: change language .js for ExtJS according to BE language (thanks to Stefano Cecere)
index 4eef3a8..4af1832 100644 (file)
@@ -1,2 +1 @@
-
-* use DB (the sessions or the caching framework) instead of the filesystem to store OpenID data (class.tx_openid_sv1.php)
+None
\ No newline at end of file
index 3400f15..6d6977d 100644 (file)
@@ -24,12 +24,12 @@ $EM_CONF[$_EXTKEY] = array(
        'state' => 'beta',
        'internal' => '',
        'uploadfolder' => 0,
-       'createDirs' => 'typo3temp/tx_openid',
+       'createDirs' => '',
        'modify_tables' => 'fe_users,be_users',
        'clearCacheOnLoad' => 0,
        'lockType' => 'system',
        'author_company' => 'TYPO3 core team',
-       'version' => '0.1.0',
+       'version' => '1.0.0',
        'constraints' => array(
                'depends' => array(
                        'typo3' => '4.3.0-0.0.0',
@@ -47,4 +47,4 @@ $EM_CONF[$_EXTKEY] = array(
        '_md5_values_when_last_written' => 'a:56:{s:4:"TODO";s:4:"977e";s:23:"class.tx_openid_eid.php";s:4:"69c1";s:29:"class.tx_openid_mod_setup.php";s:4:"917d";s:26:"class.tx_openid_return.php";s:4:"9722";s:12:"ext_icon.gif";s:4:"f1e1";s:17:"ext_localconf.php";s:4:"20c4";s:14:"ext_tables.php";s:4:"20c5";s:14:"ext_tables.sql";s:4:"f309";s:17:"locallang_csh.xml";s:4:"7e8a";s:21:"locallang_csh_mod.xml";s:4:"31c3";s:16:"locallang_db.xml";s:4:"0952";s:14:"doc/manual.sxw";s:4:"05d1";s:22:"lib/php-openid/COPYING";s:4:"3b83";s:25:"lib/php-openid/README.txt";s:4:"eb02";s:37:"lib/php-openid/php-openid-typo3.patch";s:4:"b2fb";s:30:"lib/php-openid/Auth/OpenID.php";s:4:"3be9";s:33:"lib/php-openid/Auth/OpenID/AX.php";s:4:"18c3";s:42:"lib/php-openid/Auth/OpenID/Association.php";s:4:"5b10";s:38:"lib/php-openid/Auth/OpenID/BigMath.php";s:4:"7bc0";s:39:"lib/php-openid/Auth/OpenID/Consumer.php";s:4:"db7b";s:40:"lib/php-openid/Auth/OpenID/CryptUtil.php";s:4:"6276";s:49:"lib/php-openid/Auth/OpenID/DatabaseConnection.php";s:4:"660d";s:44:"lib/php-openid/Auth/OpenID/DiffieHellman.php";s:4:"1a0b";s:39:"lib/php-openid/Auth/OpenID/Discover.php";s:4:"e9ed";s:40:"lib/php-openid/Auth/OpenID/DumbStore.php";s:4:"c1e9";s:40:"lib/php-openid/Auth/OpenID/Extension.php";s:4:"5aae";s:40:"lib/php-openid/Auth/OpenID/FileStore.php";s:4:"69da";s:35:"lib/php-openid/Auth/OpenID/HMAC.php";s:4:"a0a3";s:40:"lib/php-openid/Auth/OpenID/Interface.php";s:4:"421b";s:37:"lib/php-openid/Auth/OpenID/KVForm.php";s:4:"3c7c";s:45:"lib/php-openid/Auth/OpenID/MemcachedStore.php";s:4:"cb6d";s:38:"lib/php-openid/Auth/OpenID/Message.php";s:4:"413e";s:41:"lib/php-openid/Auth/OpenID/MySQLStore.php";s:4:"4607";s:36:"lib/php-openid/Auth/OpenID/Nonce.php";s:4:"2738";s:35:"lib/php-openid/Auth/OpenID/PAPE.php";s:4:"decb";s:36:"lib/php-openid/Auth/OpenID/Parse.php";s:4:"28c9";s:46:"lib/php-openid/Auth/OpenID/PostgreSQLStore.php";s:4:"a2da";s:39:"lib/php-openid/Auth/OpenID/SQLStore.php";s:4:"29d2";s:42:"lib/php-openid/Auth/OpenID/SQLiteStore.php";s:4:"4855";s:35:"lib/php-openid/Auth/OpenID/SReg.php";s:4:"ae70";s:37:"lib/php-openid/Auth/OpenID/Server.php";s:4:"e37b";s:44:"lib/php-openid/Auth/OpenID/ServerRequest.php";s:4:"d29d";s:40:"lib/php-openid/Auth/OpenID/TrustRoot.php";s:4:"2866";s:38:"lib/php-openid/Auth/OpenID/URINorm.php";s:4:"e4fb";s:41:"lib/php-openid/Auth/Yadis/HTTPFetcher.php";s:4:"bdaa";s:37:"lib/php-openid/Auth/Yadis/Manager.php";s:4:"ee7d";s:34:"lib/php-openid/Auth/Yadis/Misc.php";s:4:"65f6";s:49:"lib/php-openid/Auth/Yadis/ParanoidHTTPFetcher.php";s:4:"170e";s:39:"lib/php-openid/Auth/Yadis/ParseHTML.php";s:4:"d8f8";s:46:"lib/php-openid/Auth/Yadis/PlainHTTPFetcher.php";s:4:"6d0f";s:33:"lib/php-openid/Auth/Yadis/XML.php";s:4:"09f1";s:34:"lib/php-openid/Auth/Yadis/XRDS.php";s:4:"12e5";s:33:"lib/php-openid/Auth/Yadis/XRI.php";s:4:"5eca";s:36:"lib/php-openid/Auth/Yadis/XRIRes.php";s:4:"a09e";s:35:"lib/php-openid/Auth/Yadis/Yadis.php";s:4:"e3c8";s:27:"sv1/class.tx_openid_sv1.php";s:4:"5885";}',
 );
 
-?>
\ No newline at end of file
+?>
index 0607ca5..2b2e928 100644 (file)
@@ -10,4 +10,38 @@ CREATE TABLE be_users (
 #
 CREATE TABLE fe_users (
        tx_openid_openid varchar(255) DEFAULT '' NOT NULL
-);
\ No newline at end of file
+);
+
+#
+# Table structure for table 'tx_openid_assoc_store'.
+#
+CREATE TABLE tx_openid_assoc_store (
+       uid int(11) unsigned NOT NULL auto_increment,
+       pid int(11) unsigned DEFAULT '0' NOT NULL,
+       crdate int(11) unsigned DEFAULT '0' NOT NULL,
+       tstamp int(11) unsigned DEFAULT '0' NOT NULL,
+       expires int(11) unsigned DEFAULT '0' NOT NULL,
+       server_url varchar(2047) DEFAULT '' NOT NULL,
+       assoc_handle varchar(255) DEFAULT '' NOT NULL,
+       content blob,
+
+       PRIMARY KEY (uid),
+       KEY assoc_handle (assoc_handle(8)),
+       KEY expires (expires)
+) ENGINE=InnoDB;
+
+#
+# Table structure for table 'tx_openid_nonce_store'.
+#
+CREATE TABLE tx_openid_nonce_store (
+       uid int(11) unsigned NOT NULL auto_increment,
+       pid int(11) unsigned DEFAULT '0' NOT NULL,
+       crdate int(11) unsigned DEFAULT '0' NOT NULL,
+       tstamp int(11) unsigned DEFAULT '0' NOT NULL,
+       server_url varchar(2047) DEFAULT '' NOT NULL,
+       salt char(40) DEFAULT '' NOT NULL,
+
+       PRIMARY KEY (uid),
+       UNIQUE KEY nonce (server_url(255),tstamp,salt),
+       KEY crdate (crdate)
+) ENGINE=InnoDB;
diff --git a/typo3/sysext/openid/sv1/class.tx_openid_store.php b/typo3/sysext/openid/sv1/class.tx_openid_store.php
new file mode 100644 (file)
index 0000000..d1a0e86
--- /dev/null
@@ -0,0 +1,251 @@
+<?php
+/***************************************************************
+*  Copyright notice
+*
+*  (c) 2009 Dmitry Dulepov (dmitry.dulepov@gmail.com)
+*  All rights reserved
+*
+*  This script is part of the Typo3 project. The Typo3 project is
+*  free software; you can redistribute it and/or modify
+*  it under the terms of the GNU General Public License as published by
+*  the Free Software Foundation; either version 2 of the License, or
+*  (at your option) any later version.
+*
+*  The GNU General Public License can be found at
+*  http://www.gnu.org/copyleft/gpl.html.
+*
+*  This script is distributed in the hope that it will be useful,
+*  but WITHOUT ANY WARRANTY; without even the implied warranty of
+*  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+*  GNU General Public License for more details.
+*
+*  This copyright notice MUST APPEAR in all copies of the script!
+***************************************************************/
+/**
+ * $Id$
+ */
+
+require_once(t3lib_extMgm::extPath('openid', 'lib/php-openid/Auth/OpenID/Interface.php'));
+
+/**
+  * This class is a TYPO3-specific OpenID store.
+  *
+  * @author Dmitry Dulepov <dmitry.dulepov@gmail.com>
+  * @package TYPO3
+  * @subpackage tx_openid
+  */
+class tx_openid_store extends Auth_OpenID_OpenIDStore {
+
+       const ASSOCIATION_TABLE_NAME = 'tx_openid_assoc_store';
+
+       const ASSOCIATION_EXPIRATION_SAFETY_INTERVAL = 120; /* 2 minutes */
+
+       const NONCE_TABLE_NAME = 'tx_openid_nonce_store';
+
+       const NONCE_STORAGE_TIME = 864000; /* 10 days */
+
+       /**
+        * Sores the association for future use
+        *
+        * @param string $serverUrl Server URL
+        * @param Auth_OpenID_Association $association OpenID association
+        * @return void
+        */
+       public function storeAssociation($serverUrl, $association) {
+               /* @var $association Auth_OpenID_Association */
+               $GLOBALS['TYPO3_DB']->sql_query('START TRANSACTION');
+
+               if ($this->doesAssociationExist($serverUrl, $association->handle)) {
+                       $this->updateExistingAssociation($serverUrl, $association);
+               }
+               else {
+                       $this->storeNewAssociation($serverUrl, $association);
+               }
+
+               $GLOBALS['TYPO3_DB']->sql_query('COMMIT');
+       }
+
+       /**
+        * Removes all expired associations.
+        *
+        * @return int A number of removed associations
+        */
+       public function cleanupAssociations() {
+               $where = sprintf('expires<=%d', time());
+               $GLOBALS['TYPO3_DB']->exec_DELETEquery(self::ASSOCIATION_TABLE_NAME, $where);
+               return $GLOBALS['TYPO3_DB']->sql_affected_rows();
+       }
+
+       /**
+        * Obtains the association to the server
+        *
+        * @param string $serverUrl Server URL
+        * @param string $handle Association handle (optional)
+        * @return Auth_OpenID_Association
+        */
+       public function getAssociation($serverUrl, $handle = null) {
+               $this->cleanupAssociations();
+
+               $where = sprintf('server_url=%s AND expires>%d',
+                       $GLOBALS['TYPO3_DB']->fullQuoteStr($serverUrl, self::ASSOCIATION_TABLE_NAME),
+                       time());
+               if ($handle != null) {
+                       $where .= sprintf(' AND assoc_handle=%s',
+                               $GLOBALS['TYPO3_DB']->fullQuoteStr($handle, self::ASSOCIATION_TABLE_NAME));
+                       $sort = '';
+               }
+               else {
+                       $sort = 'tstamp DESC';
+               }
+               list($row) = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows('uid,content',
+                       self::ASSOCIATION_TABLE_NAME, $where, '', $sort, '1');
+
+               $result = null;
+               if (is_array($row)) {
+                       $result = @unserialize($row['content']);
+                       $this->updateAssociationTimeStamp($row['tstamp']);
+               }
+               return $result;
+       }
+
+       /**
+        * Removes the association
+        *
+        * @param string $serverUrl Server URL
+        * @param string $handle Association handle (optional)
+        * @return boolean true if the association existed
+        */
+       function removeAssociation($serverUrl, $handle) {
+               $where = sprintf('server_url=%s AND assoc_handle=%s',
+                       $GLOBALS['TYPO3_DB']->fullQuoteStr($serverUrl, self::ASSOCIATION_TABLE_NAME),
+                       $GLOBALS['TYPO3_DB']->fullQuoteStr($handle, self::ASSOCIATION_TABLE_NAME));
+               $GLOBALS['TYPO3_DB']->exec_DELETEquery(self::ASSOCIATION_TABLE_NAME, $where);
+               $deletedCount = $GLOBALS['TYPO3_DB']->sql_affected_rows();
+               return ($deletedCount > 0);
+       }
+
+       /**
+        * Removes old nonces
+        *
+        * @return void
+        */
+       public function cleanupNonces() {
+               $where = sprintf('crdate<%d', time() - self::NONCE_STORAGE_TIME);
+               $GLOBALS['TYPO3_DB']->exec_DELETEquery(self::NONCE_TABLE_NAME, $where);
+       }
+
+       /**
+        * Checks if this nonce was already used
+        * @param $serverUrl Server URL
+        * @param $timestamp Time stamp
+        * @param $salt Nonce value
+        * @return boolean true if nonce was not used before anc can be used now
+        */
+       public function useNonce($serverUrl, $timestamp, $salt) {
+               $result = false;
+
+               if (abs($timestamp - time()) < $GLOBALS['Auth_OpenID_SKEW']) {
+                       $values = array(
+                               'crdate' => time(),
+                               'salt' => $salt,
+                               'server_url' => $serverUrl,
+                               'tstamp' => $timestamp
+                       );
+                       $GLOBALS['TYPO3_DB']->exec_INSERTquery(self::NONCE_TABLE_NAME,
+                               $values);
+                       $affectedRows = $GLOBALS['TYPO3_DB']->sql_affected_rows();
+                       $result = ($affectedRows > 0);
+               }
+
+               return $result;
+       }
+
+       /**
+        * Resets the store by removing all data in it
+        *
+        * @return void
+        */
+       public function reset() {
+               $GLOBALS['TYPO3_DB']->exec_DELETEquery(self::ASSOCIATION_TABLE_NAME, '1=1');
+               $GLOBALS['TYPO3_DB']->exec_DELETEquery(self::NONCE_TABLE_NAME, '1=1');
+       }
+
+       /**
+        * Checks if such association exists.
+        *
+        * @param string $serverUrl Server URL
+        * @param Auth_OpenID_Association $association OpenID association
+        * @return boolean
+        */
+       protected function doesAssociationExist($serverUrl, $association) {
+               $where = sprintf('server_url=%s AND assoc_handle=%s AND expires>%d',
+                       $GLOBALS['TYPO3_DB']->fullQuoteStr($serverUrl, self::ASSOCIATION_TABLE_NAME),
+                       $GLOBALS['TYPO3_DB']->fullQuoteStr($association->handle, self::ASSOCIATION_TABLE_NAME),
+                       time());
+               list($row) = $GLOBALS['TYPO3_DB']->exec_SELECTgetRows(
+                       'COUNT(*) as assocCount', self::ASSOCIATION_TABLE_NAME, $where);
+               return ($row['assocCount'] > 0);
+       }
+
+       /**
+        * Updates existing association.
+        *
+        * @param string $serverUrl Server URL
+        * @param Auth_OpenID_Association $association OpenID association
+        * @return void
+        */
+       protected function updateExistingAssociation($serverUrl, Auth_OpenID_Association $association) {
+               $where = sprintf('server_url=%s AND assoc_handle=%s AND expires>%d',
+                       $GLOBALS['TYPO3_DB']->fullQuoteStr($serverUrl, self::ASSOCIATION_TABLE_NAME),
+                       $GLOBALS['TYPO3_DB']->fullQuoteStr($association->handle, self::ASSOCIATION_TABLE_NAME),
+                       time());
+               $serializedAssociation = serialize($association);
+               $values = array(
+                       'content' => $serializedAssociation,
+                       'tstamp' => time(),
+               );
+               $GLOBALS['TYPO3_DB']->exec_UPDATEquery(self::ASSOCIATION_TABLE_NAME, $where, $values);
+       }
+
+       /**
+        * Stores new association to the database.
+        *
+        * @param $serverUrl Server URL
+        * @param $association OpenID association
+        * @return void
+        */
+       protected function storeNewAssociation($serverUrl, $association) {
+               $serializedAssociation = serialize($association);
+               $values = array(
+                       'assoc_handle' => $association->handle,
+                       'content' => $serializedAssociation,
+                       'crdate' => $association->issued,
+                       'tstamp' => time(),
+                       'expires' => $association->issued + $association->lifetime - self::ASSOCIATION_EXPIRATION_SAFETY_INTERVAL,
+                       'server_url' => $serverUrl
+               );
+               // In the next query we can get race conditions. sha1_hash prevents many
+               // asociations from being stored for one server
+               $GLOBALS['TYPO3_DB']->exec_INSERTquery(self::ASSOCIATION_TABLE_NAME, $values);
+       }
+
+       /**
+        * Updates association time stamp.
+        *
+        * @param $recordId Association record id in the database
+        * @return void
+        */
+       protected function updateAssociationTimeStamp($recordId) {
+               $where = sprintf('uid=%d', $recordId);
+               $values = array(
+                       'tstamp' => time()
+               );
+               $GLOBALS['TYPO3_DB']->exec_UPDATEquery(self::ASSOCIATION_TABLE_NAME, $where, $values);
+       }
+}
+
+if (defined('TYPO3_MODE') && $TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/openid/class.tx_openid_store.php'])   {
+       include_once($TYPO3_CONF_VARS[TYPO3_MODE]['XCLASS']['ext/openid/class.tx_openid_store.php']);
+}
+
+?>
\ No newline at end of file
index d8bd986..4ddc353 100644 (file)
@@ -44,6 +44,7 @@
  */
 
 require_once(PATH_t3lib . 'class.t3lib_svbase.php');
+require_once(t3lib_extMgm::extPath('openid', 'sv1/class.tx_openid_store.php'));
 
 /**
  * Service "OpenID Authentication" for the "openid" extension.
@@ -276,7 +277,6 @@ class tx_openid_sv1 extends t3lib_svbase {
 
                        // Include files
                        require_once($phpOpenIDLibPath . '/Auth/OpenID/Consumer.php');
-                       require_once($phpOpenIDLibPath . '/Auth/OpenID/FileStore.php');
 
                        // Restore path
                        @set_include_path($oldIncludePath);
@@ -319,16 +319,10 @@ class tx_openid_sv1 extends t3lib_svbase {
         * @return      Auth_OpenID_Consumer            Consumer instance
         */
        protected function getOpenIDConsumer() {
-               // TODO Change this to a TYPO3-specific database-based store in future.
-               // File-based store is ineffective and insecure. After changing
-               // get rid of the FileStore include in includePHPOpenIDLibrary()
-               $openIDStorePath = PATH_site . 'typo3temp' . DIRECTORY_SEPARATOR . 'tx_openid';
-
-               // For now we just prevent any web access to these files
-               if (!file_exists($openIDStorePath . '/.htaccess')) {
-                       file_put_contents($openIDStorePath . '/.htaccess', 'deny from all');
-               }
-               $openIDStore = new Auth_OpenID_FileStore($openIDStorePath);
+               $openIDStore = t3lib_div::makeInstance('tx_openid_store');
+               /* @var $openIDStore tx_openid_store */
+               $openIDStore->cleanup();
+
                return new Auth_OpenID_Consumer($openIDStore);
        }