Support Joomla!

Joomla! 1.5 Documentation

Packages

Package: OpenID

Developer Network License

The Joomla! Developer Network content is © copyright 2006 by the individual contributors and can be used in accordance with the Creative Commons License, Attribution- NonCommercial- ShareAlike 2.5
Source code for file /openid/Auth/OpenID/FileStore.php

Documentation is available at FileStore.php

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

Documentation generated on Mon, 05 Mar 2007 20:59:19 +0000 by phpDocumentor 1.3.1