|
TYPO3 API
SVNRelease
|
00001 <?php 00002 00003 /** 00004 * This file supplies a Memcached store backend for OpenID servers and 00005 * consumers. 00006 * 00007 * PHP versions 4 and 5 00008 * 00009 * LICENSE: See the COPYING file included in this distribution. 00010 * 00011 * @package OpenID 00012 * @author JanRain, Inc. <openid@janrain.com> 00013 * @copyright 2005-2008 Janrain, Inc. 00014 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache 00015 */ 00016 00017 /** 00018 * Require base class for creating a new interface. 00019 */ 00020 require_once 'Auth/OpenID.php'; 00021 require_once 'Auth/OpenID/Interface.php'; 00022 require_once 'Auth/OpenID/HMAC.php'; 00023 require_once 'Auth/OpenID/Nonce.php'; 00024 00025 /** 00026 * This is a filesystem-based store for OpenID associations and 00027 * nonces. This store should be safe for use in concurrent systems on 00028 * both windows and unix (excluding NFS filesystems). There are a 00029 * couple race conditions in the system, but those failure cases have 00030 * been set up in such a way that the worst-case behavior is someone 00031 * having to try to log in a second time. 00032 * 00033 * Most of the methods of this class are implementation details. 00034 * People wishing to just use this store need only pay attention to 00035 * the constructor. 00036 * 00037 * @package OpenID 00038 */ 00039 class Auth_OpenID_FileStore extends Auth_OpenID_OpenIDStore { 00040 00041 /** 00042 * Initializes a new {@link Auth_OpenID_FileStore}. This 00043 * initializes the nonce and association directories, which are 00044 * subdirectories of the directory passed in. 00045 * 00046 * @param string $directory This is the directory to put the store 00047 * directories in. 00048 */ 00049 function Auth_OpenID_FileStore($directory) 00050 { 00051 if (!Auth_OpenID::ensureDir($directory)) { 00052 trigger_error('Not a directory and failed to create: ' 00053 . $directory, E_USER_ERROR); 00054 } 00055 $directory = realpath($directory); 00056 00057 $this->directory = $directory; 00058 $this->active = true; 00059 00060 $this->nonce_dir = $directory . DIRECTORY_SEPARATOR . 'nonces'; 00061 00062 $this->association_dir = $directory . DIRECTORY_SEPARATOR . 00063 'associations'; 00064 00065 // Temp dir must be on the same filesystem as the assciations 00066 // $directory. 00067 $this->temp_dir = $directory . DIRECTORY_SEPARATOR . 'temp'; 00068 00069 $this->max_nonce_age = 6 * 60 * 60; // Six hours, in seconds 00070 00071 if (!$this->_setup()) { 00072 trigger_error('Failed to initialize OpenID file store in ' . 00073 $directory, E_USER_ERROR); 00074 } 00075 } 00076 00077 function destroy() 00078 { 00079 Auth_OpenID_FileStore::_rmtree($this->directory); 00080 $this->active = false; 00081 } 00082 00083 /** 00084 * Make sure that the directories in which we store our data 00085 * exist. 00086 * 00087 * @access private 00088 */ 00089 function _setup() 00090 { 00091 return (Auth_OpenID::ensureDir($this->nonce_dir) && 00092 Auth_OpenID::ensureDir($this->association_dir) && 00093 Auth_OpenID::ensureDir($this->temp_dir)); 00094 } 00095 00096 /** 00097 * Create a temporary file on the same filesystem as 00098 * $this->association_dir. 00099 * 00100 * The temporary directory should not be cleaned if there are any 00101 * processes using the store. If there is no active process using 00102 * the store, it is safe to remove all of the files in the 00103 * temporary directory. 00104 * 00105 * @return array ($fd, $filename) 00106 * @access private 00107 */ 00108 function _mktemp() 00109 { 00110 $name = Auth_OpenID_FileStore::_mkstemp($dir = $this->temp_dir); 00111 $file_obj = @fopen($name, 'wb'); 00112 if ($file_obj !== false) { 00113 return array($file_obj, $name); 00114 } else { 00115 Auth_OpenID_FileStore::_removeIfPresent($name); 00116 } 00117 } 00118 00119 function cleanupNonces() 00120 { 00121 global $Auth_OpenID_SKEW; 00122 00123 $nonces = Auth_OpenID_FileStore::_listdir($this->nonce_dir); 00124 $now = time(); 00125 00126 $removed = 0; 00127 // Check all nonces for expiry 00128 foreach ($nonces as $nonce_fname) { 00129 $base = basename($nonce_fname); 00130 $parts = explode('-', $base, 2); 00131 $timestamp = $parts[0]; 00132 $timestamp = intval($timestamp, 16); 00133 if (abs($timestamp - $now) > $Auth_OpenID_SKEW) { 00134 Auth_OpenID_FileStore::_removeIfPresent($nonce_fname); 00135 $removed += 1; 00136 } 00137 } 00138 return $removed; 00139 } 00140 00141 /** 00142 * Create a unique filename for a given server url and 00143 * handle. This implementation does not assume anything about the 00144 * format of the handle. The filename that is returned will 00145 * contain the domain name from the server URL for ease of human 00146 * inspection of the data directory. 00147 * 00148 * @return string $filename 00149 */ 00150 function getAssociationFilename($server_url, $handle) 00151 { 00152 if (!$this->active) { 00153 trigger_error("FileStore no longer active", E_USER_ERROR); 00154 return null; 00155 } 00156 00157 if (strpos($server_url, '://') === false) { 00158 trigger_error(sprintf("Bad server URL: %s", $server_url), 00159 E_USER_WARNING); 00160 return null; 00161 } 00162 00163 list($proto, $rest) = explode('://', $server_url, 2); 00164 $parts = explode('/', $rest); 00165 $domain = Auth_OpenID_FileStore::_filenameEscape($parts[0]); 00166 $url_hash = Auth_OpenID_FileStore::_safe64($server_url); 00167 if ($handle) { 00168 $handle_hash = Auth_OpenID_FileStore::_safe64($handle); 00169 } else { 00170 $handle_hash = ''; 00171 } 00172 00173 $filename = sprintf('%s-%s-%s-%s', $proto, $domain, $url_hash, 00174 $handle_hash); 00175 00176 return $this->association_dir. DIRECTORY_SEPARATOR . $filename; 00177 } 00178 00179 /** 00180 * Store an association in the association directory. 00181 */ 00182 function storeAssociation($server_url, $association) 00183 { 00184 if (!$this->active) { 00185 trigger_error("FileStore no longer active", E_USER_ERROR); 00186 return false; 00187 } 00188 00189 $association_s = $association->serialize(); 00190 $filename = $this->getAssociationFilename($server_url, 00191 $association->handle); 00192 list($tmp_file, $tmp) = $this->_mktemp(); 00193 00194 if (!$tmp_file) { 00195 trigger_error("_mktemp didn't return a valid file descriptor", 00196 E_USER_WARNING); 00197 return false; 00198 } 00199 00200 fwrite($tmp_file, $association_s); 00201 00202 fflush($tmp_file); 00203 00204 fclose($tmp_file); 00205 00206 if (@rename($tmp, $filename)) { 00207 return true; 00208 } else { 00209 // In case we are running on Windows, try unlinking the 00210 // file in case it exists. 00211 @unlink($filename); 00212 00213 // Now the target should not exist. Try renaming again, 00214 // giving up if it fails. 00215 if (@rename($tmp, $filename)) { 00216 return true; 00217 } 00218 } 00219 00220 // If there was an error, don't leave the temporary file 00221 // around. 00222 Auth_OpenID_FileStore::_removeIfPresent($tmp); 00223 return false; 00224 } 00225 00226 /** 00227 * Retrieve an association. If no handle is specified, return the 00228 * association with the most recent issue time. 00229 * 00230 * @return mixed $association 00231 */ 00232 function getAssociation($server_url, $handle = null) 00233 { 00234 if (!$this->active) { 00235 trigger_error("FileStore no longer active", E_USER_ERROR); 00236 return null; 00237 } 00238 00239 if ($handle === null) { 00240 $handle = ''; 00241 } 00242 00243 // The filename with the empty handle is a prefix of all other 00244 // associations for the given server URL. 00245 $filename = $this->getAssociationFilename($server_url, $handle); 00246 00247 if ($handle) { 00248 return $this->_getAssociation($filename); 00249 } else { 00250 $association_files = 00251 Auth_OpenID_FileStore::_listdir($this->association_dir); 00252 $matching_files = array(); 00253 00254 // strip off the path to do the comparison 00255 $name = basename($filename); 00256 foreach ($association_files as $association_file) { 00257 $base = basename($association_file); 00258 if (strpos($base, $name) === 0) { 00259 $matching_files[] = $association_file; 00260 } 00261 } 00262 00263 $matching_associations = array(); 00264 // read the matching files and sort by time issued 00265 foreach ($matching_files as $full_name) { 00266 $association = $this->_getAssociation($full_name); 00267 if ($association !== null) { 00268 $matching_associations[] = array($association->issued, 00269 $association); 00270 } 00271 } 00272 00273 $issued = array(); 00274 $assocs = array(); 00275 foreach ($matching_associations as $key => $assoc) { 00276 $issued[$key] = $assoc[0]; 00277 $assocs[$key] = $assoc[1]; 00278 } 00279 00280 array_multisort($issued, SORT_DESC, $assocs, SORT_DESC, 00281 $matching_associations); 00282 00283 // return the most recently issued one. 00284 if ($matching_associations) { 00285 list($issued, $assoc) = $matching_associations[0]; 00286 return $assoc; 00287 } else { 00288 return null; 00289 } 00290 } 00291 } 00292 00293 /** 00294 * @access private 00295 */ 00296 function _getAssociation($filename) 00297 { 00298 if (!$this->active) { 00299 trigger_error("FileStore no longer active", E_USER_ERROR); 00300 return null; 00301 } 00302 00303 $assoc_file = @fopen($filename, 'rb'); 00304 00305 if ($assoc_file === false) { 00306 return null; 00307 } 00308 00309 $assoc_s = fread($assoc_file, filesize($filename)); 00310 fclose($assoc_file); 00311 00312 if (!$assoc_s) { 00313 return null; 00314 } 00315 00316 $association = 00317 Auth_OpenID_Association::deserialize('Auth_OpenID_Association', 00318 $assoc_s); 00319 00320 if (!$association) { 00321 Auth_OpenID_FileStore::_removeIfPresent($filename); 00322 return null; 00323 } 00324 00325 if ($association->getExpiresIn() == 0) { 00326 Auth_OpenID_FileStore::_removeIfPresent($filename); 00327 return null; 00328 } else { 00329 return $association; 00330 } 00331 } 00332 00333 /** 00334 * Remove an association if it exists. Do nothing if it does not. 00335 * 00336 * @return bool $success 00337 */ 00338 function removeAssociation($server_url, $handle) 00339 { 00340 if (!$this->active) { 00341 trigger_error("FileStore no longer active", E_USER_ERROR); 00342 return null; 00343 } 00344 00345 $assoc = $this->getAssociation($server_url, $handle); 00346 if ($assoc === null) { 00347 return false; 00348 } else { 00349 $filename = $this->getAssociationFilename($server_url, $handle); 00350 return Auth_OpenID_FileStore::_removeIfPresent($filename); 00351 } 00352 } 00353 00354 /** 00355 * Return whether this nonce is present. As a side effect, mark it 00356 * as no longer present. 00357 * 00358 * @return bool $present 00359 */ 00360 function useNonce($server_url, $timestamp, $salt) 00361 { 00362 global $Auth_OpenID_SKEW; 00363 00364 if (!$this->active) { 00365 trigger_error("FileStore no longer active", E_USER_ERROR); 00366 return null; 00367 } 00368 00369 if ( abs($timestamp - time()) > $Auth_OpenID_SKEW ) { 00370 return False; 00371 } 00372 00373 if ($server_url) { 00374 list($proto, $rest) = explode('://', $server_url, 2); 00375 } else { 00376 $proto = ''; 00377 $rest = ''; 00378 } 00379 00380 $parts = explode('/', $rest, 2); 00381 $domain = $this->_filenameEscape($parts[0]); 00382 $url_hash = $this->_safe64($server_url); 00383 $salt_hash = $this->_safe64($salt); 00384 00385 $filename = sprintf('%08x-%s-%s-%s-%s', $timestamp, $proto, 00386 $domain, $url_hash, $salt_hash); 00387 $filename = $this->nonce_dir . DIRECTORY_SEPARATOR . $filename; 00388 00389 $result = @fopen($filename, 'x'); 00390 00391 if ($result === false) { 00392 return false; 00393 } else { 00394 fclose($result); 00395 return true; 00396 } 00397 } 00398 00399 /** 00400 * Remove expired entries from the database. This is potentially 00401 * expensive, so only run when it is acceptable to take time. 00402 * 00403 * @access private 00404 */ 00405 function _allAssocs() 00406 { 00407 $all_associations = array(); 00408 00409 $association_filenames = 00410 Auth_OpenID_FileStore::_listdir($this->association_dir); 00411 00412 foreach ($association_filenames as $association_filename) { 00413 $association_file = fopen($association_filename, 'rb'); 00414 00415 if ($association_file !== false) { 00416 $assoc_s = fread($association_file, 00417 filesize($association_filename)); 00418 fclose($association_file); 00419 00420 // Remove expired or corrupted associations 00421 $association = 00422 Auth_OpenID_Association::deserialize( 00423 'Auth_OpenID_Association', $assoc_s); 00424 00425 if ($association === null) { 00426 Auth_OpenID_FileStore::_removeIfPresent( 00427 $association_filename); 00428 } else { 00429 if ($association->getExpiresIn() == 0) { 00430 $all_associations[] = array($association_filename, 00431 $association); 00432 } 00433 } 00434 } 00435 } 00436 00437 return $all_associations; 00438 } 00439 00440 function clean() 00441 { 00442 if (!$this->active) { 00443 trigger_error("FileStore no longer active", E_USER_ERROR); 00444 return null; 00445 } 00446 00447 $nonces = Auth_OpenID_FileStore::_listdir($this->nonce_dir); 00448 $now = time(); 00449 00450 // Check all nonces for expiry 00451 foreach ($nonces as $nonce) { 00452 if (!Auth_OpenID_checkTimestamp($nonce, $now)) { 00453 $filename = $this->nonce_dir . DIRECTORY_SEPARATOR . $nonce; 00454 Auth_OpenID_FileStore::_removeIfPresent($filename); 00455 } 00456 } 00457 00458 foreach ($this->_allAssocs() as $pair) { 00459 list($assoc_filename, $assoc) = $pair; 00460 if ($assoc->getExpiresIn() == 0) { 00461 Auth_OpenID_FileStore::_removeIfPresent($assoc_filename); 00462 } 00463 } 00464 } 00465 00466 /** 00467 * @access private 00468 */ 00469 function _rmtree($dir) 00470 { 00471 if ($dir[strlen($dir) - 1] != DIRECTORY_SEPARATOR) { 00472 $dir .= DIRECTORY_SEPARATOR; 00473 } 00474 00475 if ($handle = opendir($dir)) { 00476 while ($item = readdir($handle)) { 00477 if (!in_array($item, array('.', '..'))) { 00478 if (is_dir($dir . $item)) { 00479 00480 if (!Auth_OpenID_FileStore::_rmtree($dir . $item)) { 00481 return false; 00482 } 00483 } else if (is_file($dir . $item)) { 00484 if (!unlink($dir . $item)) { 00485 return false; 00486 } 00487 } 00488 } 00489 } 00490 00491 closedir($handle); 00492 00493 if (!@rmdir($dir)) { 00494 return false; 00495 } 00496 00497 return true; 00498 } else { 00499 // Couldn't open directory. 00500 return false; 00501 } 00502 } 00503 00504 /** 00505 * @access private 00506 */ 00507 function _mkstemp($dir) 00508 { 00509 foreach (range(0, 4) as $i) { 00510 $name = tempnam($dir, "php_openid_filestore_"); 00511 00512 if ($name !== false) { 00513 return $name; 00514 } 00515 } 00516 return false; 00517 } 00518 00519 /** 00520 * @access private 00521 */ 00522 function _mkdtemp($dir) 00523 { 00524 foreach (range(0, 4) as $i) { 00525 $name = $dir . strval(DIRECTORY_SEPARATOR) . strval(getmypid()) . 00526 "-" . strval(rand(1, time())); 00527 if (!mkdir($name, 0700)) { 00528 return false; 00529 } else { 00530 return $name; 00531 } 00532 } 00533 return false; 00534 } 00535 00536 /** 00537 * @access private 00538 */ 00539 function _listdir($dir) 00540 { 00541 $handle = opendir($dir); 00542 $files = array(); 00543 while (false !== ($filename = readdir($handle))) { 00544 if (!in_array($filename, array('.', '..'))) { 00545 $files[] = $dir . DIRECTORY_SEPARATOR . $filename; 00546 } 00547 } 00548 return $files; 00549 } 00550 00551 /** 00552 * @access private 00553 */ 00554 function _isFilenameSafe($char) 00555 { 00556 $_Auth_OpenID_filename_allowed = Auth_OpenID_letters . 00557 Auth_OpenID_digits . "."; 00558 return (strpos($_Auth_OpenID_filename_allowed, $char) !== false); 00559 } 00560 00561 /** 00562 * @access private 00563 */ 00564 function _safe64($str) 00565 { 00566 $h64 = base64_encode(Auth_OpenID_SHA1($str)); 00567 $h64 = str_replace('+', '_', $h64); 00568 $h64 = str_replace('/', '.', $h64); 00569 $h64 = str_replace('=', '', $h64); 00570 return $h64; 00571 } 00572 00573 /** 00574 * @access private 00575 */ 00576 function _filenameEscape($str) 00577 { 00578 $filename = ""; 00579 $b = Auth_OpenID::toBytes($str); 00580 00581 for ($i = 0; $i < count($b); $i++) { 00582 $c = $b[$i]; 00583 if (Auth_OpenID_FileStore::_isFilenameSafe($c)) { 00584 $filename .= $c; 00585 } else { 00586 $filename .= sprintf("_%02X", ord($c)); 00587 } 00588 } 00589 return $filename; 00590 } 00591 00592 /** 00593 * Attempt to remove a file, returning whether the file existed at 00594 * the time of the call. 00595 * 00596 * @access private 00597 * @return bool $result True if the file was present, false if not. 00598 */ 00599 function _removeIfPresent($filename) 00600 { 00601 return @unlink($filename); 00602 } 00603 00604 function cleanupAssociations() 00605 { 00606 $removed = 0; 00607 foreach ($this->_allAssocs() as $pair) { 00608 list($assoc_filename, $assoc) = $pair; 00609 if ($assoc->getExpiresIn() == 0) { 00610 $this->_removeIfPresent($assoc_filename); 00611 $removed += 1; 00612 } 00613 } 00614 return $removed; 00615 } 00616 } 00617 00618 ?>
1.8.0