|
TYPO3 API
SVNRelease
|
00001 <?php 00002 00003 /** 00004 * SQL-backed OpenID stores. 00005 * 00006 * PHP versions 4 and 5 00007 * 00008 * LICENSE: See the COPYING file included in this distribution. 00009 * 00010 * @package OpenID 00011 * @author JanRain, Inc. <openid@janrain.com> 00012 * @copyright 2005-2008 Janrain, Inc. 00013 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache 00014 */ 00015 00016 /** 00017 * Require the PEAR DB module because we'll need it for the SQL-based 00018 * stores implemented here. We silence any errors from the inclusion 00019 * because it might not be present, and a user of the SQL stores may 00020 * supply an Auth_OpenID_DatabaseConnection instance that implements 00021 * its own storage. 00022 */ 00023 global $__Auth_OpenID_PEAR_AVAILABLE; 00024 $__Auth_OpenID_PEAR_AVAILABLE = @include_once 'DB.php'; 00025 00026 /** 00027 * @access private 00028 */ 00029 require_once 'Auth/OpenID/Interface.php'; 00030 require_once 'Auth/OpenID/Nonce.php'; 00031 00032 /** 00033 * @access private 00034 */ 00035 require_once 'Auth/OpenID.php'; 00036 00037 /** 00038 * @access private 00039 */ 00040 require_once 'Auth/OpenID/Nonce.php'; 00041 00042 /** 00043 * This is the parent class for the SQL stores, which contains the 00044 * logic common to all of the SQL stores. 00045 * 00046 * The table names used are determined by the class variables 00047 * associations_table_name and nonces_table_name. To change the name 00048 * of the tables used, pass new table names into the constructor. 00049 * 00050 * To create the tables with the proper schema, see the createTables 00051 * method. 00052 * 00053 * This class shouldn't be used directly. Use one of its subclasses 00054 * instead, as those contain the code necessary to use a specific 00055 * database. If you're an OpenID integrator and you'd like to create 00056 * an SQL-driven store that wraps an application's database 00057 * abstraction, be sure to create a subclass of 00058 * {@link Auth_OpenID_DatabaseConnection} that calls the application's 00059 * database abstraction calls. Then, pass an instance of your new 00060 * database connection class to your SQLStore subclass constructor. 00061 * 00062 * All methods other than the constructor and createTables should be 00063 * considered implementation details. 00064 * 00065 * @package OpenID 00066 */ 00067 class Auth_OpenID_SQLStore extends Auth_OpenID_OpenIDStore { 00068 00069 /** 00070 * This creates a new SQLStore instance. It requires an 00071 * established database connection be given to it, and it allows 00072 * overriding the default table names. 00073 * 00074 * @param connection $connection This must be an established 00075 * connection to a database of the correct type for the SQLStore 00076 * subclass you're using. This must either be an PEAR DB 00077 * connection handle or an instance of a subclass of 00078 * Auth_OpenID_DatabaseConnection. 00079 * 00080 * @param associations_table: This is an optional parameter to 00081 * specify the name of the table used for storing associations. 00082 * The default value is 'oid_associations'. 00083 * 00084 * @param nonces_table: This is an optional parameter to specify 00085 * the name of the table used for storing nonces. The default 00086 * value is 'oid_nonces'. 00087 */ 00088 function Auth_OpenID_SQLStore($connection, 00089 $associations_table = null, 00090 $nonces_table = null) 00091 { 00092 global $__Auth_OpenID_PEAR_AVAILABLE; 00093 00094 $this->associations_table_name = "oid_associations"; 00095 $this->nonces_table_name = "oid_nonces"; 00096 00097 // Check the connection object type to be sure it's a PEAR 00098 // database connection. 00099 if (!(is_object($connection) && 00100 (is_subclass_of($connection, 'db_common') || 00101 is_subclass_of($connection, 00102 'auth_openid_databaseconnection')))) { 00103 trigger_error("Auth_OpenID_SQLStore expected PEAR connection " . 00104 "object (got ".get_class($connection).")", 00105 E_USER_ERROR); 00106 return; 00107 } 00108 00109 $this->connection = $connection; 00110 00111 // Be sure to set the fetch mode so the results are keyed on 00112 // column name instead of column index. This is a PEAR 00113 // constant, so only try to use it if PEAR is present. Note 00114 // that Auth_Openid_Databaseconnection instances need not 00115 // implement ::setFetchMode for this reason. 00116 if ($__Auth_OpenID_PEAR_AVAILABLE) { 00117 $this->connection->setFetchMode(DB_FETCHMODE_ASSOC); 00118 } 00119 00120 if ($associations_table) { 00121 $this->associations_table_name = $associations_table; 00122 } 00123 00124 if ($nonces_table) { 00125 $this->nonces_table_name = $nonces_table; 00126 } 00127 00128 $this->max_nonce_age = 6 * 60 * 60; 00129 00130 // Be sure to run the database queries with auto-commit mode 00131 // turned OFF, because we want every function to run in a 00132 // transaction, implicitly. As a rule, methods named with a 00133 // leading underscore will NOT control transaction behavior. 00134 // Callers of these methods will worry about transactions. 00135 $this->connection->autoCommit(false); 00136 00137 // Create an empty SQL strings array. 00138 $this->sql = array(); 00139 00140 // Call this method (which should be overridden by subclasses) 00141 // to populate the $this->sql array with SQL strings. 00142 $this->setSQL(); 00143 00144 // Verify that all required SQL statements have been set, and 00145 // raise an error if any expected SQL strings were either 00146 // absent or empty. 00147 list($missing, $empty) = $this->_verifySQL(); 00148 00149 if ($missing) { 00150 trigger_error("Expected keys in SQL query list: " . 00151 implode(", ", $missing), 00152 E_USER_ERROR); 00153 return; 00154 } 00155 00156 if ($empty) { 00157 trigger_error("SQL list keys have no SQL strings: " . 00158 implode(", ", $empty), 00159 E_USER_ERROR); 00160 return; 00161 } 00162 00163 // Add table names to queries. 00164 $this->_fixSQL(); 00165 } 00166 00167 function tableExists($table_name) 00168 { 00169 return !$this->isError( 00170 $this->connection->query( 00171 sprintf("SELECT * FROM %s LIMIT 0", 00172 $table_name))); 00173 } 00174 00175 /** 00176 * Returns true if $value constitutes a database error; returns 00177 * false otherwise. 00178 */ 00179 function isError($value) 00180 { 00181 return PEAR::isError($value); 00182 } 00183 00184 /** 00185 * Converts a query result to a boolean. If the result is a 00186 * database error according to $this->isError(), this returns 00187 * false; otherwise, this returns true. 00188 */ 00189 function resultToBool($obj) 00190 { 00191 if ($this->isError($obj)) { 00192 return false; 00193 } else { 00194 return true; 00195 } 00196 } 00197 00198 /** 00199 * This method should be overridden by subclasses. This method is 00200 * called by the constructor to set values in $this->sql, which is 00201 * an array keyed on sql name. 00202 */ 00203 function setSQL() 00204 { 00205 } 00206 00207 /** 00208 * Resets the store by removing all records from the store's 00209 * tables. 00210 */ 00211 function reset() 00212 { 00213 $this->connection->query(sprintf("DELETE FROM %s", 00214 $this->associations_table_name)); 00215 00216 $this->connection->query(sprintf("DELETE FROM %s", 00217 $this->nonces_table_name)); 00218 } 00219 00220 /** 00221 * @access private 00222 */ 00223 function _verifySQL() 00224 { 00225 $missing = array(); 00226 $empty = array(); 00227 00228 $required_sql_keys = array( 00229 'nonce_table', 00230 'assoc_table', 00231 'set_assoc', 00232 'get_assoc', 00233 'get_assocs', 00234 'remove_assoc' 00235 ); 00236 00237 foreach ($required_sql_keys as $key) { 00238 if (!array_key_exists($key, $this->sql)) { 00239 $missing[] = $key; 00240 } else if (!$this->sql[$key]) { 00241 $empty[] = $key; 00242 } 00243 } 00244 00245 return array($missing, $empty); 00246 } 00247 00248 /** 00249 * @access private 00250 */ 00251 function _fixSQL() 00252 { 00253 $replacements = array( 00254 array( 00255 'value' => $this->nonces_table_name, 00256 'keys' => array('nonce_table', 00257 'add_nonce', 00258 'clean_nonce') 00259 ), 00260 array( 00261 'value' => $this->associations_table_name, 00262 'keys' => array('assoc_table', 00263 'set_assoc', 00264 'get_assoc', 00265 'get_assocs', 00266 'remove_assoc', 00267 'clean_assoc') 00268 ) 00269 ); 00270 00271 foreach ($replacements as $item) { 00272 $value = $item['value']; 00273 $keys = $item['keys']; 00274 00275 foreach ($keys as $k) { 00276 if (is_array($this->sql[$k])) { 00277 foreach ($this->sql[$k] as $part_key => $part_value) { 00278 $this->sql[$k][$part_key] = sprintf($part_value, 00279 $value); 00280 } 00281 } else { 00282 $this->sql[$k] = sprintf($this->sql[$k], $value); 00283 } 00284 } 00285 } 00286 } 00287 00288 function blobDecode($blob) 00289 { 00290 return $blob; 00291 } 00292 00293 function blobEncode($str) 00294 { 00295 return $str; 00296 } 00297 00298 function createTables() 00299 { 00300 $this->connection->autoCommit(true); 00301 $n = $this->create_nonce_table(); 00302 $a = $this->create_assoc_table(); 00303 $this->connection->autoCommit(false); 00304 00305 if ($n && $a) { 00306 return true; 00307 } else { 00308 return false; 00309 } 00310 } 00311 00312 function create_nonce_table() 00313 { 00314 if (!$this->tableExists($this->nonces_table_name)) { 00315 $r = $this->connection->query($this->sql['nonce_table']); 00316 return $this->resultToBool($r); 00317 } 00318 return true; 00319 } 00320 00321 function create_assoc_table() 00322 { 00323 if (!$this->tableExists($this->associations_table_name)) { 00324 $r = $this->connection->query($this->sql['assoc_table']); 00325 return $this->resultToBool($r); 00326 } 00327 return true; 00328 } 00329 00330 /** 00331 * @access private 00332 */ 00333 function _set_assoc($server_url, $handle, $secret, $issued, 00334 $lifetime, $assoc_type) 00335 { 00336 return $this->connection->query($this->sql['set_assoc'], 00337 array( 00338 $server_url, 00339 $handle, 00340 $secret, 00341 $issued, 00342 $lifetime, 00343 $assoc_type)); 00344 } 00345 00346 function storeAssociation($server_url, $association) 00347 { 00348 if ($this->resultToBool($this->_set_assoc( 00349 $server_url, 00350 $association->handle, 00351 $this->blobEncode( 00352 $association->secret), 00353 $association->issued, 00354 $association->lifetime, 00355 $association->assoc_type 00356 ))) { 00357 $this->connection->commit(); 00358 } else { 00359 $this->connection->rollback(); 00360 } 00361 } 00362 00363 /** 00364 * @access private 00365 */ 00366 function _get_assoc($server_url, $handle) 00367 { 00368 $result = $this->connection->getRow($this->sql['get_assoc'], 00369 array($server_url, $handle)); 00370 if ($this->isError($result)) { 00371 return null; 00372 } else { 00373 return $result; 00374 } 00375 } 00376 00377 /** 00378 * @access private 00379 */ 00380 function _get_assocs($server_url) 00381 { 00382 $result = $this->connection->getAll($this->sql['get_assocs'], 00383 array($server_url)); 00384 00385 if ($this->isError($result)) { 00386 return array(); 00387 } else { 00388 return $result; 00389 } 00390 } 00391 00392 function removeAssociation($server_url, $handle) 00393 { 00394 if ($this->_get_assoc($server_url, $handle) == null) { 00395 return false; 00396 } 00397 00398 if ($this->resultToBool($this->connection->query( 00399 $this->sql['remove_assoc'], 00400 array($server_url, $handle)))) { 00401 $this->connection->commit(); 00402 } else { 00403 $this->connection->rollback(); 00404 } 00405 00406 return true; 00407 } 00408 00409 function getAssociation($server_url, $handle = null) 00410 { 00411 if ($handle !== null) { 00412 $assoc = $this->_get_assoc($server_url, $handle); 00413 00414 $assocs = array(); 00415 if ($assoc) { 00416 $assocs[] = $assoc; 00417 } 00418 } else { 00419 $assocs = $this->_get_assocs($server_url); 00420 } 00421 00422 if (!$assocs || (count($assocs) == 0)) { 00423 return null; 00424 } else { 00425 $associations = array(); 00426 00427 foreach ($assocs as $assoc_row) { 00428 $assoc = new Auth_OpenID_Association($assoc_row['handle'], 00429 $assoc_row['secret'], 00430 $assoc_row['issued'], 00431 $assoc_row['lifetime'], 00432 $assoc_row['assoc_type']); 00433 00434 $assoc->secret = $this->blobDecode($assoc->secret); 00435 00436 if ($assoc->getExpiresIn() == 0) { 00437 $this->removeAssociation($server_url, $assoc->handle); 00438 } else { 00439 $associations[] = array($assoc->issued, $assoc); 00440 } 00441 } 00442 00443 if ($associations) { 00444 $issued = array(); 00445 $assocs = array(); 00446 foreach ($associations as $key => $assoc) { 00447 $issued[$key] = $assoc[0]; 00448 $assocs[$key] = $assoc[1]; 00449 } 00450 00451 array_multisort($issued, SORT_DESC, $assocs, SORT_DESC, 00452 $associations); 00453 00454 // return the most recently issued one. 00455 list($issued, $assoc) = $associations[0]; 00456 return $assoc; 00457 } else { 00458 return null; 00459 } 00460 } 00461 } 00462 00463 /** 00464 * @access private 00465 */ 00466 function _add_nonce($server_url, $timestamp, $salt) 00467 { 00468 $sql = $this->sql['add_nonce']; 00469 $result = $this->connection->query($sql, array($server_url, 00470 $timestamp, 00471 $salt)); 00472 if ($this->isError($result)) { 00473 $this->connection->rollback(); 00474 } else { 00475 $this->connection->commit(); 00476 } 00477 return $this->resultToBool($result); 00478 } 00479 00480 function useNonce($server_url, $timestamp, $salt) 00481 { 00482 global $Auth_OpenID_SKEW; 00483 00484 if ( abs($timestamp - time()) > $Auth_OpenID_SKEW ) { 00485 return False; 00486 } 00487 00488 return $this->_add_nonce($server_url, $timestamp, $salt); 00489 } 00490 00491 /** 00492 * "Octifies" a binary string by returning a string with escaped 00493 * octal bytes. This is used for preparing binary data for 00494 * PostgreSQL BYTEA fields. 00495 * 00496 * @access private 00497 */ 00498 function _octify($str) 00499 { 00500 $result = ""; 00501 for ($i = 0; $i < Auth_OpenID::bytes($str); $i++) { 00502 $ch = substr($str, $i, 1); 00503 if ($ch == "\\") { 00504 $result .= "\\\\\\\\"; 00505 } else if (ord($ch) == 0) { 00506 $result .= "\\\\000"; 00507 } else { 00508 $result .= "\\" . strval(decoct(ord($ch))); 00509 } 00510 } 00511 return $result; 00512 } 00513 00514 /** 00515 * "Unoctifies" octal-escaped data from PostgreSQL and returns the 00516 * resulting ASCII (possibly binary) string. 00517 * 00518 * @access private 00519 */ 00520 function _unoctify($str) 00521 { 00522 $result = ""; 00523 $i = 0; 00524 while ($i < strlen($str)) { 00525 $char = $str[$i]; 00526 if ($char == "\\") { 00527 // Look to see if the next char is a backslash and 00528 // append it. 00529 if ($str[$i + 1] != "\\") { 00530 $octal_digits = substr($str, $i + 1, 3); 00531 $dec = octdec($octal_digits); 00532 $char = chr($dec); 00533 $i += 4; 00534 } else { 00535 $char = "\\"; 00536 $i += 2; 00537 } 00538 } else { 00539 $i += 1; 00540 } 00541 00542 $result .= $char; 00543 } 00544 00545 return $result; 00546 } 00547 00548 function cleanupNonces() 00549 { 00550 global $Auth_OpenID_SKEW; 00551 $v = time() - $Auth_OpenID_SKEW; 00552 00553 $this->connection->query($this->sql['clean_nonce'], array($v)); 00554 $num = $this->connection->affectedRows(); 00555 $this->connection->commit(); 00556 return $num; 00557 } 00558 00559 function cleanupAssociations() 00560 { 00561 $this->connection->query($this->sql['clean_assoc'], 00562 array(time())); 00563 $num = $this->connection->affectedRows(); 00564 $this->connection->commit(); 00565 return $num; 00566 } 00567 } 00568 00569 ?>
1.8.0