414 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
			
		
		
	
	
			414 lines
		
	
	
		
			14 KiB
		
	
	
	
		
			PHP
		
	
	
	
	
	
| <?php
 | |
| 
 | |
| /**
 | |
|  * SQL-backed OpenID stores for use with PEAR::MDB2.
 | |
|  *
 | |
|  * PHP versions 4 and 5
 | |
|  *
 | |
|  * LICENSE: See the COPYING file included in this distribution.
 | |
|  *
 | |
|  * @package OpenID
 | |
|  * @author JanRain, Inc. <openid@janrain.com>
 | |
|  * @copyright 2005 Janrain, Inc.
 | |
|  * @license http://www.gnu.org/copyleft/lesser.html LGPL
 | |
|  */
 | |
| 
 | |
| require_once 'MDB2.php';
 | |
| 
 | |
| /**
 | |
|  * @access private
 | |
|  */
 | |
| require_once 'Auth/OpenID/Interface.php';
 | |
| 
 | |
| /**
 | |
|  * @access private
 | |
|  */
 | |
| require_once 'Auth/OpenID.php';
 | |
| 
 | |
| /**
 | |
|  * @access private
 | |
|  */
 | |
| require_once 'Auth/OpenID/Nonce.php';
 | |
| 
 | |
| /**
 | |
|  * This store uses a PEAR::MDB2 connection to store persistence
 | |
|  * information.
 | |
|  *
 | |
|  * The table names used are determined by the class variables
 | |
|  * associations_table_name and nonces_table_name.  To change the name
 | |
|  * of the tables used, pass new table names into the constructor.
 | |
|  *
 | |
|  * To create the tables with the proper schema, see the createTables
 | |
|  * method.
 | |
|  *
 | |
|  * @package OpenID
 | |
|  */
 | |
| class Auth_OpenID_MDB2Store extends Auth_OpenID_OpenIDStore {
 | |
|     /**
 | |
|      * This creates a new MDB2Store instance.  It requires an
 | |
|      * established database connection be given to it, and it allows
 | |
|      * overriding the default table names.
 | |
|      *
 | |
|      * @param connection $connection This must be an established
 | |
|      * connection to a database of the correct type for the SQLStore
 | |
|      * subclass you're using.  This must be a PEAR::MDB2 connection
 | |
|      * handle.
 | |
|      *
 | |
|      * @param associations_table: This is an optional parameter to
 | |
|      * specify the name of the table used for storing associations.
 | |
|      * The default value is 'oid_associations'.
 | |
|      *
 | |
|      * @param nonces_table: This is an optional parameter to specify
 | |
|      * the name of the table used for storing nonces.  The default
 | |
|      * value is 'oid_nonces'.
 | |
|      */
 | |
|     function Auth_OpenID_MDB2Store($connection,
 | |
|                                   $associations_table = null,
 | |
|                                   $nonces_table = null)
 | |
|     {
 | |
|         $this->associations_table_name = "oid_associations";
 | |
|         $this->nonces_table_name = "oid_nonces";
 | |
| 
 | |
|         // Check the connection object type to be sure it's a PEAR
 | |
|         // database connection.
 | |
|         if (!is_object($connection) ||
 | |
|             !is_subclass_of($connection, 'mdb2_driver_common')) {
 | |
|             trigger_error("Auth_OpenID_MDB2Store expected PEAR connection " .
 | |
|                           "object (got ".get_class($connection).")",
 | |
|                           E_USER_ERROR);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         $this->connection = $connection;
 | |
| 
 | |
|         // Be sure to set the fetch mode so the results are keyed on
 | |
|         // column name instead of column index.
 | |
|         $this->connection->setFetchMode(MDB2_FETCHMODE_ASSOC);
 | |
|         
 | |
|         if (@PEAR::isError($this->connection->loadModule('Extended'))) {
 | |
|             trigger_error("Unable to load MDB2_Extended module", E_USER_ERROR);
 | |
|             return;
 | |
|         }
 | |
| 
 | |
|         if ($associations_table) {
 | |
|             $this->associations_table_name = $associations_table;
 | |
|         }
 | |
| 
 | |
|         if ($nonces_table) {
 | |
|             $this->nonces_table_name = $nonces_table;
 | |
|         }
 | |
| 
 | |
|         $this->max_nonce_age = 6 * 60 * 60;
 | |
|     }
 | |
| 
 | |
|     function tableExists($table_name)
 | |
|     {
 | |
|         return !@PEAR::isError($this->connection->query(
 | |
|                                   sprintf("SELECT * FROM %s LIMIT 0",
 | |
|                                           $table_name)));
 | |
|     }
 | |
| 
 | |
|     function createTables()
 | |
|     {
 | |
|         $n = $this->create_nonce_table();
 | |
|         $a = $this->create_assoc_table();
 | |
| 
 | |
|         if (!$n || !$a) {
 | |
|             return false;
 | |
|         }
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     function create_nonce_table()
 | |
|     {
 | |
|         if (!$this->tableExists($this->nonces_table_name)) {
 | |
|             switch ($this->connection->phptype) {
 | |
|                 case "mysql":
 | |
|                 case "mysqli":
 | |
|                     // Custom SQL for MySQL to use InnoDB and variable-
 | |
|                     // length keys
 | |
|                     $r = $this->connection->exec(
 | |
|                         sprintf("CREATE TABLE %s (\n".
 | |
|                                 "  server_url VARCHAR(2047) NOT NULL DEFAULT '',\n".
 | |
|                                 "  timestamp INTEGER NOT NULL,\n".
 | |
|                                 "  salt CHAR(40) NOT NULL,\n".
 | |
|                                 "  UNIQUE (server_url(255), timestamp, salt)\n".
 | |
|                                 ") TYPE=InnoDB",
 | |
|                                 $this->nonces_table_name));
 | |
|                     if (@PEAR::isError($r)) {
 | |
|                         return false;
 | |
|                     }
 | |
|                     break;
 | |
|                 default:
 | |
|                     if (@PEAR::isError(
 | |
|                         $this->connection->loadModule('Manager'))) {
 | |
|                         return false;
 | |
|                     }
 | |
|                     $fields = array(
 | |
|                         "server_url" => array(
 | |
|                             "type" => "text",
 | |
|                             "length" => 2047,
 | |
|                             "notnull" => true
 | |
|                         ),
 | |
|                         "timestamp" => array(
 | |
|                             "type" => "integer",
 | |
|                             "notnull" => true
 | |
|                         ),
 | |
|                         "salt" => array(
 | |
|                             "type" => "text",
 | |
|                             "length" => 40,
 | |
|                             "fixed" => true,
 | |
|                             "notnull" => true
 | |
|                         )
 | |
|                     );
 | |
|                     $constraint = array(
 | |
|                         "unique" => 1,
 | |
|                         "fields" => array(
 | |
|                             "server_url" => true,
 | |
|                             "timestamp" => true,
 | |
|                             "salt" => true
 | |
|                         )
 | |
|                     );
 | |
|                     
 | |
|                     $r = $this->connection->createTable($this->nonces_table_name,
 | |
|                                                         $fields);
 | |
|                     if (@PEAR::isError($r)) {
 | |
|                         return false;
 | |
|                     }
 | |
|                     
 | |
|                     $r = $this->connection->createConstraint(
 | |
|                         $this->nonces_table_name,
 | |
|                         $this->nonces_table_name . "_constraint",
 | |
|                         $constraint);
 | |
|                     if (@PEAR::isError($r)) {
 | |
|                         return false;
 | |
|                     }
 | |
|                     break;
 | |
|             }
 | |
|         }
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     function create_assoc_table()
 | |
|     {
 | |
|         if (!$this->tableExists($this->associations_table_name)) {
 | |
|             switch ($this->connection->phptype) {
 | |
|                 case "mysql":
 | |
|                 case "mysqli":
 | |
|                     // Custom SQL for MySQL to use InnoDB and variable-
 | |
|                     // length keys
 | |
|                     $r = $this->connection->exec(
 | |
|                         sprintf("CREATE TABLE %s(\n".
 | |
|                                 "  server_url VARCHAR(2047) NOT NULL DEFAULT '',\n".
 | |
|                                 "  handle VARCHAR(255) NOT NULL,\n".
 | |
|                                 "  secret BLOB NOT NULL,\n".
 | |
|                                 "  issued INTEGER NOT NULL,\n".
 | |
|                                 "  lifetime INTEGER NOT NULL,\n".
 | |
|                                 "  assoc_type VARCHAR(64) NOT NULL,\n".
 | |
|                                 "  PRIMARY KEY (server_url(255), handle)\n".
 | |
|                                 ") TYPE=InnoDB",
 | |
|                             $this->associations_table_name));
 | |
|                     if (@PEAR::isError($r)) {
 | |
|                         return false;
 | |
|                     }
 | |
|                     break;
 | |
|                 default:
 | |
|                     if (@PEAR::isError(
 | |
|                         $this->connection->loadModule('Manager'))) {
 | |
|                         return false;
 | |
|                     }
 | |
|                     $fields = array(
 | |
|                         "server_url" => array(
 | |
|                             "type" => "text",
 | |
|                             "length" => 2047,
 | |
|                             "notnull" => true
 | |
|                         ),
 | |
|                         "handle" => array(
 | |
|                             "type" => "text",
 | |
|                             "length" => 255,
 | |
|                             "notnull" => true
 | |
|                         ),
 | |
|                         "secret" => array(
 | |
|                             "type" => "blob",
 | |
|                             "length" => "255",
 | |
|                             "notnull" => true
 | |
|                         ),
 | |
|                         "issued" => array(
 | |
|                             "type" => "integer",
 | |
|                             "notnull" => true
 | |
|                         ),
 | |
|                         "lifetime" => array(
 | |
|                             "type" => "integer",
 | |
|                             "notnull" => true
 | |
|                         ),
 | |
|                         "assoc_type" => array(
 | |
|                             "type" => "text",
 | |
|                             "length" => 64,
 | |
|                             "notnull" => true
 | |
|                         )
 | |
|                     );
 | |
|                     $options = array(
 | |
|                         "primary" => array(
 | |
|                             "server_url" => true,
 | |
|                             "handle" => true
 | |
|                         )
 | |
|                     );
 | |
|                     
 | |
|                     $r = $this->connection->createTable(
 | |
|                         $this->associations_table_name,
 | |
|                         $fields,
 | |
|                         $options);
 | |
|                     if (@PEAR::isError($r)) {
 | |
|                         return false;
 | |
|                     }
 | |
|                     break;
 | |
|             }
 | |
|         }
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     function storeAssociation($server_url, $association)
 | |
|     {
 | |
|         $fields = array(
 | |
|             "server_url" => array(
 | |
|                 "value" => $server_url,
 | |
|                 "key" => true
 | |
|             ),
 | |
|             "handle" => array(
 | |
|                 "value" => $association->handle,
 | |
|                 "key" => true
 | |
|             ),
 | |
|             "secret" => array(
 | |
|                 "value" => $association->secret,
 | |
|                 "type" => "blob"
 | |
|             ),
 | |
|             "issued" => array(
 | |
|                 "value" => $association->issued
 | |
|             ),
 | |
|             "lifetime" => array(
 | |
|                 "value" => $association->lifetime
 | |
|             ),
 | |
|             "assoc_type" => array(
 | |
|                 "value" => $association->assoc_type
 | |
|             )
 | |
|         );
 | |
|         
 | |
|         return !@PEAR::isError($this->connection->replace(
 | |
|                                   $this->associations_table_name,
 | |
|                                   $fields));
 | |
|     }
 | |
| 
 | |
|     function cleanupNonces()
 | |
|     {
 | |
|         global $Auth_OpenID_SKEW;
 | |
|         $v = time() - $Auth_OpenID_SKEW;
 | |
| 
 | |
|         return $this->connection->exec(
 | |
|             sprintf("DELETE FROM %s WHERE timestamp < %d",
 | |
|                     $this->nonces_table_name, $v));
 | |
|     }
 | |
| 
 | |
|     function cleanupAssociations()
 | |
|     {
 | |
|         return $this->connection->exec(
 | |
|             sprintf("DELETE FROM %s WHERE issued + lifetime < %d",
 | |
|                     $this->associations_table_name, time()));
 | |
|     }
 | |
| 
 | |
|     function getAssociation($server_url, $handle = null)
 | |
|     {
 | |
|         $sql = "";
 | |
|         $params = null;
 | |
|         $types = array(
 | |
|                        "text",
 | |
|                        "blob",
 | |
|                        "integer",
 | |
|                        "integer",
 | |
|                        "text"
 | |
|                        );
 | |
|         if ($handle !== null) {
 | |
|             $sql = sprintf("SELECT handle, secret, issued, lifetime, assoc_type " .
 | |
|                            "FROM %s WHERE server_url = ? AND handle = ?",
 | |
|                            $this->associations_table_name);
 | |
|             $params = array($server_url, $handle);
 | |
|         } else {
 | |
|             $sql = sprintf("SELECT handle, secret, issued, lifetime, assoc_type " .
 | |
|                            "FROM %s WHERE server_url = ? ORDER BY issued DESC",
 | |
|                            $this->associations_table_name);
 | |
|             $params = array($server_url);
 | |
|         }
 | |
|         
 | |
|         $assoc = $this->connection->getRow($sql, $types, $params);
 | |
| 
 | |
|         if (!$assoc || @PEAR::isError($assoc)) {
 | |
|             return null;
 | |
|         } else {
 | |
|             $association = new Auth_OpenID_Association($assoc['handle'],
 | |
|                                                        stream_get_contents(
 | |
|                                                            $assoc['secret']),
 | |
|                                                        $assoc['issued'],
 | |
|                                                        $assoc['lifetime'],
 | |
|                                                        $assoc['assoc_type']);
 | |
|             fclose($assoc['secret']);
 | |
|             return $association;
 | |
|         }
 | |
|     }
 | |
| 
 | |
|     function removeAssociation($server_url, $handle)
 | |
|     {
 | |
|         $r = $this->connection->execParam(
 | |
|             sprintf("DELETE FROM %s WHERE server_url = ? AND handle = ?",
 | |
|                     $this->associations_table_name),
 | |
|             array($server_url, $handle));
 | |
|         
 | |
|         if (@PEAR::isError($r) || $r == 0) {
 | |
|             return false;
 | |
|         }
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     function useNonce($server_url, $timestamp, $salt)
 | |
|     {
 | |
|         global $Auth_OpenID_SKEW;
 | |
| 
 | |
|         if (abs($timestamp - time()) > $Auth_OpenID_SKEW ) {
 | |
|             return false;
 | |
|         }
 | |
|         
 | |
|         $fields = array(
 | |
|                         "timestamp" => $timestamp,
 | |
|                         "salt" => $salt
 | |
|                         );
 | |
|         
 | |
|         if (!empty($server_url)) {
 | |
|             $fields["server_url"] = $server_url;
 | |
|         }
 | |
|         
 | |
|         $r = $this->connection->autoExecute(
 | |
|             $this->nonces_table_name,
 | |
|             $fields,
 | |
|             MDB2_AUTOQUERY_INSERT);
 | |
|         
 | |
|         if (@PEAR::isError($r)) {
 | |
|             return false;
 | |
|         }
 | |
|         return true;
 | |
|     }
 | |
| 
 | |
|     /**
 | |
|      * Resets the store by removing all records from the store's
 | |
|      * tables.
 | |
|      */
 | |
|     function reset()
 | |
|     {
 | |
|         $this->connection->query(sprintf("DELETE FROM %s",
 | |
|                                          $this->associations_table_name));
 | |
| 
 | |
|         $this->connection->query(sprintf("DELETE FROM %s",
 | |
|                                          $this->nonces_table_name));
 | |
|     }
 | |
| 
 | |
| }
 | |
| 
 | |
| ?>
 |