summaryrefslogtreecommitdiffstats
path: root/inc/simplesamlphp/vendor/openid/php-openid/Auth/OpenID/SQLStore.php
diff options
context:
space:
mode:
Diffstat (limited to 'inc/simplesamlphp/vendor/openid/php-openid/Auth/OpenID/SQLStore.php')
-rw-r--r--inc/simplesamlphp/vendor/openid/php-openid/Auth/OpenID/SQLStore.php557
1 files changed, 557 insertions, 0 deletions
diff --git a/inc/simplesamlphp/vendor/openid/php-openid/Auth/OpenID/SQLStore.php b/inc/simplesamlphp/vendor/openid/php-openid/Auth/OpenID/SQLStore.php
new file mode 100644
index 0000000..2dc731a
--- /dev/null
+++ b/inc/simplesamlphp/vendor/openid/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;
+ }
+}
+
+