class OpenID::Store::Filesystem

Public Class Methods

new(directory) click to toggle source

Create a Filesystem store instance, putting all data in directory.

# File lib/openid/store/filesystem.rb, line 15
def initialize(directory)
  @nonce_dir = File.join(directory, 'nonces')
  @association_dir = File.join(directory, 'associations')
  @temp_dir = File.join(directory, 'temp')

  self.ensure_dir(@nonce_dir)
  self.ensure_dir(@association_dir)
  self.ensure_dir(@temp_dir)
end

Public Instance Methods

_get_association(filename) click to toggle source
# File lib/openid/store/filesystem.rb, line 99
def _get_association(filename)
  begin
    assoc_file = File.open(filename, "r")
  rescue Errno::ENOENT
    return nil
  else
    begin
      assoc_s = assoc_file.read
    ensure
      assoc_file.close
    end

    begin
      association = Association.deserialize(assoc_s)
    rescue
      self.remove_if_present(filename)
      return nil
    end

    # clean up expired associations
    if association.expires_in == 0
      self.remove_if_present(filename)
      return nil
    else
      return association
    end
  end
end
cleanup() click to toggle source

Remove expired entries from the database. This is potentially expensive, so only run when it is acceptable to take time.

# File lib/openid/store/filesystem.rb, line 170
def cleanup
  cleanup_associations
  cleanup_nonces
end
cleanup_associations() click to toggle source
# File lib/openid/store/filesystem.rb, line 175
def cleanup_associations
  association_filenames = Dir[File.join(@association_dir, "*")]
  count = 0
  association_filenames.each do |af|
    begin
      f = File.open(af, 'r')
    rescue Errno::ENOENT
      next
    else
      begin
        assoc_s = f.read
      ensure
        f.close
      end
      begin
        association = OpenID::Association.deserialize(assoc_s)
      rescue StandardError
        self.remove_if_present(af)
        next
      else
        if association.expires_in == 0
          self.remove_if_present(af)
          count += 1
        end
      end
    end
  end
  return count
end
cleanup_nonces() click to toggle source
# File lib/openid/store/filesystem.rb, line 205
def cleanup_nonces
  nonces = Dir[File.join(@nonce_dir, "*")]
  now = Time.now.to_i

  count = 0
  nonces.each do |filename|
    nonce = filename.split('/')[-1]
    timestamp = nonce.split('-', 2)[0].to_i(16)
    nonce_age = (timestamp - now).abs
    if nonce_age > Nonce.skew
      self.remove_if_present(filename)
      count += 1
    end
  end
  return count
end
get_association(server_url, handle=nil) click to toggle source

Retrieve an association

# File lib/openid/store/filesystem.rb, line 79
def get_association(server_url, handle=nil)
  # the filename with empty handle is the prefix for the associations
  # for a given server url
  filename = get_association_filename(server_url, handle)
  if handle
    return _get_association(filename)
  end
  assoc_filenames = Dir.glob(filename.to_s + '*')

  assocs = assoc_filenames.collect do |f|
    _get_association(f)
  end

  assocs = assocs.find_all { |a| not a.nil? }
  assocs = assocs.sort_by { |a| a.issued }

  return nil if assocs.empty?
  return assocs[-1]
end
get_association_filename(server_url, handle) click to toggle source

Create a unique filename for a given server url and handle. The filename that is returned will contain the domain name from the server URL for ease of human inspection of the data dir.

# File lib/openid/store/filesystem.rb, line 28
def get_association_filename(server_url, handle)
  unless server_url.index('://')
    raise ArgumentError, "Bad server URL: #{server_url}"
  end

  proto, rest = server_url.split('://', 2)
  domain = filename_escape(rest.split('/',2)[0])
  url_hash = safe64(server_url)
  if handle
    handle_hash = safe64(handle)
  else
    handle_hash = ''
  end
  filename = [proto,domain,url_hash,handle_hash].join('-')
  File.join(@association_dir, filename)
end
remove_association(server_url, handle) click to toggle source

Remove an association if it exists, otherwise do nothing.

# File lib/openid/store/filesystem.rb, line 129
def remove_association(server_url, handle)
  assoc = get_association(server_url, handle)

  if assoc.nil?
    return false
  else
    filename = get_association_filename(server_url, handle)
    return self.remove_if_present(filename)
  end
end
store_association(server_url, association) click to toggle source

Store an association in the assoc directory

# File lib/openid/store/filesystem.rb, line 46
def store_association(server_url, association)
  assoc_s = association.serialize
  filename = get_association_filename(server_url, association.handle)
  f, tmp = mktemp

  begin
    begin
      f.write(assoc_s)
      f.fsync
    ensure
      f.close
    end

    begin
      File.rename(tmp, filename)
    rescue Errno::EEXIST

      begin
        File.unlink(filename)
      rescue Errno::ENOENT
        # do nothing
      end

      File.rename(tmp, filename)
    end

  rescue
    self.remove_if_present(tmp)
    raise
  end
end
use_nonce(server_url, timestamp, salt) click to toggle source

Return whether the nonce is valid

# File lib/openid/store/filesystem.rb, line 141
def use_nonce(server_url, timestamp, salt)
  return false if (timestamp - Time.now.to_i).abs > Nonce.skew

  if server_url and !server_url.empty?
    proto, rest = server_url.split('://',2)
  else
    proto, rest = '',''
  end
  raise "Bad server URL" unless proto && rest

  domain = filename_escape(rest.split('/',2)[0])
  url_hash = safe64(server_url)
  salt_hash = safe64(salt)

  nonce_fn = '%08x-%s-%s-%s-%s'%[timestamp, proto, domain, url_hash, salt_hash]

  filename = File.join(@nonce_dir, nonce_fn)

  begin
    fd = File.new(filename, File::CREAT | File::EXCL | File::WRONLY, 0200)
    fd.close
    return true
  rescue Errno::EEXIST
    return false
  end
end

Protected Instance Methods

ensure_dir(dir_name) click to toggle source

ensure that a path exists

# File lib/openid/store/filesystem.rb, line 260
def ensure_dir(dir_name)
  FileUtils::mkdir_p(dir_name)
end
filename_escape(s) click to toggle source

create a safe filename from a url

# File lib/openid/store/filesystem.rb, line 231
def filename_escape(s)
  s = '' if s.nil?
  filename_chunks = s.each_char.flat_map {|c|
    @@FILENAME_ALLOWED.include?(c) ? c : c.bytes.map {|b|
      "_%02X" % b
    }
  }.join
end
mktemp() click to toggle source

Create a temporary file and return the File object and filename.

# File lib/openid/store/filesystem.rb, line 225
def mktemp
  f = Tempfile.new('tmp', @temp_dir)
  [f, f.path]
end
remove_if_present(filename) click to toggle source

remove file if present in filesystem

# File lib/openid/store/filesystem.rb, line 250
def remove_if_present(filename)
  begin
    File.unlink(filename)
  rescue Errno::ENOENT
    return false
  end
  return true
end
safe64(s) click to toggle source
# File lib/openid/store/filesystem.rb, line 240
def safe64(s)
  s = OpenID::CryptUtil.sha1(s)
  s = OpenID::Util.to_base64(s)
  s.gsub!('+', '_')
  s.gsub!('/', '.')
  s.gsub!('=', '')
  return s
end