diff options
Diffstat (limited to 'vendors/php-openid/Auth/OpenID/SQLStore.php')
-rw-r--r-- | vendors/php-openid/Auth/OpenID/SQLStore.php | 557 |
1 files changed, 557 insertions, 0 deletions
diff --git a/vendors/php-openid/Auth/OpenID/SQLStore.php b/vendors/php-openid/Auth/OpenID/SQLStore.php new file mode 100644 index 000000000..c04059732 --- /dev/null +++ b/vendors/php-openid/Auth/OpenID/SQLStore.php @@ -0,0 +1,557 @@ +<?php + +/** + * SQL-backed OpenID stores. + * + * PHP versions 4 and 5 + * + * LICENSE: See the COPYING file included in this distribution. + * + * @package OpenID + * @author JanRain, Inc. <openid@janrain.com> + * @copyright 2005-2008 Janrain, Inc. + * @license http://www.apache.org/licenses/LICENSE-2.0 Apache + */ + +/** + * @access private + */ +require_once 'Auth/OpenID/Interface.php'; +require_once 'Auth/OpenID/Nonce.php'; + +/** + * @access private + */ +require_once 'Auth/OpenID.php'; + +/** + * @access private + */ +require_once 'Auth/OpenID/Nonce.php'; + +/** + * This is the parent class for the SQL stores, which contains the + * logic common to all of the SQL stores. + * + * 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. + * + * This class shouldn't be used directly. Use one of its subclasses + * instead, as those contain the code necessary to use a specific + * database. If you're an OpenID integrator and you'd like to create + * an SQL-driven store that wraps an application's database + * abstraction, be sure to create a subclass of + * {@link Auth_OpenID_DatabaseConnection} that calls the application's + * database abstraction calls. Then, pass an instance of your new + * database connection class to your SQLStore subclass constructor. + * + * All methods other than the constructor and createTables should be + * considered implementation details. + * + * @package OpenID + */ +class Auth_OpenID_SQLStore extends Auth_OpenID_OpenIDStore { + + /** + * This creates a new SQLStore 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 either be an PEAR DB + * connection handle or an instance of a subclass of + * Auth_OpenID_DatabaseConnection. + * + * @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_SQLStore($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, 'db_common') || + is_subclass_of($connection, + 'auth_openid_databaseconnection')))) { + trigger_error("Auth_OpenID_SQLStore 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 is a PEAR + // constant, so only try to use it if PEAR is present. Note + // that Auth_Openid_Databaseconnection instances need not + // implement ::setFetchMode for this reason. + if (is_subclass_of($this->connection, 'db_common')) { + $this->connection->setFetchMode(DB_FETCHMODE_ASSOC); + } + + 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; + + // Be sure to run the database queries with auto-commit mode + // turned OFF, because we want every function to run in a + // transaction, implicitly. As a rule, methods named with a + // leading underscore will NOT control transaction behavior. + // Callers of these methods will worry about transactions. + $this->connection->autoCommit(false); + + // Create an empty SQL strings array. + $this->sql = array(); + + // Call this method (which should be overridden by subclasses) + // to populate the $this->sql array with SQL strings. + $this->setSQL(); + + // Verify that all required SQL statements have been set, and + // raise an error if any expected SQL strings were either + // absent or empty. + list($missing, $empty) = $this->_verifySQL(); + + if ($missing) { + trigger_error("Expected keys in SQL query list: " . + implode(", ", $missing), + E_USER_ERROR); + return; + } + + if ($empty) { + trigger_error("SQL list keys have no SQL strings: " . + implode(", ", $empty), + E_USER_ERROR); + return; + } + + // Add table names to queries. + $this->_fixSQL(); + } + + function tableExists($table_name) + { + return !$this->isError( + $this->connection->query( + sprintf("SELECT * FROM %s LIMIT 0", + $table_name))); + } + + /** + * Returns true if $value constitutes a database error; returns + * false otherwise. + */ + function isError($value) + { + return PEAR::isError($value); + } + + /** + * Converts a query result to a boolean. If the result is a + * database error according to $this->isError(), this returns + * false; otherwise, this returns true. + */ + function resultToBool($obj) + { + if ($this->isError($obj)) { + return false; + } else { + return true; + } + } + + /** + * This method should be overridden by subclasses. This method is + * called by the constructor to set values in $this->sql, which is + * an array keyed on sql name. + */ + function setSQL() + { + } + + /** + * 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)); + } + + /** + * @access private + */ + function _verifySQL() + { + $missing = array(); + $empty = array(); + + $required_sql_keys = array( + 'nonce_table', + 'assoc_table', + 'set_assoc', + 'get_assoc', + 'get_assocs', + 'remove_assoc' + ); + + foreach ($required_sql_keys as $key) { + if (!array_key_exists($key, $this->sql)) { + $missing[] = $key; + } else if (!$this->sql[$key]) { + $empty[] = $key; + } + } + + return array($missing, $empty); + } + + /** + * @access private + */ + function _fixSQL() + { + $replacements = array( + array( + 'value' => $this->nonces_table_name, + 'keys' => array('nonce_table', + 'add_nonce', + 'clean_nonce') + ), + array( + 'value' => $this->associations_table_name, + 'keys' => array('assoc_table', + 'set_assoc', + 'get_assoc', + 'get_assocs', + 'remove_assoc', + 'clean_assoc') + ) + ); + + foreach ($replacements as $item) { + $value = $item['value']; + $keys = $item['keys']; + + foreach ($keys as $k) { + if (is_array($this->sql[$k])) { + foreach ($this->sql[$k] as $part_key => $part_value) { + $this->sql[$k][$part_key] = sprintf($part_value, + $value); + } + } else { + $this->sql[$k] = sprintf($this->sql[$k], $value); + } + } + } + } + + function blobDecode($blob) + { + return $blob; + } + + function blobEncode($str) + { + return $str; + } + + function createTables() + { + $this->connection->autoCommit(true); + $n = $this->create_nonce_table(); + $a = $this->create_assoc_table(); + $this->connection->autoCommit(false); + + if ($n && $a) { + return true; + } else { + return false; + } + } + + function create_nonce_table() + { + if (!$this->tableExists($this->nonces_table_name)) { + $r = $this->connection->query($this->sql['nonce_table']); + return $this->resultToBool($r); + } + return true; + } + + function create_assoc_table() + { + if (!$this->tableExists($this->associations_table_name)) { + $r = $this->connection->query($this->sql['assoc_table']); + return $this->resultToBool($r); + } + return true; + } + + /** + * @access private + */ + function _set_assoc($server_url, $handle, $secret, $issued, + $lifetime, $assoc_type) + { + return $this->connection->query($this->sql['set_assoc'], + array( + $server_url, + $handle, + $secret, + $issued, + $lifetime, + $assoc_type)); + } + + function storeAssociation($server_url, $association) + { + if ($this->resultToBool($this->_set_assoc( + $server_url, + $association->handle, + $this->blobEncode( + $association->secret), + $association->issued, + $association->lifetime, + $association->assoc_type + ))) { + $this->connection->commit(); + } else { + $this->connection->rollback(); + } + } + + /** + * @access private + */ + function _get_assoc($server_url, $handle) + { + $result = $this->connection->getRow($this->sql['get_assoc'], + array($server_url, $handle)); + if ($this->isError($result)) { + return null; + } else { + return $result; + } + } + + /** + * @access private + */ + function _get_assocs($server_url) + { + $result = $this->connection->getAll($this->sql['get_assocs'], + array($server_url)); + + if ($this->isError($result)) { + return array(); + } else { + return $result; + } + } + + function removeAssociation($server_url, $handle) + { + if ($this->_get_assoc($server_url, $handle) == null) { + return false; + } + + if ($this->resultToBool($this->connection->query( + $this->sql['remove_assoc'], + array($server_url, $handle)))) { + $this->connection->commit(); + } else { + $this->connection->rollback(); + } + + return true; + } + + function getAssociation($server_url, $handle = null) + { + if ($handle !== null) { + $assoc = $this->_get_assoc($server_url, $handle); + + $assocs = array(); + if ($assoc) { + $assocs[] = $assoc; + } + } else { + $assocs = $this->_get_assocs($server_url); + } + + if (!$assocs || (count($assocs) == 0)) { + return null; + } else { + $associations = array(); + + foreach ($assocs as $assoc_row) { + $assoc = new Auth_OpenID_Association($assoc_row['handle'], + $assoc_row['secret'], + $assoc_row['issued'], + $assoc_row['lifetime'], + $assoc_row['assoc_type']); + + $assoc->secret = $this->blobDecode($assoc->secret); + + if ($assoc->getExpiresIn() == 0) { + $this->removeAssociation($server_url, $assoc->handle); + } else { + $associations[] = array($assoc->issued, $assoc); + } + } + + if ($associations) { + $issued = array(); + $assocs = array(); + foreach ($associations as $key => $assoc) { + $issued[$key] = $assoc[0]; + $assocs[$key] = $assoc[1]; + } + + array_multisort($issued, SORT_DESC, $assocs, SORT_DESC, + $associations); + + // return the most recently issued one. + list($issued, $assoc) = $associations[0]; + return $assoc; + } else { + return null; + } + } + } + + /** + * @access private + */ + function _add_nonce($server_url, $timestamp, $salt) + { + $sql = $this->sql['add_nonce']; + $result = $this->connection->query($sql, array($server_url, + $timestamp, + $salt)); + if ($this->isError($result)) { + $this->connection->rollback(); + } else { + $this->connection->commit(); + } + return $this->resultToBool($result); + } + + function useNonce($server_url, $timestamp, $salt) + { + global $Auth_OpenID_SKEW; + + if ( abs($timestamp - time()) > $Auth_OpenID_SKEW ) { + return false; + } + + return $this->_add_nonce($server_url, $timestamp, $salt); + } + + /** + * "Octifies" a binary string by returning a string with escaped + * octal bytes. This is used for preparing binary data for + * PostgreSQL BYTEA fields. + * + * @access private + */ + function _octify($str) + { + $result = ""; + for ($i = 0; $i < Auth_OpenID::bytes($str); $i++) { + $ch = substr($str, $i, 1); + if ($ch == "\\") { + $result .= "\\\\\\\\"; + } else if (ord($ch) == 0) { + $result .= "\\\\000"; + } else { + $result .= "\\" . strval(decoct(ord($ch))); + } + } + return $result; + } + + /** + * "Unoctifies" octal-escaped data from PostgreSQL and returns the + * resulting ASCII (possibly binary) string. + * + * @access private + */ + function _unoctify($str) + { + $result = ""; + $i = 0; + while ($i < strlen($str)) { + $char = $str[$i]; + if ($char == "\\") { + // Look to see if the next char is a backslash and + // append it. + if ($str[$i + 1] != "\\") { + $octal_digits = substr($str, $i + 1, 3); + $dec = octdec($octal_digits); + $char = chr($dec); + $i += 4; + } else { + $char = "\\"; + $i += 2; + } + } else { + $i += 1; + } + + $result .= $char; + } + + return $result; + } + + function cleanupNonces() + { + global $Auth_OpenID_SKEW; + $v = time() - $Auth_OpenID_SKEW; + + $this->connection->query($this->sql['clean_nonce'], array($v)); + $num = $this->connection->affectedRows(); + $this->connection->commit(); + return $num; + } + + function cleanupAssociations() + { + $this->connection->query($this->sql['clean_assoc'], + array(time())); + $num = $this->connection->affectedRows(); + $this->connection->commit(); + return $num; + } +} + + |