00001 <?php
00002
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
00039 class Auth_OpenID_FileStore extends Auth_OpenID_OpenIDStore {
00040
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
00066
00067 $this->temp_dir = $directory . DIRECTORY_SEPARATOR . 'temp';
00068
00069 $this->max_nonce_age = 6 * 60 * 60;
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
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
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
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
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
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
00210
00211 @unlink($filename);
00212
00213
00214
00215 if (@rename($tmp, $filename)) {
00216 return true;
00217 }
00218 }
00219
00220
00221
00222 Auth_OpenID_FileStore::_removeIfPresent($tmp);
00223 return false;
00224 }
00225
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
00244
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
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
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
00284 if ($matching_associations) {
00285 list($issued, $assoc) = $matching_associations[0];
00286 return $assoc;
00287 } else {
00288 return null;
00289 }
00290 }
00291 }
00292
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
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
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
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
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
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
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
00500 return false;
00501 }
00502 }
00503
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
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
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
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
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
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
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 ?>