merb-core 0.9.5 → 0.9.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/CHANGELOG +925 -0
- data/CONTRIBUTORS +93 -0
- data/PUBLIC_CHANGELOG +85 -0
- data/Rakefile +18 -28
- data/bin/merb +34 -5
- data/lib/merb-core/autoload.rb +2 -3
- data/lib/merb-core/bootloader.rb +60 -66
- data/lib/merb-core/config.rb +7 -1
- data/lib/merb-core/controller/abstract_controller.rb +35 -21
- data/lib/merb-core/controller/merb_controller.rb +15 -42
- data/lib/merb-core/controller/mixins/authentication.rb +42 -6
- data/lib/merb-core/controller/mixins/conditional_get.rb +83 -0
- data/lib/merb-core/controller/mixins/render.rb +3 -3
- data/lib/merb-core/core_ext/kernel.rb +6 -19
- data/lib/merb-core/dispatch/cookies.rb +96 -80
- data/lib/merb-core/dispatch/default_exception/views/index.html.erb +2 -0
- data/lib/merb-core/dispatch/request.rb +18 -16
- data/lib/merb-core/dispatch/router/route.rb +6 -0
- data/lib/merb-core/dispatch/router.rb +4 -1
- data/lib/merb-core/dispatch/session/container.rb +64 -0
- data/lib/merb-core/dispatch/session/cookie.rb +91 -101
- data/lib/merb-core/dispatch/session/memcached.rb +38 -174
- data/lib/merb-core/dispatch/session/memory.rb +62 -208
- data/lib/merb-core/dispatch/session/store_container.rb +145 -0
- data/lib/merb-core/dispatch/session.rb +174 -48
- data/lib/merb-core/rack/middleware/conditional_get.rb +14 -8
- data/lib/merb-core/rack/middleware/csrf.rb +73 -0
- data/lib/merb-core/rack.rb +1 -0
- data/lib/merb-core/script.rb +112 -0
- data/lib/merb-core/server.rb +2 -0
- data/lib/merb-core/tasks/merb_rake_helper.rb +25 -0
- data/lib/merb-core/test/helpers/request_helper.rb +40 -3
- data/lib/merb-core/test/run_specs.rb +4 -3
- data/lib/merb-core/vendor/facets/inflect.rb +7 -10
- data/lib/merb-core/version.rb +1 -1
- data/lib/merb-core.rb +11 -40
- data/spec/private/core_ext/kernel_spec.rb +0 -11
- data/spec/private/dispatch/fixture/log/merb_test.log +893 -0
- data/spec/private/router/fixture/log/merb_test.log +12 -1728
- data/spec/private/router/route_spec.rb +4 -0
- data/spec/private/router/router_spec.rb +8 -0
- data/spec/private/vendor/facets/plural_spec.rb +1 -1
- data/spec/private/vendor/facets/singular_spec.rb +1 -1
- data/spec/public/abstract_controller/controllers/display.rb +8 -2
- data/spec/public/abstract_controller/controllers/filters.rb +18 -0
- data/spec/public/abstract_controller/display_spec.rb +6 -2
- data/spec/public/abstract_controller/filter_spec.rb +4 -0
- data/spec/public/controller/authentication_spec.rb +114 -43
- data/spec/public/controller/base_spec.rb +8 -0
- data/spec/public/controller/conditional_get_spec.rb +100 -0
- data/spec/public/controller/config/init.rb +1 -1
- data/spec/public/controller/controllers/authentication.rb +29 -0
- data/spec/public/controller/controllers/base.rb +13 -0
- data/spec/public/controller/controllers/conditional_get.rb +35 -0
- data/spec/public/controller/controllers/cookies.rb +10 -1
- data/spec/public/controller/cookies_spec.rb +38 -9
- data/spec/public/controller/spec_helper.rb +1 -0
- data/spec/public/controller/url_spec.rb +70 -1
- data/spec/public/directory_structure/directory/log/merb_test.log +461 -0
- data/spec/public/rack/conditinal_get_middleware_spec.rb +77 -89
- data/spec/public/rack/csrf_middleware_spec.rb +70 -0
- data/spec/public/reloading/directory/log/merb_test.log +52 -0
- data/spec/public/request/request_spec.rb +19 -1
- data/spec/public/router/fixation_spec.rb +26 -4
- data/spec/public/router/fixture/log/merb_test.log +234 -30332
- data/spec/public/session/controllers/sessions.rb +52 -0
- data/spec/public/session/cookie_session_spec.rb +73 -0
- data/spec/public/session/memcached_session_spec.rb +31 -0
- data/spec/public/session/memory_session_spec.rb +28 -0
- data/spec/public/session/multiple_sessions_spec.rb +74 -0
- data/spec/public/session/no_session_spec.rb +12 -0
- data/spec/public/session/session_spec.rb +91 -0
- data/spec/public/test/controllers/spec_helper_controller.rb +2 -1
- data/spec/public/test/request_helper_spec.rb +15 -0
- data/spec/spec_helper.rb +2 -2
- metadata +23 -5
- data/spec/private/dispatch/cookies_spec.rb +0 -219
- data/spec/private/dispatch/session_mixin_spec.rb +0 -47
@@ -1,62 +1,48 @@
|
|
1
1
|
module Merb
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
# ==== Parameters
|
11
|
-
# request_cookies<Hash>:: Initial cookie store.
|
12
|
-
# headers<Hash>:: The response headers.
|
13
|
-
def initialize(request_cookies, headers)
|
14
|
-
@_cookies = request_cookies
|
15
|
-
@_headers = headers
|
3
|
+
class Cookies < Mash
|
4
|
+
|
5
|
+
def initialize(constructor = {}, cookie_defaults = {})
|
6
|
+
@_options_lookup = Mash.new
|
7
|
+
@_cookie_defaults = cookie_defaults
|
8
|
+
super constructor
|
16
9
|
end
|
17
|
-
|
10
|
+
|
11
|
+
# Implicit assignment of cookie key and value.
|
12
|
+
#
|
18
13
|
# ==== Parameters
|
19
14
|
# name<~to_s>:: Name of the cookie.
|
15
|
+
# value<~to_s>:: Value of the cookie.
|
20
16
|
#
|
21
|
-
# ====
|
22
|
-
#
|
23
|
-
|
24
|
-
|
17
|
+
# ==== Notes
|
18
|
+
# By using this method, a cookie key is marked for being
|
19
|
+
# included in the Set-Cookie response header.
|
20
|
+
def []=(key, value)
|
21
|
+
@_options_lookup[key] ||= {}
|
22
|
+
super
|
25
23
|
end
|
26
|
-
|
24
|
+
|
25
|
+
# Explicit assignment of cookie key, value and options
|
26
|
+
#
|
27
27
|
# ==== Parameters
|
28
28
|
# name<~to_s>:: Name of the cookie.
|
29
|
-
#
|
29
|
+
# value<~to_s>:: Value of the cookie.
|
30
|
+
# options<Hash>:: Additional options for the cookie (see below).
|
30
31
|
#
|
31
32
|
# ==== Options (options)
|
32
|
-
# :value<~to_s>:: Value of the cookie
|
33
33
|
# :path<String>:: The path for which this cookie applies. Defaults to "/".
|
34
34
|
# :expires<Time>:: Cookie expiry date.
|
35
35
|
# :domain<String>:: The domain for which this cookie applies.
|
36
36
|
# :secure<Boolean>:: Security flag.
|
37
37
|
#
|
38
|
-
# ====
|
39
|
-
#
|
40
|
-
#
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
# # => Will set a cookie that expires in 2 weeks
|
45
|
-
def []=(name, options)
|
46
|
-
value = ''
|
47
|
-
if options.is_a?(Hash)
|
48
|
-
options = Mash.new(options)
|
49
|
-
value = options.delete(:value)
|
50
|
-
else
|
51
|
-
value = options
|
52
|
-
options = Mash.new
|
53
|
-
end
|
54
|
-
@_cookies[name] = value
|
55
|
-
set_cookie(name, Merb::Request.escape(value), options)
|
56
|
-
Merb.logger.info("Cookie set: #{name} => #{value} -- #{options.inspect}")
|
57
|
-
options
|
38
|
+
# ==== Notes
|
39
|
+
# By using this method, a cookie key is marked for being
|
40
|
+
# included in the Set-Cookie response header.
|
41
|
+
def set_cookie(name, value, options = {})
|
42
|
+
@_options_lookup[name] = options
|
43
|
+
self[name] = value
|
58
44
|
end
|
59
|
-
|
45
|
+
|
60
46
|
# Removes the cookie on the client machine by setting the value to an empty
|
61
47
|
# string and setting its expiration date into the past.
|
62
48
|
#
|
@@ -64,50 +50,80 @@ module Merb
|
|
64
50
|
# name<~to_s>:: Name of the cookie to delete.
|
65
51
|
# options<Hash>:: Additional options to pass to +set_cookie+.
|
66
52
|
def delete(name, options = {})
|
67
|
-
|
68
|
-
options = Mash.new(options)
|
69
|
-
options[:expires] = Time.at(0)
|
70
|
-
|
71
|
-
if domain = options[:domain] || Merb::Controller._session_cookie_domain
|
72
|
-
options[:domain] = domain
|
73
|
-
end
|
74
|
-
|
75
|
-
set_cookie(name, "", options)
|
76
|
-
Merb.logger.info("Cookie deleted: #{name} => #{cookie.inspect}")
|
77
|
-
cookie
|
53
|
+
set_cookie(name, "", options.merge(:expires => Time.at(0)))
|
78
54
|
end
|
79
55
|
|
80
|
-
#
|
81
|
-
# name<~to_s>:: Name of the cookie.
|
82
|
-
# value<~to_s>:: Value of the cookie.
|
83
|
-
# options<Hash>:: Additional options for the cookie (see below).
|
56
|
+
# Generate any necessary headers.
|
84
57
|
#
|
85
|
-
# ====
|
86
|
-
#
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
options
|
58
|
+
# ==== Returns
|
59
|
+
# Hash:: The headers to set, or an empty array if no cookies are set.
|
60
|
+
def extract_headers(controller_defaults = {})
|
61
|
+
defaults = @_cookie_defaults.merge(controller_defaults)
|
62
|
+
cookies = []
|
63
|
+
self.each do |name, value|
|
64
|
+
# Only set cookies that marked for inclusion in the response header.
|
65
|
+
next unless @_options_lookup[name]
|
66
|
+
options = defaults.merge(@_options_lookup[name])
|
67
|
+
if (expiry = options[:expires]).respond_to?(:gmtime)
|
68
|
+
options[:expires] = expiry.gmtime.strftime(Merb::Const::COOKIE_EXPIRATION_FORMAT)
|
69
|
+
end
|
70
|
+
secure = options.delete(:secure)
|
71
|
+
kookie = "#{name}=#{Merb::Request.escape(value)}; "
|
72
|
+
options.each { |k, v| kookie << "#{k}=#{v}; " }
|
73
|
+
kookie << 'secure' if secure
|
74
|
+
cookies << kookie.rstrip
|
94
75
|
end
|
76
|
+
cookies.empty? ? {} : { 'Set-Cookie' => cookies }
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
module CookiesMixin
|
82
|
+
|
83
|
+
def self.included(base)
|
84
|
+
# Allow per-controller default cookie domains (see callback below)
|
85
|
+
base.class_inheritable_accessor :_default_cookie_domain
|
86
|
+
base._default_cookie_domain = Merb::Config[:default_cookie_domain]
|
95
87
|
|
96
|
-
|
97
|
-
|
88
|
+
# Add a callback to enable Set-Cookie headers
|
89
|
+
base._after_dispatch_callbacks << lambda do |c|
|
90
|
+
headers = c.request.cookies.extract_headers(:domain => c._default_cookie_domain)
|
91
|
+
c.headers.update(headers)
|
98
92
|
end
|
99
|
-
|
100
|
-
secure = options.delete(:secure)
|
101
|
-
|
102
|
-
@_headers['Set-Cookie'] ||=[]
|
103
|
-
|
104
|
-
kookie = "#{name}=#{value}; "
|
105
|
-
# options are sorted for testing purposes:
|
106
|
-
# Hash is unsorted so string is spec is random every run
|
107
|
-
kookie << options.map{|k, v| "#{k}=#{v};"}.join(' ')
|
108
|
-
kookie << ' secure' if secure
|
109
|
-
|
110
|
-
@_headers['Set-Cookie'] << kookie
|
111
93
|
end
|
94
|
+
|
95
|
+
# ==== Returns
|
96
|
+
# Merb::Cookies::
|
97
|
+
# A new Merb::Cookies instance representing the cookies that came in
|
98
|
+
# from the request object
|
99
|
+
#
|
100
|
+
# ==== Notes
|
101
|
+
# Headers are passed into the cookie object so that you can do:
|
102
|
+
# cookies[:foo] = "bar"
|
103
|
+
def cookies
|
104
|
+
request.cookies
|
105
|
+
end
|
106
|
+
|
107
|
+
module RequestMixin
|
108
|
+
|
109
|
+
# ==== Returns
|
110
|
+
# Hash:: The cookies for this request.
|
111
|
+
#
|
112
|
+
# ==== Notes
|
113
|
+
# If a method #default_cookies is defined it will be called. This can
|
114
|
+
# be used for session fixation purposes for example. The method returns
|
115
|
+
# a Hash of key => value pairs.
|
116
|
+
def cookies
|
117
|
+
@cookies ||= begin
|
118
|
+
values = self.class.query_parse(@env[Merb::Const::HTTP_COOKIE], ';,')
|
119
|
+
cookies = Merb::Cookies.new(values, :domain => Merb::Controller._default_cookie_domain, :path => '/')
|
120
|
+
cookies.update(default_cookies) if respond_to?(:default_cookies)
|
121
|
+
cookies
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
112
127
|
end
|
128
|
+
|
113
129
|
end
|
@@ -28,8 +28,10 @@
|
|
28
28
|
<h3>Parameters</h3>
|
29
29
|
<%= listing("Parameter", "Value", request.params) %>
|
30
30
|
|
31
|
+
<% if request.session? %>
|
31
32
|
<h3>Session</h3>
|
32
33
|
<%= listing("Key", "Value", request.session) %>
|
34
|
+
<% end %>
|
33
35
|
|
34
36
|
<h3>Cookies</h3>
|
35
37
|
<%= listing("Cookie", "Value", request.cookies) %>
|
@@ -3,8 +3,8 @@ require 'tempfile'
|
|
3
3
|
module Merb
|
4
4
|
|
5
5
|
class Request
|
6
|
-
# def env def
|
7
|
-
attr_accessor :env, :
|
6
|
+
# def env def exceptions def route_params
|
7
|
+
attr_accessor :env, :exceptions, :route
|
8
8
|
attr_reader :route_params
|
9
9
|
|
10
10
|
# by setting these to false, auto-parsing is disabled; this way you can
|
@@ -207,6 +207,7 @@ module Merb
|
|
207
207
|
end
|
208
208
|
|
209
209
|
public
|
210
|
+
|
210
211
|
# ==== Returns
|
211
212
|
# Mash:: All request parameters.
|
212
213
|
#
|
@@ -236,20 +237,7 @@ module Merb
|
|
236
237
|
def reset_params!
|
237
238
|
@params = nil
|
238
239
|
end
|
239
|
-
|
240
|
-
# ==== Returns
|
241
|
-
# Hash:: The cookies for this request.
|
242
|
-
def cookies
|
243
|
-
@cookies ||= begin
|
244
|
-
cookies = self.class.query_parse(@env[Merb::Const::HTTP_COOKIE], ';,')
|
245
|
-
if route && route.allow_fixation? && params.key?(Merb::Controller._session_id_key)
|
246
|
-
Merb.logger.info("Fixated session id: #{Merb::Controller._session_id_key}")
|
247
|
-
cookies[Merb::Controller._session_id_key] = params[Merb::Controller._session_id_key]
|
248
|
-
end
|
249
|
-
cookies
|
250
|
-
end
|
251
|
-
end
|
252
|
-
|
240
|
+
|
253
241
|
# ==== Returns
|
254
242
|
# String:: The raw post.
|
255
243
|
def raw_post
|
@@ -459,6 +447,20 @@ module Merb
|
|
459
447
|
def domain(tld_length = 1)
|
460
448
|
host.split('.').last(1 + tld_length).join('.').sub(/:\d+$/,'')
|
461
449
|
end
|
450
|
+
|
451
|
+
# ==== Returns
|
452
|
+
# Value of If-None-Match request header.
|
453
|
+
def if_none_match
|
454
|
+
@env[Merb::Const::HTTP_IF_NONE_MATCH]
|
455
|
+
end
|
456
|
+
|
457
|
+
# ==== Returns
|
458
|
+
# Value of If-Modified-Since request header.
|
459
|
+
def if_modified_since
|
460
|
+
if time = @env[Merb::Const::HTTP_IF_MODIFIED_SINCE]
|
461
|
+
Time.rfc2822(time)
|
462
|
+
end
|
463
|
+
end
|
462
464
|
|
463
465
|
class << self
|
464
466
|
|
@@ -258,9 +258,15 @@ module Merb
|
|
258
258
|
format = fallback[:format] if format == :current
|
259
259
|
url += ".#{format}"
|
260
260
|
end
|
261
|
+
if query_params
|
262
|
+
fragment = query_params.delete(:fragment)
|
263
|
+
end
|
261
264
|
if query_params && !query_params.empty?
|
262
265
|
url += "?" + Merb::Request.params_to_query_string(query_params)
|
263
266
|
end
|
267
|
+
if fragment
|
268
|
+
url += "##{fragment}"
|
269
|
+
end
|
264
270
|
url
|
265
271
|
end
|
266
272
|
|
@@ -169,7 +169,7 @@ module Merb
|
|
169
169
|
# String:: The generated URL.
|
170
170
|
def generate_for_default_route(params, fallback)
|
171
171
|
query_params = params.reject do |k,v|
|
172
|
-
[:controller, :action, :id, :format].include?(k.to_sym)
|
172
|
+
[:controller, :action, :id, :format, :fragment].include?(k.to_sym)
|
173
173
|
end
|
174
174
|
|
175
175
|
controller = params[:controller] || fallback[:controller]
|
@@ -191,6 +191,9 @@ module Merb
|
|
191
191
|
unless query_params.empty?
|
192
192
|
url += "?" + Merb::Request.params_to_query_string(query_params)
|
193
193
|
end
|
194
|
+
if params[:fragment]
|
195
|
+
url += "##{params[:fragment]}"
|
196
|
+
end
|
194
197
|
url
|
195
198
|
end
|
196
199
|
end # self
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Merb
|
2
|
+
class SessionContainer < Mash
|
3
|
+
|
4
|
+
class_inheritable_accessor :session_store_type
|
5
|
+
cattr_accessor :subclasses
|
6
|
+
self.subclasses = []
|
7
|
+
|
8
|
+
attr_reader :session_id
|
9
|
+
attr_accessor :needs_new_cookie
|
10
|
+
|
11
|
+
class << self
|
12
|
+
|
13
|
+
# Register the subclass as an available session store type.
|
14
|
+
def inherited(klass)
|
15
|
+
self.subclasses << klass.to_s
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
# Generates a new session ID and creates a new session.
|
20
|
+
#
|
21
|
+
# ==== Returns
|
22
|
+
# SessionContainer:: The new session.
|
23
|
+
def generate
|
24
|
+
end
|
25
|
+
|
26
|
+
# ==== Parameters
|
27
|
+
# request<Merb::Request>:: The Merb::Request that came in from Rack.
|
28
|
+
#
|
29
|
+
# ==== Returns
|
30
|
+
# SessionContainer:: a SessionContainer. If no sessions were found,
|
31
|
+
# a new SessionContainer will be generated.
|
32
|
+
def setup(request)
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
# ==== Parameters
|
38
|
+
# session_id<String>:: A unique identifier for this session.
|
39
|
+
def initialize(session_id)
|
40
|
+
self.session_id = session_id
|
41
|
+
end
|
42
|
+
|
43
|
+
# Teardown and/or persist the current session.
|
44
|
+
#
|
45
|
+
# ==== Parameters
|
46
|
+
# request<Merb::Request>:: The Merb::Request that came in from Rack.
|
47
|
+
def finalize(request)
|
48
|
+
end
|
49
|
+
|
50
|
+
# Assign a new session_id.
|
51
|
+
#
|
52
|
+
# Recreates the cookie with the default expiration time. Useful during log
|
53
|
+
# in for pushing back the expiration date.
|
54
|
+
def session_id=(sid)
|
55
|
+
self.needs_new_cookie = (@session_id && @session_id != sid)
|
56
|
+
@session_id = sid
|
57
|
+
end
|
58
|
+
|
59
|
+
# Regenerate the Session ID
|
60
|
+
def regenerate
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
64
|
+
end
|
@@ -3,36 +3,6 @@ require 'openssl' # to generate the HMAC message digest
|
|
3
3
|
# Most of this code is taken from bitsweat's implementation in rails
|
4
4
|
module Merb
|
5
5
|
|
6
|
-
module SessionMixin
|
7
|
-
|
8
|
-
# Adds a before and after dispatch hook for setting up the cookie session
|
9
|
-
# store.
|
10
|
-
#
|
11
|
-
# ==== Parameters
|
12
|
-
# base<Class>:: The class to which the SessionMixin is mixed into.
|
13
|
-
def setup_session
|
14
|
-
request.session = Merb::CookieSession.new(cookies[_session_id_key], _session_secret_key)
|
15
|
-
@original_session = request.session.read_cookie
|
16
|
-
end
|
17
|
-
|
18
|
-
# Finalizes the session by storing the session in a cookie, if the session
|
19
|
-
# has changed.
|
20
|
-
def finalize_session
|
21
|
-
new_session = request.session.read_cookie
|
22
|
-
if @original_session != new_session
|
23
|
-
options = {:expires => (Time.now + _session_expiry)}
|
24
|
-
options[:domain] = _session_cookie_domain if _session_cookie_domain
|
25
|
-
cookies.set_cookie(_session_id_key, new_session, options)
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
# ==== Returns
|
30
|
-
# String:: The session store type, i.e. "cookie".
|
31
|
-
def session_store_type
|
32
|
-
"cookie"
|
33
|
-
end
|
34
|
-
end
|
35
|
-
|
36
6
|
# If you have more than 4K of session data or don't want your data to be
|
37
7
|
# visible to the user, pick another session store.
|
38
8
|
#
|
@@ -41,108 +11,112 @@ module Merb
|
|
41
11
|
#
|
42
12
|
# A message digest is included with the cookie to ensure data integrity:
|
43
13
|
# a user cannot alter session data without knowing the secret key included
|
44
|
-
# in the hash.
|
45
|
-
#
|
14
|
+
# in the hash.
|
15
|
+
#
|
46
16
|
# To use Cookie Sessions, set in config/merb.yml
|
47
17
|
# :session_secret_key - your secret digest key
|
48
18
|
# :session_store: cookie
|
49
|
-
class CookieSession
|
19
|
+
class CookieSession < SessionContainer
|
50
20
|
# TODO (maybe):
|
51
21
|
# include request ip address
|
52
22
|
# AES encrypt marshaled data
|
53
|
-
|
23
|
+
|
54
24
|
# Raised when storing more than 4K of session data.
|
55
25
|
class CookieOverflow < StandardError; end
|
56
|
-
|
26
|
+
|
57
27
|
# Raised when the cookie fails its integrity check.
|
58
28
|
class TamperedWithCookie < StandardError; end
|
59
|
-
|
29
|
+
|
60
30
|
# Cookies can typically store 4096 bytes.
|
61
31
|
MAX = 4096
|
62
32
|
DIGEST = OpenSSL::Digest::Digest.new('SHA1') # or MD5, RIPEMD160, SHA256?
|
63
|
-
|
64
|
-
|
33
|
+
|
34
|
+
attr_accessor :_original_session_data
|
35
|
+
|
36
|
+
# The session store type
|
37
|
+
self.session_store_type = :cookie
|
38
|
+
|
39
|
+
class << self
|
40
|
+
# Generates a new session ID and creates a new session.
|
41
|
+
#
|
42
|
+
# ==== Returns
|
43
|
+
# SessionContainer:: The new session.
|
44
|
+
def generate
|
45
|
+
self.new(Merb::SessionMixin.rand_uuid, "", Merb::Request._session_secret_key)
|
46
|
+
end
|
47
|
+
|
48
|
+
# Setup a new session.
|
49
|
+
#
|
50
|
+
# ==== Parameters
|
51
|
+
# request<Merb::Request>:: The Merb::Request that came in from Rack.
|
52
|
+
#
|
53
|
+
# ==== Returns
|
54
|
+
# SessionContainer:: a SessionContainer. If no sessions were found,
|
55
|
+
# a new SessionContainer will be generated.
|
56
|
+
def setup(request)
|
57
|
+
session = self.new(Merb::SessionMixin.rand_uuid,
|
58
|
+
request.session_cookie_value, request._session_secret_key)
|
59
|
+
session._original_session_data = session.to_cookie
|
60
|
+
request.session = session
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
65
64
|
|
66
65
|
# ==== Parameters
|
67
|
-
#
|
66
|
+
# session_id<String>:: A unique identifier for this session.
|
67
|
+
# cookie<String>:: The raw cookie data.
|
68
68
|
# secret<String>:: A session secret.
|
69
69
|
#
|
70
70
|
# ==== Raises
|
71
71
|
# ArgumentError:: Nil or blank secret.
|
72
|
-
def initialize(cookie, secret)
|
73
|
-
|
72
|
+
def initialize(session_id, cookie, secret)
|
73
|
+
super session_id
|
74
|
+
if secret.blank? || secret.length < 16
|
75
|
+
Merb.logger.warn("You must specify a session_secret_key in your init file, and it must be at least 16 characters")
|
74
76
|
raise ArgumentError, 'A secret is required to generate an integrity hash for cookie session data.'
|
75
77
|
end
|
76
78
|
@secret = secret
|
77
|
-
|
79
|
+
self.update(unmarshal(cookie))
|
78
80
|
end
|
79
|
-
|
80
|
-
#
|
81
|
-
# String:: Cookie value.
|
81
|
+
|
82
|
+
# Teardown and/or persist the current session.
|
82
83
|
#
|
83
|
-
# ==== Raises
|
84
|
-
# CookieOverflow:: Session contains too much information.
|
85
|
-
def read_cookie
|
86
|
-
unless @data.nil?
|
87
|
-
updated = marshal(@data)
|
88
|
-
raise CookieOverflow if updated.size > MAX
|
89
|
-
updated
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
84
|
# ==== Parameters
|
94
|
-
#
|
95
|
-
|
96
|
-
|
97
|
-
|
85
|
+
# request<Merb::Request>:: The Merb::Request that came in from Rack.
|
86
|
+
def finalize(request)
|
87
|
+
if _original_session_data != (new_session_data = self.to_cookie)
|
88
|
+
request.set_session_cookie_value(new_session_data)
|
89
|
+
end
|
98
90
|
end
|
99
91
|
|
100
|
-
#
|
101
|
-
# k<~to_s>:: The key of the session parameter to retrieve.
|
92
|
+
# Create the raw cookie string; includes an HMAC keyed message digest.
|
102
93
|
#
|
103
94
|
# ==== Returns
|
104
|
-
# String::
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
# Yields the session data to an each block.
|
95
|
+
# String:: Cookie value.
|
96
|
+
#
|
97
|
+
# ==== Raises
|
98
|
+
# CookieOverflow:: Session contains too much information.
|
110
99
|
#
|
111
|
-
# ====
|
112
|
-
#
|
113
|
-
|
114
|
-
|
100
|
+
# ==== Notes
|
101
|
+
# The data (self) is converted to a Hash first, since a container might
|
102
|
+
# choose to do a full Marshal on the data, which would make it persist
|
103
|
+
# attributes like 'needs_new_cookie', which it shouldn't.
|
104
|
+
def to_cookie
|
105
|
+
unless self.empty?
|
106
|
+
data = self.serialize
|
107
|
+
value = Merb::Request.escape "#{data}--#{generate_digest(data)}"
|
108
|
+
raise CookieOverflow if value.size > MAX
|
109
|
+
value
|
110
|
+
end
|
115
111
|
end
|
116
112
|
|
117
|
-
# Deletes the session by emptying stored data.
|
118
|
-
def delete
|
119
|
-
@data = {}
|
120
|
-
end
|
121
|
-
|
122
113
|
private
|
123
114
|
|
124
|
-
# Attempts to redirect any messages to the data object.
|
125
|
-
def method_missing(name, *args, &block)
|
126
|
-
@data.send(name, *args, &block)
|
127
|
-
end
|
128
|
-
|
129
115
|
# Generate the HMAC keyed message digest. Uses SHA1.
|
130
116
|
def generate_digest(data)
|
131
117
|
OpenSSL::HMAC.hexdigest(DIGEST, @secret, data)
|
132
118
|
end
|
133
|
-
|
134
|
-
# Marshal a session hash into safe cookie data. Include an integrity hash.
|
135
|
-
#
|
136
|
-
# ==== Parameters
|
137
|
-
# session<Hash>:: The session to store in the cookie.
|
138
|
-
#
|
139
|
-
# ==== Returns
|
140
|
-
# String:: The cookie to be stored.
|
141
|
-
def marshal(session)
|
142
|
-
data = Base64.encode64(Marshal.dump(session)).chop
|
143
|
-
Merb::Request.escape "#{data}--#{generate_digest(data)}"
|
144
|
-
end
|
145
|
-
|
119
|
+
|
146
120
|
# Unmarshal cookie data to a hash and verify its integrity.
|
147
121
|
#
|
148
122
|
# ==== Parameters
|
@@ -154,15 +128,31 @@ module Merb
|
|
154
128
|
# ==== Returns
|
155
129
|
# Hash:: The stored session data.
|
156
130
|
def unmarshal(cookie)
|
157
|
-
if cookie
|
158
|
-
|
159
|
-
|
131
|
+
if cookie.blank?
|
132
|
+
{}
|
133
|
+
else
|
134
|
+
data, digest = Merb::Request.unescape(cookie).split('--')
|
135
|
+
return {} if data.blank? || digest.blank?
|
160
136
|
unless digest == generate_digest(data)
|
161
|
-
|
162
|
-
|
137
|
+
clear
|
138
|
+
unless Merb::Config[:ignore_tampered_cookies]
|
139
|
+
raise TamperedWithCookie, "Maybe the site's session_secret_key has changed?"
|
140
|
+
end
|
163
141
|
end
|
164
|
-
|
142
|
+
unserialize(data)
|
165
143
|
end
|
166
144
|
end
|
145
|
+
|
146
|
+
protected
|
147
|
+
|
148
|
+
# Serialize current session data - as a Hash
|
149
|
+
def serialize
|
150
|
+
Base64.encode64(Marshal.dump(self.to_hash)).chop
|
151
|
+
end
|
152
|
+
|
153
|
+
# Unserialize the raw cookie data - to a Hash
|
154
|
+
def unserialize(data)
|
155
|
+
Marshal.load(Base64.decode64(data)) rescue {}
|
156
|
+
end
|
167
157
|
end
|
168
|
-
end
|
158
|
+
end
|