aboutsummaryrefslogtreecommitdiffstats
path: root/actionpack/lib/action_dispatch/middleware/session/abstract_store.rb
blob: b4a7f1ab89b7ccb16a1e44aa4d3db458b8bdce91 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
require 'rack/utils'
require 'rack/request'
require 'rack/session/abstract/id'
require 'action_dispatch/middleware/cookies'
require 'active_support/core_ext/object/blank'

module ActionDispatch
  module Session
    class SessionRestoreError < StandardError #:nodoc:
      attr_reader :original_exception

      def initialize(const_error)
        @original_exception = const_error

        super("Session contains objects whose class definition isn't available.\n" +
          "Remember to require the classes for all objects kept in the session.\n" +
          "(Original exception: #{const_error.message} [#{const_error.class}])\n")
      end
    end

    module Compatibility
      def initialize(app, options = {})
        options[:key] ||= '_session_id'
        super
      end

      def generate_sid
        sid = SecureRandom.hex(16)
        sid.encode!('UTF-8')
        sid
      end

    protected

      def initialize_sid
        @default_options.delete(:sidbits)
        @default_options.delete(:secure_random)
      end
    end

    module StaleSessionCheck
      def load_session(env)
        stale_session_check! { super }
      end

      def extract_session_id(env)
        stale_session_check! { super }
      end

      def stale_session_check!
        yield
      rescue ArgumentError => argument_error
        if argument_error.message =~ %r{undefined class/module ([\w:]*\w)}
          begin
            # Note that the regexp does not allow $1 to end with a ':'
            $1.constantize
          rescue LoadError, NameError => e
            raise ActionDispatch::Session::SessionRestoreError, e, e.backtrace
          end
          retry
        else
          raise
        end
      end
    end

    class AbstractStore < Rack::Session::Abstract::ID
      include Compatibility
      include StaleSessionCheck

      ENV_SESSION_KEY         = Rack::Session::Abstract::ENV_SESSION_KEY # :nodoc:
      ENV_SESSION_OPTIONS_KEY = Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY # :nodoc:

      private

      module DestroyableSession
        def destroy
          clear
          options = @env[Rack::Session::Abstract::ENV_SESSION_OPTIONS_KEY] if @env
          options ||= {}
          @by.send(:destroy_session, @env, options[:id], options) if @by
          options[:id] = nil
          @loaded = false
        end
      end

      ::Rack::Session::Abstract::SessionHash.send :include, DestroyableSession

      def prepare_session(env)
        session_was                  = env[ENV_SESSION_KEY]
        env[ENV_SESSION_KEY]         = Rack::Session::Abstract::SessionHash.new(self, env)
        env[ENV_SESSION_OPTIONS_KEY] = Request::Session::Options.new(self, env, @default_options)
        env[ENV_SESSION_KEY].merge! session_was if session_was
      end

      def set_cookie(env, session_id, cookie)
        request = ActionDispatch::Request.new(env)
        request.cookie_jar[key] = cookie
      end
    end
  end

  class Request
    # SessionHash is responsible to lazily load the session from store.
    class Session
      class Options #:nodoc:
        def initialize(by, env, default_options)
          @by       = by
          @env      = env
          @delegate = default_options
        end

        def [](key)
          if key == :id
            @delegate.fetch(key) {
              @delegate[:id] = @by.send(:extract_session_id, @env)
            }
          else
            @delegate[key]
          end
        end

        def []=(k,v);        @delegate[k] = v; end
        def to_hash;         @delegate.dup; end
        def values_at(*args) @delegate.values_at(*args); end
      end
    end
  end
end