Last active
November 5, 2021 21:41
-
-
Save envygeeks/8246199 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
require 'action_dispatch/middleware/session/abstract_store' | |
require 'rack/session/abstract/id' | |
module ActionDispatch | |
module Session | |
class RedisStore < Rack::Session::Abstract::ID | |
include StaleSessionCheck | |
include Compatibility | |
attr_reader :server | |
attr_reader :expire_after | |
attr_reader :key | |
class SessionError < StandardError | |
def initialize(e = nil) | |
Rails.logger.error(e) if e | |
super("There was an issue saving or loading the session.") | |
end | |
end | |
def initialize(app, server:, redis_key:, expire_after: 1.hour, key: '_sid', **kwd) | |
@server = parse_server(server) | |
@redis_key = parse_redis_key(redis_key) | |
@expire_after = expire_after || 1.hour | |
@key = key || "_sid" | |
super(app, **kwd, | |
key: key | |
) | |
end | |
def redis | |
@redis ||= Redis.new( | |
@server | |
) | |
end | |
def redis_key(sid = nil) | |
sid ? "#{@redis_key}:#{sid}" : @redis_key | |
end | |
def get_session(env, sid) | |
rsid = redis_key(sid) | |
if sid && sid != rsid && redis.exists(rsid) | |
begin | |
session = \ | |
YAML.load(redis.get(rsid)) | |
if ! session.is_a?(Hash) | |
raise SessionError | |
end | |
rescue => e | |
Rails.logger.error(e) | |
redis.del(rsid) | |
session, sid = {}, generate_sid | |
end | |
else | |
sid, session = generate_sid, {} | |
end | |
unless session["session_id"] | |
session.merge!({ | |
"session_id" => sid | |
}) | |
end | |
[ | |
sid, | |
session | |
] | |
end | |
# --------------------------------------------------------------- | |
# Set the session inside of Redis and pass it out. | |
# --------------------------------------------------------------- | |
def set_session(env, sid, session, opts) | |
unless session["session_id"] && session["session_id"] == sid | |
session["session_id"] = sid | |
end | |
serialized_data = YAML.dump(session) | |
rsid = redis_key(sid) | |
if redis_key == rsid || ! \ | |
redis.set(rsid, serialized_data) == "OK" | |
raise SessionError | |
else | |
#2: The double tap. | |
redis.expire(rsid, @expire_after) | |
end | |
sid | |
end | |
# --------------------------------------------------------------- | |
# Drop the session from Redis and pass a new sid. | |
# --------------------------------------------------------------- | |
def destroy_session(env, sid, opts) | |
redis.del(sid) | |
unless opts[:drop] | |
generate_sid | |
end | |
end | |
# --------------------------------------------------------------- | |
# Pull the session id from the cookie: This is where we differ a | |
# lot from the way that Rails and Rack handle cookies, because | |
# the cookie is the session ID we simply pull the session ID | |
# straight from the cookie jar and send it into `#get_session` | |
# --------------------------------------------------------------- | |
def load_session(env) | |
get_session(env, cookie_jar(env)[@key]) | |
end | |
# --------------------------------------------------------------- | |
def set_cookie(env, sid, cookie) | |
cookie_jar(env)[@key] = \ | |
cookie | |
end | |
# --------------------------------------------------------------- | |
# The most important piece, we need to sign our cookies. | |
# --------------------------------------------------------------- | |
def cookie_jar(env) | |
ActionDispatch::Request.new(env).cookie_jar.signed | |
end | |
# --------------------------------------------------------------- | |
# Ensures all the server options are properly set for the | |
# user so you can either pass just a few if you need or all if | |
# you really want to pass them all. | |
# --------------------------------------------------------------- | |
protected | |
def parse_server(srv = nil) | |
if ! srv.is_a?(Hash) | |
srv = {} | |
end | |
srv[:database] ||= 0 | |
srv[:namespace] ||= :session | |
srv[:host] ||= "127.0.0.1" | |
srv[:port] ||= 6379 | |
srv | |
end | |
# --------------------------------------------------------------- | |
protected | |
def parse_redis_key(rkey = nil) | |
(rkey || "rails:session").chomp(":") | |
end | |
end | |
end | |
end |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# --------------------------------------------------------------------- | |
# Copyright 2013 Jordon Bedwell. | |
# --------------------------------------------------------------------- | |
require "rspec/helper" | |
describe ActionDispatch::Session::RedisStore, :type => :request do | |
before do | |
@request_forgery_orig = ActionController::Base.allow_forgery_protection | |
ActionController::Base.allow_forgery_protection = true | |
stub_request(:get, /.*/).to_return({ | |
:body => "" | |
}) | |
end | |
after do | |
ActionController::Base.allow_forgery_protection = \ | |
@request_forgery_orig | |
end | |
before :each do | |
get "/" | |
end | |
describe "opts (through #initialize)" do | |
it "allows :expire_after" do | |
subj = described_class.new({}, { | |
:expire_after => 3600 | |
}) | |
expect(subj.expire_after).to eq 3600 | |
expect(subj.default_options[:expire_after]).to eq 3600 | |
end | |
it "allows :redis_key" do | |
subj = described_class.new({}, { | |
:redis_key => "foo" | |
}) | |
expect(subj.redis_key).to eq "foo" | |
end | |
[:namespace, :host, :port, :database].each do |k| | |
it "allows server[:#{k}] opt" do | |
subj = described_class.new({}, :server => { | |
k => "foo" | |
}) | |
expect(subj.server[k]).to eq "foo" | |
expect(subj.server).to be_instance_of Hash | |
end | |
end | |
it "allows :key (for the cookie)" do | |
subj = described_class.new({}, { | |
:key => "foo" | |
}) | |
expect(subj.key).to eq "foo" | |
end | |
end | |
describe "#redis_key" do | |
it "appends if an sid is provided" do | |
subj = described_class.new({}) | |
expect(subj.redis_key("foo")).to end_with ":foo" | |
end | |
end | |
describe "#get_session" do | |
context "on bad deserialize" do | |
before :each do | |
subject.redis.set(subject.redis_key("foo"), "foo") | |
end | |
subject do | |
described_class.new({}) | |
end | |
it "resets the session" do | |
expect(subject.get_session( \ | |
request.env, "foo").first).not_to eq "foo" | |
end | |
it "logs" do | |
expect(Rails.logger).to receive(:error).and_call_original | |
subject.get_session(request.env, "foo") | |
end | |
it "removes the redis key" do | |
subject.get_session(request.env, "foo") | |
expect(subject.redis.keys \ | |
).not_to include subject.redis_key("foo") | |
end | |
end | |
context "on bad sid" do | |
it "resets" do | |
subj = described_class.new({ }) | |
expect(subj.get_session(request.env, "foo").first).not_to eq "foo" | |
end | |
end | |
it "adds session_id to the session hash" do | |
get "/" | |
expect(request.session).to have_key "session_id" | |
end | |
end | |
describe "#set_session" do | |
it "saves" do | |
subj = described_class.new({}) | |
rtrn = subj.get_session(request.env, request.session["session_id"]) | |
expect(rtrn.last).to eq request.session.to_hash | |
end | |
it "adds session_id" do | |
subj, session = described_class.new({}), {} | |
expect(session).to receive(:[]).with("session_id").and_call_original | |
subj.set_session(request.env, "foo", session, {}) | |
end | |
context "on bad sid" do | |
it "raises" do | |
subj = described_class.new({}) | |
expect_error described_class::SessionError do | |
subj.set_session(request.env, nil, {}, {}) | |
end | |
end | |
end | |
end | |
describe "#cookie_jar" do | |
it "uses a signed cookie" do | |
subj, expected = described_class.new({}), [ | |
ActionDispatch::Cookies::SignedCookieJar, | |
ActionDispatch::Cookies::UpgradeLegacySignedCookieJar | |
] | |
expect(expected).to include subj.cookie_jar(request.env).class | |
end | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment