123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558 |
- <?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;
- }
- }
|