Froze rails gems
[depot.git] / vendor / rails / actionpack / lib / action_controller / session / cookie_store.rb
1 require 'cgi'
2 require 'cgi/session'
3 require 'openssl' # to generate the HMAC message digest
4
5 # This cookie-based session store is the Rails default. Sessions typically
6 # contain at most a user_id and flash message; both fit within the 4K cookie
7 # size limit. Cookie-based sessions are dramatically faster than the
8 # alternatives.
9 #
10 # If you have more than 4K of session data or don't want your data to be
11 # visible to the user, pick another session store.
12 #
13 # CookieOverflow is raised if you attempt to store more than 4K of data.
14 # TamperedWithCookie is raised if the data integrity check fails.
15 #
16 # A message digest is included with the cookie to ensure data integrity:
17 # a user cannot alter his +user_id+ without knowing the secret key included in
18 # the hash. New apps are generated with a pregenerated secret in
19 # config/environment.rb. Set your own for old apps you're upgrading.
20 #
21 # Session options:
22 #
23 # * <tt>:secret</tt>: An application-wide key string or block returning a string
24 # called per generated digest. The block is called with the CGI::Session
25 # instance as an argument. It's important that the secret is not vulnerable to
26 # a dictionary attack. Therefore, you should choose a secret consisting of
27 # random numbers and letters and more than 30 characters. Examples:
28 #
29 # :secret => '449fe2e7daee471bffae2fd8dc02313d'
30 # :secret => Proc.new { User.current_user.secret_key }
31 #
32 # * <tt>:digest</tt>: The message digest algorithm used to verify session
33 # integrity defaults to 'SHA1' but may be any digest provided by OpenSSL,
34 # such as 'MD5', 'RIPEMD160', 'SHA256', etc.
35 #
36 # To generate a secret key for an existing application, run
37 # "rake secret" and set the key in config/environment.rb.
38 #
39 # Note that changing digest or secret invalidates all existing sessions!
40 class CGI::Session::CookieStore
41 # Cookies can typically store 4096 bytes.
42 MAX = 4096
43 SECRET_MIN_LENGTH = 30 # characters
44
45 # Raised when storing more than 4K of session data.
46 class CookieOverflow < StandardError; end
47
48 # Raised when the cookie fails its integrity check.
49 class TamperedWithCookie < StandardError; end
50
51 # Called from CGI::Session only.
52 def initialize(session, options = {})
53 # The session_key option is required.
54 if options['session_key'].blank?
55 raise ArgumentError, 'A session_key is required to write a cookie containing the session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase" } in config/environment.rb'
56 end
57
58 # The secret option is required.
59 ensure_secret_secure(options['secret'])
60
61 # Keep the session and its secret on hand so we can read and write cookies.
62 @session, @secret = session, options['secret']
63
64 # Message digest defaults to SHA1.
65 @digest = options['digest'] || 'SHA1'
66
67 # Default cookie options derived from session settings.
68 @cookie_options = {
69 'name' => options['session_key'],
70 'path' => options['session_path'],
71 'domain' => options['session_domain'],
72 'expires' => options['session_expires'],
73 'secure' => options['session_secure'],
74 'http_only' => options['session_http_only']
75 }
76
77 # Set no_hidden and no_cookies since the session id is unused and we
78 # set our own data cookie.
79 options['no_hidden'] = true
80 options['no_cookies'] = true
81 end
82
83 # To prevent users from using something insecure like "Password" we make sure that the
84 # secret they've provided is at least 30 characters in length.
85 def ensure_secret_secure(secret)
86 # There's no way we can do this check if they've provided a proc for the
87 # secret.
88 return true if secret.is_a?(Proc)
89
90 if secret.blank?
91 raise ArgumentError, %Q{A secret is required to generate an integrity hash for cookie session data. Use config.action_controller.session = { :session_key => "_myapp_session", :secret => "some secret phrase of at least #{SECRET_MIN_LENGTH} characters" } in config/environment.rb}
92 end
93
94 if secret.length < SECRET_MIN_LENGTH
95 raise ArgumentError, %Q{Secret should be something secure, like "#{CGI::Session.generate_unique_id}". The value you provided, "#{secret}", is shorter than the minimum length of #{SECRET_MIN_LENGTH} characters}
96 end
97 end
98
99 # Restore session data from the cookie.
100 def restore
101 @original = read_cookie
102 @data = unmarshal(@original) || {}
103 end
104
105 # Wait until close to write the session data cookie.
106 def update; end
107
108 # Write the session data cookie if it was loaded and has changed.
109 def close
110 if defined?(@data) && !@data.blank?
111 updated = marshal(@data)
112 raise CookieOverflow if updated.size > MAX
113 write_cookie('value' => updated) unless updated == @original
114 end
115 end
116
117 # Delete the session data by setting an expired cookie with no data.
118 def delete
119 @data = nil
120 clear_old_cookie_value
121 write_cookie('value' => nil, 'expires' => 1.year.ago)
122 end
123
124 # Generate the HMAC keyed message digest. Uses SHA1 by default.
125 def generate_digest(data)
126 key = @secret.respond_to?(:call) ? @secret.call(@session) : @secret
127 OpenSSL::HMAC.hexdigest(OpenSSL::Digest::Digest.new(@digest), key, data)
128 end
129
130 private
131 # Marshal a session hash into safe cookie data. Include an integrity hash.
132 def marshal(session)
133 data = ActiveSupport::Base64.encode64s(Marshal.dump(session))
134 "#{data}--#{generate_digest(data)}"
135 end
136
137 # Unmarshal cookie data to a hash and verify its integrity.
138 def unmarshal(cookie)
139 if cookie
140 data, digest = cookie.split('--')
141
142 # Do two checks to transparently support old double-escaped data.
143 unless digest == generate_digest(data) || digest == generate_digest(data = CGI.unescape(data))
144 delete
145 raise TamperedWithCookie
146 end
147
148 Marshal.load(ActiveSupport::Base64.decode64(data))
149 end
150 end
151
152 # Read the session data cookie.
153 def read_cookie
154 @session.cgi.cookies[@cookie_options['name']].first
155 end
156
157 # CGI likes to make you hack.
158 def write_cookie(options)
159 cookie = CGI::Cookie.new(@cookie_options.merge(options))
160 @session.cgi.send :instance_variable_set, '@output_cookies', [cookie]
161 end
162
163 # Clear cookie value so subsequent new_session doesn't reload old data.
164 def clear_old_cookie_value
165 @session.cgi.cookies[@cookie_options['name']].clear
166 end
167 end