| [ Index ] |
PHP Cross Reference of Joomla 1.5.26 DE |
[Summary view] [Print] [Text view]
1 <?php 2 3 /** 4 * This file supplies a Memcached store backend for OpenID servers and 5 * consumers. 6 * 7 * PHP versions 4 and 5 8 * 9 * LICENSE: See the COPYING file included in this distribution. 10 * 11 * @package OpenID 12 * @author JanRain, Inc. <openid@janrain.com> 13 * @copyright 2005-2008 Janrain, Inc. 14 * @license http://www.apache.org/licenses/LICENSE-2.0 Apache 15 */ 16 17 // Do not allow direct access 18 defined( '_JEXEC' ) or die( 'Restricted access' ); 19 20 /** 21 * Require base class for creating a new interface. 22 */ 23 require_once 'Auth/OpenID.php'; 24 require_once 'Auth/OpenID/Interface.php'; 25 require_once 'Auth/OpenID/HMAC.php'; 26 require_once 'Auth/OpenID/Nonce.php'; 27 28 /** 29 * This is a filesystem-based store for OpenID associations and 30 * nonces. This store should be safe for use in concurrent systems on 31 * both windows and unix (excluding NFS filesystems). There are a 32 * couple race conditions in the system, but those failure cases have 33 * been set up in such a way that the worst-case behavior is someone 34 * having to try to log in a second time. 35 * 36 * Most of the methods of this class are implementation details. 37 * People wishing to just use this store need only pay attention to 38 * the constructor. 39 * 40 * @package OpenID 41 */ 42 class Auth_OpenID_FileStore extends Auth_OpenID_OpenIDStore { 43 44 /** 45 * Initializes a new {@link Auth_OpenID_FileStore}. This 46 * initializes the nonce and association directories, which are 47 * subdirectories of the directory passed in. 48 * 49 * @param string $directory This is the directory to put the store 50 * directories in. 51 */ 52 function Auth_OpenID_FileStore($directory) 53 { 54 if (!Auth_OpenID::ensureDir($directory)) { 55 trigger_error('Not a directory and failed to create: ' 56 . $directory, E_USER_ERROR); 57 } 58 $directory = realpath($directory); 59 60 $this->directory = $directory; 61 $this->active = true; 62 63 $this->nonce_dir = $directory . DIRECTORY_SEPARATOR . 'nonces'; 64 65 $this->association_dir = $directory . DIRECTORY_SEPARATOR . 66 'associations'; 67 68 // Temp dir must be on the same filesystem as the assciations 69 // $directory. 70 $this->temp_dir = $directory . DIRECTORY_SEPARATOR . 'temp'; 71 72 $this->max_nonce_age = 6 * 60 * 60; // Six hours, in seconds 73 74 if (!$this->_setup()) { 75 trigger_error('Failed to initialize OpenID file store in ' . 76 $directory, E_USER_ERROR); 77 } 78 } 79 80 function destroy() 81 { 82 Auth_OpenID_FileStore::_rmtree($this->directory); 83 $this->active = false; 84 } 85 86 /** 87 * Make sure that the directories in which we store our data 88 * exist. 89 * 90 * @access private 91 */ 92 function _setup() 93 { 94 return (Auth_OpenID::ensureDir($this->nonce_dir) && 95 Auth_OpenID::ensureDir($this->association_dir) && 96 Auth_OpenID::ensureDir($this->temp_dir)); 97 } 98 99 /** 100 * Create a temporary file on the same filesystem as 101 * $this->association_dir. 102 * 103 * The temporary directory should not be cleaned if there are any 104 * processes using the store. If there is no active process using 105 * the store, it is safe to remove all of the files in the 106 * temporary directory. 107 * 108 * @return array ($fd, $filename) 109 * @access private 110 */ 111 function _mktemp() 112 { 113 $name = Auth_OpenID_FileStore::_mkstemp($dir = $this->temp_dir); 114 $file_obj = @fopen($name, 'wb'); 115 if ($file_obj !== false) { 116 return array($file_obj, $name); 117 } else { 118 Auth_OpenID_FileStore::_removeIfPresent($name); 119 } 120 } 121 122 function cleanupNonces() 123 { 124 global $Auth_OpenID_SKEW; 125 126 $nonces = Auth_OpenID_FileStore::_listdir($this->nonce_dir); 127 $now = time(); 128 129 $removed = 0; 130 // Check all nonces for expiry 131 foreach ($nonces as $nonce_fname) { 132 $base = basename($nonce_fname); 133 $parts = explode('-', $base, 2); 134 $timestamp = $parts[0]; 135 $timestamp = intval($timestamp, 16); 136 if (abs($timestamp - $now) > $Auth_OpenID_SKEW) { 137 Auth_OpenID_FileStore::_removeIfPresent($nonce_fname); 138 $removed += 1; 139 } 140 } 141 return $removed; 142 } 143 144 /** 145 * Create a unique filename for a given server url and 146 * handle. This implementation does not assume anything about the 147 * format of the handle. The filename that is returned will 148 * contain the domain name from the server URL for ease of human 149 * inspection of the data directory. 150 * 151 * @return string $filename 152 */ 153 function getAssociationFilename($server_url, $handle) 154 { 155 if (!$this->active) { 156 trigger_error("FileStore no longer active", E_USER_ERROR); 157 return null; 158 } 159 160 if (strpos($server_url, '://') === false) { 161 trigger_error(sprintf("Bad server URL: %s", $server_url), 162 E_USER_WARNING); 163 return null; 164 } 165 166 list($proto, $rest) = explode('://', $server_url, 2); 167 $parts = explode('/', $rest); 168 $domain = Auth_OpenID_FileStore::_filenameEscape($parts[0]); 169 $url_hash = Auth_OpenID_FileStore::_safe64($server_url); 170 if ($handle) { 171 $handle_hash = Auth_OpenID_FileStore::_safe64($handle); 172 } else { 173 $handle_hash = ''; 174 } 175 176 $filename = sprintf('%s-%s-%s-%s', $proto, $domain, $url_hash, 177 $handle_hash); 178 179 return $this->association_dir. DIRECTORY_SEPARATOR . $filename; 180 } 181 182 /** 183 * Store an association in the association directory. 184 */ 185 function storeAssociation($server_url, $association) 186 { 187 if (!$this->active) { 188 trigger_error("FileStore no longer active", E_USER_ERROR); 189 return false; 190 } 191 192 $association_s = $association->serialize(); 193 $filename = $this->getAssociationFilename($server_url, 194 $association->handle); 195 list($tmp_file, $tmp) = $this->_mktemp(); 196 197 if (!$tmp_file) { 198 trigger_error("_mktemp didn't return a valid file descriptor", 199 E_USER_WARNING); 200 return false; 201 } 202 203 fwrite($tmp_file, $association_s); 204 205 fflush($tmp_file); 206 207 fclose($tmp_file); 208 209 if (@rename($tmp, $filename)) { 210 return true; 211 } else { 212 // In case we are running on Windows, try unlinking the 213 // file in case it exists. 214 @unlink($filename); 215 216 // Now the target should not exist. Try renaming again, 217 // giving up if it fails. 218 if (@rename($tmp, $filename)) { 219 return true; 220 } 221 } 222 223 // If there was an error, don't leave the temporary file 224 // around. 225 Auth_OpenID_FileStore::_removeIfPresent($tmp); 226 return false; 227 } 228 229 /** 230 * Retrieve an association. If no handle is specified, return the 231 * association with the most recent issue time. 232 * 233 * @return mixed $association 234 */ 235 function getAssociation($server_url, $handle = null) 236 { 237 if (!$this->active) { 238 trigger_error("FileStore no longer active", E_USER_ERROR); 239 return null; 240 } 241 242 if ($handle === null) { 243 $handle = ''; 244 } 245 246 // The filename with the empty handle is a prefix of all other 247 // associations for the given server URL. 248 $filename = $this->getAssociationFilename($server_url, $handle); 249 250 if ($handle) { 251 return $this->_getAssociation($filename); 252 } else { 253 $association_files = 254 Auth_OpenID_FileStore::_listdir($this->association_dir); 255 $matching_files = array(); 256 257 // strip off the path to do the comparison 258 $name = basename($filename); 259 foreach ($association_files as $association_file) { 260 $base = basename($association_file); 261 if (strpos($base, $name) === 0) { 262 $matching_files[] = $association_file; 263 } 264 } 265 266 $matching_associations = array(); 267 // read the matching files and sort by time issued 268 foreach ($matching_files as $full_name) { 269 $association = $this->_getAssociation($full_name); 270 if ($association !== null) { 271 $matching_associations[] = array($association->issued, 272 $association); 273 } 274 } 275 276 $issued = array(); 277 $assocs = array(); 278 foreach ($matching_associations as $key => $assoc) { 279 $issued[$key] = $assoc[0]; 280 $assocs[$key] = $assoc[1]; 281 } 282 283 array_multisort($issued, SORT_DESC, $assocs, SORT_DESC, 284 $matching_associations); 285 286 // return the most recently issued one. 287 if ($matching_associations) { 288 list($issued, $assoc) = $matching_associations[0]; 289 return $assoc; 290 } else { 291 return null; 292 } 293 } 294 } 295 296 /** 297 * @access private 298 */ 299 function _getAssociation($filename) 300 { 301 if (!$this->active) { 302 trigger_error("FileStore no longer active", E_USER_ERROR); 303 return null; 304 } 305 306 $assoc_file = @fopen($filename, 'rb'); 307 308 if ($assoc_file === false) { 309 return null; 310 } 311 312 $assoc_s = fread($assoc_file, filesize($filename)); 313 fclose($assoc_file); 314 315 if (!$assoc_s) { 316 return null; 317 } 318 319 $association = 320 Auth_OpenID_Association::deserialize('Auth_OpenID_Association', 321 $assoc_s); 322 323 if (!$association) { 324 Auth_OpenID_FileStore::_removeIfPresent($filename); 325 return null; 326 } 327 328 if ($association->getExpiresIn() == 0) { 329 Auth_OpenID_FileStore::_removeIfPresent($filename); 330 return null; 331 } else { 332 return $association; 333 } 334 } 335 336 /** 337 * Remove an association if it exists. Do nothing if it does not. 338 * 339 * @return bool $success 340 */ 341 function removeAssociation($server_url, $handle) 342 { 343 if (!$this->active) { 344 trigger_error("FileStore no longer active", E_USER_ERROR); 345 return null; 346 } 347 348 $assoc = $this->getAssociation($server_url, $handle); 349 if ($assoc === null) { 350 return false; 351 } else { 352 $filename = $this->getAssociationFilename($server_url, $handle); 353 return Auth_OpenID_FileStore::_removeIfPresent($filename); 354 } 355 } 356 357 /** 358 * Return whether this nonce is present. As a side effect, mark it 359 * as no longer present. 360 * 361 * @return bool $present 362 */ 363 function useNonce($server_url, $timestamp, $salt) 364 { 365 global $Auth_OpenID_SKEW; 366 367 if (!$this->active) { 368 trigger_error("FileStore no longer active", E_USER_ERROR); 369 return null; 370 } 371 372 if ( abs($timestamp - time()) > $Auth_OpenID_SKEW ) { 373 return False; 374 } 375 376 if ($server_url) { 377 list($proto, $rest) = explode('://', $server_url, 2); 378 } else { 379 $proto = ''; 380 $rest = ''; 381 } 382 383 $parts = explode('/', $rest, 2); 384 $domain = $this->_filenameEscape($parts[0]); 385 $url_hash = $this->_safe64($server_url); 386 $salt_hash = $this->_safe64($salt); 387 388 $filename = sprintf('%08x-%s-%s-%s-%s', $timestamp, $proto, 389 $domain, $url_hash, $salt_hash); 390 $filename = $this->nonce_dir . DIRECTORY_SEPARATOR . $filename; 391 392 $result = @fopen($filename, 'x'); 393 394 if ($result === false) { 395 return false; 396 } else { 397 fclose($result); 398 return true; 399 } 400 } 401 402 /** 403 * Remove expired entries from the database. This is potentially 404 * expensive, so only run when it is acceptable to take time. 405 * 406 * @access private 407 */ 408 function _allAssocs() 409 { 410 $all_associations = array(); 411 412 $association_filenames = 413 Auth_OpenID_FileStore::_listdir($this->association_dir); 414 415 foreach ($association_filenames as $association_filename) { 416 $association_file = fopen($association_filename, 'rb'); 417 418 if ($association_file !== false) { 419 $assoc_s = fread($association_file, 420 filesize($association_filename)); 421 fclose($association_file); 422 423 // Remove expired or corrupted associations 424 $association = 425 Auth_OpenID_Association::deserialize( 426 'Auth_OpenID_Association', $assoc_s); 427 428 if ($association === null) { 429 Auth_OpenID_FileStore::_removeIfPresent( 430 $association_filename); 431 } else { 432 if ($association->getExpiresIn() == 0) { 433 $all_associations[] = array($association_filename, 434 $association); 435 } 436 } 437 } 438 } 439 440 return $all_associations; 441 } 442 443 function clean() 444 { 445 if (!$this->active) { 446 trigger_error("FileStore no longer active", E_USER_ERROR); 447 return null; 448 } 449 450 $nonces = Auth_OpenID_FileStore::_listdir($this->nonce_dir); 451 $now = time(); 452 453 // Check all nonces for expiry 454 foreach ($nonces as $nonce) { 455 if (!Auth_OpenID_checkTimestamp($nonce, $now)) { 456 $filename = $this->nonce_dir . DIRECTORY_SEPARATOR . $nonce; 457 Auth_OpenID_FileStore::_removeIfPresent($filename); 458 } 459 } 460 461 foreach ($this->_allAssocs() as $pair) { 462 list($assoc_filename, $assoc) = $pair; 463 if ($assoc->getExpiresIn() == 0) { 464 Auth_OpenID_FileStore::_removeIfPresent($assoc_filename); 465 } 466 } 467 } 468 469 /** 470 * @access private 471 */ 472 function _rmtree($dir) 473 { 474 if ($dir[strlen($dir) - 1] != DIRECTORY_SEPARATOR) { 475 $dir .= DIRECTORY_SEPARATOR; 476 } 477 478 if ($handle = opendir($dir)) { 479 while ($item = readdir($handle)) { 480 if (!in_array($item, array('.', '..'))) { 481 if (is_dir($dir . $item)) { 482 483 if (!Auth_OpenID_FileStore::_rmtree($dir . $item)) { 484 return false; 485 } 486 } else if (is_file($dir . $item)) { 487 if (!unlink($dir . $item)) { 488 return false; 489 } 490 } 491 } 492 } 493 494 closedir($handle); 495 496 if (!@rmdir($dir)) { 497 return false; 498 } 499 500 return true; 501 } else { 502 // Couldn't open directory. 503 return false; 504 } 505 } 506 507 /** 508 * @access private 509 */ 510 function _mkstemp($dir) 511 { 512 foreach (range(0, 4) as $i) { 513 $name = tempnam($dir, "php_openid_filestore_"); 514 515 if ($name !== false) { 516 return $name; 517 } 518 } 519 return false; 520 } 521 522 /** 523 * @access private 524 */ 525 function _mkdtemp($dir) 526 { 527 foreach (range(0, 4) as $i) { 528 $name = $dir . strval(DIRECTORY_SEPARATOR) . strval(getmypid()) . 529 "-" . strval(rand(1, time())); 530 if (!mkdir($name, 0700)) { 531 return false; 532 } else { 533 return $name; 534 } 535 } 536 return false; 537 } 538 539 /** 540 * @access private 541 */ 542 function _listdir($dir) 543 { 544 $handle = opendir($dir); 545 $files = array(); 546 while (false !== ($filename = readdir($handle))) { 547 if (!in_array($filename, array('.', '..'))) { 548 $files[] = $dir . DIRECTORY_SEPARATOR . $filename; 549 } 550 } 551 return $files; 552 } 553 554 /** 555 * @access private 556 */ 557 function _isFilenameSafe($char) 558 { 559 $_Auth_OpenID_filename_allowed = Auth_OpenID_letters . 560 Auth_OpenID_digits . "."; 561 return (strpos($_Auth_OpenID_filename_allowed, $char) !== false); 562 } 563 564 /** 565 * @access private 566 */ 567 function _safe64($str) 568 { 569 $h64 = base64_encode(Auth_OpenID_SHA1($str)); 570 $h64 = str_replace('+', '_', $h64); 571 $h64 = str_replace('/', '.', $h64); 572 $h64 = str_replace('=', '', $h64); 573 return $h64; 574 } 575 576 /** 577 * @access private 578 */ 579 function _filenameEscape($str) 580 { 581 $filename = ""; 582 $b = Auth_OpenID::toBytes($str); 583 584 for ($i = 0; $i < count($b); $i++) { 585 $c = $b[$i]; 586 if (Auth_OpenID_FileStore::_isFilenameSafe($c)) { 587 $filename .= $c; 588 } else { 589 $filename .= sprintf("_%02X", ord($c)); 590 } 591 } 592 return $filename; 593 } 594 595 /** 596 * Attempt to remove a file, returning whether the file existed at 597 * the time of the call. 598 * 599 * @access private 600 * @return bool $result True if the file was present, false if not. 601 */ 602 function _removeIfPresent($filename) 603 { 604 return @unlink($filename); 605 } 606 607 function cleanupAssociations() 608 { 609 $removed = 0; 610 foreach ($this->_allAssocs() as $pair) { 611 list($assoc_filename, $assoc) = $pair; 612 if ($assoc->getExpiresIn() == 0) { 613 $this->_removeIfPresent($assoc_filename); 614 $removed += 1; 615 } 616 } 617 return $removed; 618 } 619 } 620 621 ?>
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Mar 28 15:54:07 2012 | Cross-referenced by PHPXref 0.7.1 |