* @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 __construct($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 = [ "server_url" => [ "type" => "text", "length" => 2047, "notnull" => true ], "timestamp" => [ "type" => "integer", "notnull" => true ], "salt" => [ "type" => "text", "length" => 40, "fixed" => true, "notnull" => true ] ]; $constraint = [ "unique" => 1, "fields" => [ "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 = [ "server_url" => [ "type" => "text", "length" => 2047, "notnull" => true ], "handle" => [ "type" => "text", "length" => 255, "notnull" => true ], "secret" => [ "type" => "blob", "length" => "255", "notnull" => true ], "issued" => [ "type" => "integer", "notnull" => true ], "lifetime" => [ "type" => "integer", "notnull" => true ], "assoc_type" => [ "type" => "text", "length" => 64, "notnull" => true ] ]; $options = [ "primary" => [ "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 = [ "server_url" => [ "value" => $server_url, "key" => true ], "handle" => [ "value" => $association->handle, "key" => true ], "secret" => [ "value" => $association->secret, "type" => "blob" ], "issued" => [ "value" => $association->issued ], "lifetime" => [ "value" => $association->lifetime ], "assoc_type" => [ "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 = [ "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 = [$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 = [$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), [$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 = [ "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)); } } ?>