class Rack::Session::File
Rack::Session::File
provides simple filed based session management. By default, the session is stored in /tmp while cookie holds only session id.
When the :secret key is set (recommended), cookie data is checked for data integrity. The :old_secret key is also accepted allowing smooth secret rotation.
Garbage collection is controlled via :gc_probability and :gc_maxlife. Every call to write_session
, garbage collector is called with probability of :gc_probability. It scans :dir for sessions files and deletes ones with mtime older than :gc_maxlife.
Supported options for constructor are:
:dir directory into which save sessions :prefix session file prefix :key under what cookie save the session_id :domain domain should the session_id cookie is valid for :path path the session_id cookie is valid for :expire_after session_id cookie expires after this seconds :secret secret to use for integrity check :old_secret secret previously used, allowing smooth secret rotation :gc_probability probability of gc to run, in interval [0; 1] :gc_maxlife how old (in seconds) session files should be cleaned up
Default values:
:dir File.join(Dir.tmpdir(), 'file-rack') :prefix 'file-rack-session-' :key rack.session :domain nil :path nil :expire_after nil :secret nil :old_secret nil :gc_probability 0.01 :gc_maxlife 1200
Example:
use Rack::Session::File, dir: '/tmp', prefix: 'session-', All parameters are optional.
Constants
- SESSION_ID
Public Class Methods
new(app, options = {})
click to toggle source
Calls superclass method
# File lib/rack/session/file.rb, line 62 def initialize(app, options = {}) @secrets = options.values_at(:secret, :old_secret).compact @hmac = options.fetch(:hmac, OpenSSL::Digest::SHA1) @dir = options[:dir] || ::File.join(Dir.tmpdir(), 'file-rack') @prefix = options[:prefix] || 'file-rack-session-' FileUtils.mkdir_p @dir @gc_probability = options[:gc_probability] || 0.01 @gc_maxlife = options[:gc_maxlife] || 1200 warn <<~MSG unless secure?(options) SECURITY WARNING: No secret option provided to Rack::Session::Cookie. This poses a security threat. It is strongly recommended that you provide a secret to prevent exploits that may be possible from crafted cookies. This will not be supported in future versions of Rack, and future versions will even invalidate your existing user cookies. Called from: #{caller[0]}. MSG super(app, options.merge!(cookie_only: false)) end
Private Instance Methods
delete_session(req, session_id, options)
click to toggle source
# File lib/rack/session/file.rb, line 111 def delete_session(req, session_id, options) begin File.delete(path_for_sid(session_id)) rescue => e warn "Cannot delete session #{session_id}: #{e}" end unless options[:drop] generate_sid else nil end end
digest_match?(data, digest)
click to toggle source
# File lib/rack/session/file.rb, line 153 def digest_match?(data, digest) return unless data && digest @secrets.any? do |secret| Rack::Utils.secure_compare(digest, generate_hmac(data, secret)) end end
find_session(req, sid)
click to toggle source
# File lib/rack/session/file.rb, line 88 def find_session(req, sid) data = load_data(req) data = persistent_session_id(data) [data[SESSION_ID], data] end
generate_hmac(data, secret)
click to toggle source
# File lib/rack/session/file.rb, line 160 def generate_hmac(data, secret) OpenSSL::HMAC.hexdigest(@hmac.new, secret, data) end
load_data(req)
click to toggle source
# File lib/rack/session/file.rb, line 125 def load_data(req) sid = req.cookies[@key] if @secrets.size > 0 and sid sid, digest = sid.split('--', 2) sid = nil unless digest_match?(sid, digest) end if sid store = PStore.new(path_for_sid(sid), read_only: true) store.transaction { store[:session] } else {} end end
path_for_sid(sid)
click to toggle source
# File lib/rack/session/file.rb, line 149 def path_for_sid(sid) ::File.join @dir, "#{@prefix}#{sid}" end
persistent_session_id(data, sid = nil)
click to toggle source
# File lib/rack/session/file.rb, line 138 def persistent_session_id(data, sid = nil) data ||= {} data[SESSION_ID] ||= sid unless data[SESSION_ID] begin data[SESSION_ID] = generate_sid end while ::File.exist? path_for_sid(data[SESSION_ID]) end data end
secure?(options)
click to toggle source
# File lib/rack/session/file.rb, line 164 def secure?(options) @secrets.size >= 1 end
try_gc_run!()
click to toggle source
# File lib/rack/session/file.rb, line 168 def try_gc_run! return unless Random.rand < @gc_probability threshold = Time.now - @gc_maxlife Dir.chdir(@dir) do Dir.entries(@dir).each do |entry| next unless entry[/#{@prefix}/] begin ::File.delete(entry) if ::File.mtime(entry) < threshold rescue => e warn "Cannot delete session file #{entry}: #{e}" end end end end
write_session(req, session_id, session, options)
click to toggle source
# File lib/rack/session/file.rb, line 94 def write_session(req, session_id, session, options) if options[:renew] session[SESSION_ID] = generate_sid end store = PStore.new(path_for_sid(session_id)) store.transaction { store[:session] = session } try_gc_run! if @secrets.first "#{session_id}--#{generate_hmac(session_id, @secrets.first)}" else session_id end end