mlanthaler: Workaround: This will fix a conflict of the built-in DB class with PEAR's DB class. These changes can be reverted as soon as the developers of OpenID applied my patch to their library.

(merged from branches/gsoc)


git-svn-id: svn://svn.silverstripe.com/silverstripe/open/modules/sapphire/trunk@42029 467b73ca-7a2a-4603-9d3b-597d59a354a9
This commit is contained in:
Ingo Schommer 2007-09-16 01:58:39 +00:00
parent 390d71785f
commit 8521832df4

View File

@ -8,9 +8,15 @@
/** /**
* Require the {@link Auth_OpenID_MySQLStore MySQL storage class} * Require the {@link Auth_OpenID OpenID utility function class}
*/ */
require_once "Auth/OpenID/MySQLStore.php"; require_once 'Auth/OpenID.php';
/**
* Require the {@link Auth_OpenID_OpenIDStore storage class}
*/
require_once 'Auth/OpenID/Interface.php';
/** /**
@ -30,7 +36,7 @@ require_once "Auth/OpenID/DatabaseConnection.php";
* *
* @author Markus Lanthaler <markus@silverstripe.com> * @author Markus Lanthaler <markus@silverstripe.com>
*/ */
class OpenIDStorage extends Auth_OpenID_MySQLStore { class OpenIDStorage extends Auth_OpenID_OpenIDStore {
/** /**
* This static variable is used to decrease the number of table existence * This static variable is used to decrease the number of table existence
@ -75,7 +81,76 @@ class OpenIDStorage extends Auth_OpenID_MySQLStore {
$connection = new OpenIDDatabaseConnection(); $connection = new OpenIDDatabaseConnection();
parent::__construct($connection, $associations_table, $nonces_table); //-------------------------------------------------------------------//
// This part normally resided in the Auth_OpenID_SQLStore class, but
// due to a name conflict of the DB class we can't simple inherit from
// it!
$this->associations_table_name = "oid_associations";
$this->nonces_table_name = "oid_nonces";
// Check the connection object type to be sure it's a PEAR compatible
// 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 compatible " .
"connection object (got ".get_class($connection).")",
E_USER_ERROR);
return;
}
$this->connection = $connection;
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();
//--------------------------------------------------------------------------------
if(self::$S_checkedTableExistence == false) { if(self::$S_checkedTableExistence == false) {
@ -106,8 +181,6 @@ class OpenIDStorage extends Auth_OpenID_MySQLStore {
* @access private * @access private
*/ */
function setSQL() { function setSQL() {
parent::setSQL();
$this->sql['nonce_table'] = $this->sql['nonce_table'] =
"CREATE TABLE %s (\n". "CREATE TABLE %s (\n".
" server_url VARCHAR(2047),\n". " server_url VARCHAR(2047),\n".
@ -126,6 +199,26 @@ class OpenIDStorage extends Auth_OpenID_MySQLStore {
" assoc_type VARCHAR(64),\n". " assoc_type VARCHAR(64),\n".
" PRIMARY KEY (server_url(255), handle)\n". " PRIMARY KEY (server_url(255), handle)\n".
")"; ")";
$this->sql['set_assoc'] =
"REPLACE INTO %s VALUES (?, ?, !, ?, ?, ?)";
$this->sql['get_assocs'] =
"SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
"WHERE server_url = ?";
$this->sql['get_assoc'] =
"SELECT handle, secret, issued, lifetime, assoc_type FROM %s ".
"WHERE server_url = ? AND handle = ?";
$this->sql['remove_assoc'] =
"DELETE FROM %s WHERE server_url = ? AND handle = ?";
$this->sql['add_nonce'] =
"INSERT INTO %s (server_url, timestamp, salt) VALUES (?, ?, ?)";
$this->sql['get_expired'] =
"SELECT server_url FROM %s WHERE issued + lifetime < ?";
} }
@ -161,6 +254,466 @@ class OpenIDStorage extends Auth_OpenID_MySQLStore {
return $this->resultToBool( return $this->resultToBool(
$this->connection->query($this->sql['assoc_table'])); $this->connection->query($this->sql['assoc_table']));
} }
/**
* Check if a table exists
*
* @param string $table_name Table to check
* @return bool Returns TRUE if the table exists, otherwise FALSE.
*/
function tableExists($table_name)
{
return !$this->isError($this->connection->query(sprintf(
"SELECT * FROM %s LIMIT 0", $table_name)));
}
/**
* Converts a query result to a boolean
*
* @param object $obj Query result
* @return bool If the result is a database error according to
* {@link isError()}, this returns FALSE; otherwise, this
* returns TRUE.
*/
function resultToBool($obj)
{
if($this->isError($obj)) {
return false;
} else {
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));
}
/**
* Check if all the required SQL statements are set
*
* @return array Returns an array of in the form of
* array($missing, $empty) containing the missing and
* empty SQL statements.
*/
private function _verifySQL()
{
$missing = array();
$empty = array();
$required_sql_keys = array('nonce_table',
'assoc_table',
'set_assoc',
'get_assoc',
'get_assocs',
'remove_assoc',
'get_expired',
);
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);
}
/**
* Fix SQL statements
*
* This function replaces the place holders in the set SQL statements
* with the right table names.
*/
private function _fixSQL()
{
$replacements = array(array('value' => $this->nonces_table_name,
'keys' => array('nonce_table',
'add_nonce')
),
array('value' => $this->associations_table_name,
'keys' => array('assoc_table',
'set_assoc',
'get_assoc',
'get_assocs',
'remove_assoc',
'get_expired')
)
);
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);
}
}
}
}
/**
* Decode a BLOB field
*
* @param mixed $blob The BLOB field's value
* @return mixed The decoded BLOB value
*/
function blobDecode($blob)
{
return $blob;
}
/**
* Encode a value for a BLOB field
*
* @param mixed $blob The value for the BLOB field
* @return mixed The encoded value
*/
function blobEncode($blob)
{
return "0x" . bin2hex($blob);
}
/**
* Create the needed tables
*
* @return boolean Returns TRUE on success or FALSE on failure.
*/
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;
}
}
/**
* Set an association
*
* Helper function for {@link storeAssociation()}.
*
* @param string $server_url The server URL
* @param string $handle The handle for the association
* @param string $secret The secret
* @param string $issued When was the association issued?
* @param string $lifetime The lifetime of the association
* @param string $assoc_type The association type
* @return
*/
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));
}
/**
* Store an association
*
* @param string $server_url The URL of the server
* @param Auth_OpenID_Association The association object to store
*/
function storeAssociation($server_url, Auth_OpenID_Association $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();
}
}
/**
* Get an association
*
* This is a helper function for {@link getAssociation()}
*
* @param string $server_url The server URL
* @param string $server_url The handle
* @return array Returns the association row or NULL on error.
*/
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;
}
}
/**
* Get all associations for a specific server URL
*
* This is a helper function for {@link getAssociation()}
*
* @param string $server_url The server URL
* @return array Returns the association rows or an empty array on error.
*/
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;
}
}
/**
* Get an association
*
* @param string $server_url The URL of the server for which the
* associations should be retrieved
* @param string $handle Optional: The handle if one specific association
* should be returned. If set to NULL the most
* recently issued one will be returned.
* @return Auth_OpenID_Association The association or NULL if not found.
*/
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;
}
}
}
/**
* Remove an association
*
* @param string $server_url The server URL
* @param string $server_url The handle
* @return array Returns the association row or NULL on error.
*/
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;
}
/**
* Get the expired associations
*
* @return array Returns an array of expired server URLs
*/
function getExpired()
{
$sql = $this->sql['get_expired'];
$result = $this->connection->getAll($sql, array(time()));
$expired = array();
foreach($result as $row) {
$expired[] = $row['server_url'];
}
return $expired;
}
/**
* Store a nonce
*
* This is a helper function for {@link useNonce()}.
*
* @param string $server_url The URL of the server for which the nonce is
* used
* @param string $timestamp The timestamp of the creation of the nonce
* @param string $salt The value of the nonce
* @return bool Returns TRUE on success, FALSE on failure.
*/
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);
}
/**
* Store a nonce
*
* @param string $server_url The URL of the server for which the nonce is
* used
* @param string $timestamp The timestamp of the creation of the nonce
* @param string $salt The value of the nonce
* @return bool Returns TRUE on success, FALSE on failure.
*/
function useNonce($server_url, $timestamp, $salt)
{
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.
*
* @param string $str The binary string to octify
* @return string The octified string
*/
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.
*
* @param string $str The octified string
* @return string The unoctified (binary) string
*/
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;
}
} }