Source code for file /openid/Auth/OpenID/FileStore.php
Documentation is available at FileStore.php
* This file supplies a Memcached store backend for OpenID servers and
* LICENSE: See the COPYING file included in this distribution.
* @copyright 2005 Janrain, Inc.
* @license http://www.gnu.org/copyleft/lesser.html LGPL
* Require base class for creating a new interface.
require_once 'Auth/OpenID.php';
require_once 'Auth/OpenID/Interface.php';
require_once 'Auth/OpenID/HMACSHA1.php';
* This is a filesystem-based store for OpenID associations and
* nonces. This store should be safe for use in concurrent systems on
* both windows and unix (excluding NFS filesystems). There are a
* couple race conditions in the system, but those failure cases have
* been set up in such a way that the worst-case behavior is someone
* having to try to log in a second time.
* Most of the methods of this class are implementation details.
* People wishing to just use this store need only pay attention to
* Initializes a new {@link Auth_OpenID_FileStore}. This
* initializes the nonce and association directories, which are
* subdirectories of the directory passed in.
* @param string $directory This is the directory to put the store
if (!Auth_OpenID::ensureDir($directory)) {
.
$directory, E_USER_ERROR);
$this->directory =
$directory;
$this->nonce_dir =
$directory .
DIRECTORY_SEPARATOR .
'nonces';
$this->association_dir =
$directory .
DIRECTORY_SEPARATOR .
// Temp dir must be on the same filesystem as the assciations
// $directory and the $directory containing the auth key file.
$this->temp_dir =
$directory .
DIRECTORY_SEPARATOR .
'temp';
$this->auth_key_name =
$directory .
DIRECTORY_SEPARATOR .
'auth_key';
$this->max_nonce_age =
6 *
60 *
60; // Six hours, in seconds
$directory, E_USER_ERROR);
* Make sure that the directories in which we store our data
return (Auth_OpenID::ensureDir(dirname($this->auth_key_name)) &&
Auth_OpenID::ensureDir($this->nonce_dir) &&
Auth_OpenID::ensureDir($this->association_dir) &&
Auth_OpenID::ensureDir($this->temp_dir));
* Create a temporary file on the same filesystem as
* $this->auth_key_name and $this->association_dir.
* The temporary directory should not be cleaned if there are any
* processes using the store. If there is no active process using
* the store, it is safe to remove all of the files in the
* @return array ($fd, $filename)
$file_obj =
@fopen($name, 'wb');
if ($file_obj !==
false) {
return array($file_obj, $name);
* Read the auth key from the auth key file. Will return None if
* there is currently no key.
$auth_key_file =
@fopen($this->auth_key_name, 'rb');
if ($auth_key_file ===
false) {
* Generate a new random auth key and safely store it in the
* location specified by $this->auth_key_name.
list
($file_obj, $tmp) =
$this->_mktemp();
$saved =
link($tmp, $this->auth_key_name);
$saved =
rename($tmp, $this->auth_key_name);
// The link failed, either because we lack the permission,
// or because the file already exists; try to read the key
// in case the file already existed.
* Retrieve the auth key from the file specified by
* $this->auth_key_name, creating it if it does not exist.
if ($auth_key ===
null) {
$fmt =
'Got an invalid auth key from %s. Expected '.
'%d-byte string. Got: %s';
* Create a unique filename for a given server url and
* handle. This implementation does not assume anything about the
* format of the handle. The filename that is returned will
* contain the domain name from the server URL for ease of human
* inspection of the data directory.
* @return string $filename
if (strpos($server_url, '://') ===
false) {
list
($proto, $rest) =
explode('://', $server_url, 2);
$filename =
sprintf('%s-%s-%s-%s', $proto, $domain, $url_hash,
return $this->association_dir.
DIRECTORY_SEPARATOR .
$filename;
* Store an association in the association directory.
$association_s =
$association->serialize();
list
($tmp_file, $tmp) =
$this->_mktemp();
fwrite($tmp_file, $association_s);
if (@rename($tmp, $filename)) {
// In case we are running on Windows, try unlinking the
// file in case it exists.
// Now the target should not exist. Try renaming again,
// giving up if it fails.
if (@rename($tmp, $filename)) {
// If there was an error, don't leave the temporary file
* Retrieve an association. If no handle is specified, return the
* association with the most recent issue time.
* @return mixed $association
// The filename with the empty handle is a prefix of all other
// associations for the given server URL.
return $this->_getAssociation($filename);
$matching_files =
array();
// strip off the path to do the comparison
foreach ($association_files as $association_file) {
if (strpos($association_file, $name) ===
0) {
$matching_files[] =
$association_file;
$matching_associations =
array();
// read the matching files and sort by time issued
foreach ($matching_files as $name) {
$full_name =
$this->association_dir .
DIRECTORY_SEPARATOR .
$association =
$this->_getAssociation($full_name);
if ($association !==
null) {
$matching_associations[] =
array($association->issued,
foreach ($matching_associations as $key =>
$assoc) {
$issued[$key] =
$assoc[0];
$assocs[$key] =
$assoc[1];
// return the most recently issued one.
if ($matching_associations) {
list
($issued, $assoc) =
$matching_associations[0];
function _getAssociation($filename)
$assoc_file =
@fopen($filename, 'rb');
if ($assoc_file ===
false) {
if ($association->getExpiresIn() ==
0) {
* Remove an association if it exists. Do nothing if it does not.
* Mark this nonce as present.
$filename =
$this->nonce_dir .
DIRECTORY_SEPARATOR .
$nonce;
$nonce_file =
fopen($filename, 'w');
if ($nonce_file ===
false) {
* Return whether this nonce is present. As a side effect, mark it
$filename =
$this->nonce_dir .
DIRECTORY_SEPARATOR .
$nonce;
// Either it is too old or we are using it. Either way, we
$nonce_age =
$now -
$st[9];
// We can us it if the age of the file is less than the
return $nonce_age <=
$this->max_nonce_age;
* Remove expired entries from the database. This is potentially
* expensive, so only run when it is acceptable to take time.
// Check all nonces for expiry
foreach ($nonces as $nonce) {
$filename =
$this->nonce_dir .
DIRECTORY_SEPARATOR .
$nonce;
// Remove the nonce if it has expired
$nonce_age =
$now -
$st[9];
if ($nonce_age >
$this->max_nonce_age) {
foreach ($association_filenames as $association_filename) {
$association_file =
fopen($association_filename, 'rb');
if ($association_file !==
false) {
$assoc_s =
fread($association_file,
// Remove expired or corrupted associations
'Auth_OpenID_Association', $assoc_s);
if ($association ===
null) {
if ($association->getExpiresIn() ==
0) {
if ($dir[strlen($dir) -
1] !=
DIRECTORY_SEPARATOR) {
$dir .=
DIRECTORY_SEPARATOR;
if (!in_array($item, array('.', '..'))) {
} else if (is_file($dir .
$item)) {
// Couldn't open directory.
foreach (range(0, 4) as $i) {
$name =
tempnam($dir, "php_openid_filestore_");
foreach (range(0, 4) as $i) {
if (!mkdir($name, 0700)) {
while (false !==
($filename =
readdir($handle))) {
function _isFilenameSafe($char)
return (strpos($_Auth_OpenID_filename_allowed, $char) !==
false);
function _filenameEscape($str)
for ($i =
0; $i <
strlen($str); $i++
) {
* Attempt to remove a file, returning whether the file existed at
* @return bool $result True if the file was present, false if not.
function _removeIfPresent($filename)