cookies_manager 0.2.2 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.rvmrc +1 -1
- data/CHANGELOG.rdoc +5 -0
- data/Gemfile +1 -0
- data/README.rdoc +16 -8
- data/cookies_manager.gemspec +3 -3
- data/lib/cookies_manager/base.rb +6 -25
- data/lib/cookies_manager/controller_additions.rb +32 -18
- data/lib/cookies_manager/version.rb +1 -1
- data/spec/README.rdoc +1 -8
- data/spec/cookies_manager/base_spec.rb +41 -145
- data/spec/cookies_manager/controller_additions_spec.rb +1 -1
- data/spec/spec_helper.rb +4 -12
- metadata +6 -37
data/.rvmrc
CHANGED
@@ -1 +1 @@
|
|
1
|
-
rvm use 1.
|
1
|
+
rvm use 1.9.2@cookies_manager --create
|
data/CHANGELOG.rdoc
CHANGED
data/Gemfile
CHANGED
data/README.rdoc
CHANGED
@@ -4,16 +4,20 @@ CookiesManager is a simple tool that provides a convenient way to manage any kin
|
|
4
4
|
- The data you store in the cookies is automatically marshalled, zipped, and base64-encoded.
|
5
5
|
- The data you ask to retrieve from the cookies is read from a cache, which spares the need for reverse transformation as long as the cache is in sync with the cookies. When the cache is out of sync, CookiesManager automatically resynchronizes the cache by reading the data from the cookies and processing reverse transformation (base64-decoding, unzipping, unmarshalling).
|
6
6
|
- The cache is defined somehow at the controller instance level. Thus, each HTTP request has its own cache for its whole lifetime.
|
7
|
+
- Cookies are signed to prevent tampering.
|
7
8
|
|
8
9
|
== Installation
|
9
10
|
|
10
|
-
In <b>Rails
|
11
|
+
In <b>Rails 3</b>, add this to your environment.rb file.
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
13
|
+
gem 'cookies_manager'
|
14
|
+
|
15
|
+
Then set a secret token in your config/secret_token.rb file.
|
16
|
+
# Your secret key for verifying the integrity of signed cookies.
|
17
|
+
# If you change this key, all old signed cookies will become invalid!
|
18
|
+
# Make sure the secret is at least 30 characters and all random,
|
19
|
+
# no regular words or you'll be exposed to dictionary attacks.
|
20
|
+
<Your Application Name>::Application.config.secret_token = 'your secret token'
|
17
21
|
|
18
22
|
== Getting started
|
19
23
|
|
@@ -55,16 +59,20 @@ than the session length, such as user display preferences.
|
|
55
59
|
|
56
60
|
== Important notes
|
57
61
|
|
62
|
+
- Do not store sensitive data in your cookies. Although all cookies are signed, those are not encrypted.
|
58
63
|
- If your application is multi-threaded, using CookiesManager is threadsafe as long as your threads refer the same CookiesManager instance.
|
59
64
|
- Unless you know what you are really doing, do not store large data in your cookies, since these are included in HTTP request headers.
|
60
65
|
- Do not store model objects in cookies (nor in session). For more explanation, check {Ryan Bates's screencast about dangers of model in session}[http://railscasts.com/episodes/13-dangers-of-model-in-session].
|
66
|
+
- If you really need to deal with an object, I recommend to store it in your database and set a simple related token in your the cookies.
|
61
67
|
|
62
68
|
== Running the tests
|
63
69
|
|
64
70
|
RSpec 2 is used for testing. To get the specs running, check {spec/README}[https://github.com/RStrike/CookiesManager/blob/master/spec/README.rdoc].
|
65
71
|
|
66
|
-
==
|
67
|
-
|
72
|
+
== Compatibility
|
73
|
+
Tested on:
|
74
|
+
- Ruby 1.9.3
|
75
|
+
- Rails 3.2.3
|
68
76
|
|
69
77
|
== Changes
|
70
78
|
|
data/cookies_manager.gemspec
CHANGED
@@ -18,8 +18,8 @@ Gem::Specification.new do |s|
|
|
18
18
|
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
19
19
|
s.require_paths = ["lib"]
|
20
20
|
|
21
|
-
s.add_dependency 'actionpack', '~> 2.3
|
22
|
-
s.add_dependency '
|
23
|
-
s.add_development_dependency 'rspec', '~> 2.
|
21
|
+
s.add_dependency 'actionpack', '~> 3.2.3'
|
22
|
+
s.add_dependency 'activesupport', '~> 3.2.3'
|
23
|
+
s.add_development_dependency 'rspec', '~> 2.9.0'
|
24
24
|
s.add_development_dependency 'rr', '~> 1.0.4'
|
25
25
|
end
|
data/lib/cookies_manager/base.rb
CHANGED
@@ -1,9 +1,6 @@
|
|
1
|
-
require 'aquarium'
|
2
|
-
|
3
1
|
module CookiesManager
|
4
2
|
# The base class of CookiesManager
|
5
3
|
class Base
|
6
|
-
include Aquarium::DSL # AOP Filters are defined at the end of the class
|
7
4
|
|
8
5
|
attr_accessor :cookies # The cookies hash to be based on
|
9
6
|
|
@@ -36,6 +33,7 @@ module CookiesManager
|
|
36
33
|
# @return [Object] the data associated with the key
|
37
34
|
#
|
38
35
|
def read(key, opts = {})
|
36
|
+
opts.symbolize_keys!
|
39
37
|
result = nil
|
40
38
|
getMutex(key).synchronize do
|
41
39
|
result = read_from_cache_or_cookies(key, opts)
|
@@ -68,12 +66,13 @@ module CookiesManager
|
|
68
66
|
# @return [Integer] the number of bytes written in the cookies
|
69
67
|
#
|
70
68
|
def write(key, data, opts = {})
|
69
|
+
opts.symbolize_keys!
|
71
70
|
unpacked_data = data
|
72
71
|
data = pack(data) unless opts[:skip_pack]
|
73
72
|
result = nil
|
74
73
|
getMutex(key).synchronize do
|
75
74
|
cache[key] ||= {}
|
76
|
-
result = cookies[key] = {:value => data}.merge(opts) # store the packed data in the cookies hash
|
75
|
+
result = cookies.signed[key] = {:value => data}.merge(opts) # store the packed data in the cookies hash
|
77
76
|
cache[key][:unpacked_data] = unpacked_data # store the unpacked data in the cache for fast read in the read method
|
78
77
|
cache[key][:packed_data] = data # store the packed data in the cache for fast change diff in the read method
|
79
78
|
end
|
@@ -93,6 +92,7 @@ module CookiesManager
|
|
93
92
|
# @return (see #read)
|
94
93
|
#
|
95
94
|
def delete(key, opts = {})
|
95
|
+
opts.symbolize_keys!
|
96
96
|
result = nil
|
97
97
|
getMutex(key).synchronize do
|
98
98
|
result = read_from_cache_or_cookies(key, opts)
|
@@ -121,7 +121,7 @@ module CookiesManager
|
|
121
121
|
# reads from the cache if in sync. Otherwise, reads from the cookies and resynchronizes the cache for the given key
|
122
122
|
def read_from_cache_or_cookies(key, opts)
|
123
123
|
result = nil
|
124
|
-
data_from_cookies = cookies[key]
|
124
|
+
data_from_cookies = cookies.signed[key]
|
125
125
|
cache[key] ||= {}
|
126
126
|
if cache[key][:packed_data] == data_from_cookies # checks whether cache is in sync with cookies
|
127
127
|
result = cache[key][:unpacked_data] # reads from cache
|
@@ -145,28 +145,9 @@ module CookiesManager
|
|
145
145
|
end
|
146
146
|
|
147
147
|
def unpack(data)
|
148
|
-
Marshal.load(ActiveSupport::Gzip.decompress(Base64.decode64(data)))
|
148
|
+
Marshal.load(ActiveSupport::Gzip.decompress(Base64.decode64(data))) unless data.nil?
|
149
149
|
end
|
150
150
|
|
151
|
-
#=================#
|
152
|
-
#== AOP Filters ==#
|
153
|
-
#=================#
|
154
|
-
|
155
|
-
# Since Aquarium sets the arities of observered methods to -1, we need to save the methods arities in a hash declared as a class variable
|
156
|
-
self.instance_methods(false).each { |method| (@method_arities ||= {})[method.to_sym] = instance_method(method).arity }
|
157
|
-
|
158
|
-
around :methods => [:read, :write, :delete] do |join_point, object, *args|
|
159
|
-
key = (args[0] = args[0].to_s) # we should stick with string keys since the cookies hash does not support indifferent access (i.e. :foo and "foo" are different keys), although this has changed in rails 3.1
|
160
|
-
opts = args[last_arg_index(join_point.method_name)] # retrieve the options arg (last argument)
|
161
|
-
opts.symbolize_keys! if opts.is_a?(Hash)
|
162
|
-
join_point.proceed(*args)
|
163
|
-
end
|
164
|
-
|
165
|
-
# Returns the index of the last arg in the method signature
|
166
|
-
def self.last_arg_index(method_name)
|
167
|
-
instance_eval { @method_arities[method_name] }.abs - 1
|
168
|
-
end
|
169
|
-
|
170
151
|
end
|
171
152
|
|
172
153
|
end
|
@@ -4,26 +4,40 @@ module CookiesManager
|
|
4
4
|
# It is automatically extended by all controllers.
|
5
5
|
module ControllerAdditions
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
controller.instance_variable_set(:@_cookies_manager, CookiesManager::Base.new(controller.instance_eval { cookies } ))
|
19
|
-
# wraps the instance variable in a the +cookies_manager+ method
|
20
|
-
define_method :cookies_manager, proc { controller.instance_variable_get(:@_cookies_manager) }
|
21
|
-
# makes the +cookies_manager+ method available to all views as a helper method
|
22
|
-
helper_method :cookies_manager
|
7
|
+
module ClassMethods
|
8
|
+
# Sets up a before filter that creates a new CookiesManager into an instance variable,
|
9
|
+
# which is made available to all views through the +cookies_manager+ helper method.
|
10
|
+
#
|
11
|
+
# You can call this method on your controller class as follows:
|
12
|
+
#
|
13
|
+
# class YourController < ApplicationController
|
14
|
+
# load_cookies_manager
|
15
|
+
#
|
16
|
+
def load_cookies_manager
|
17
|
+
before_filter :build_cookies_manager
|
23
18
|
end
|
24
19
|
end
|
20
|
+
|
21
|
+
# Builds a cookies manager as a controller instance variable, made available to all views
|
22
|
+
def build_cookies_manager
|
23
|
+
# defines a CookiesManager instance variable, based on the cookies hash
|
24
|
+
@_cookies_manager = CookiesManager::Base.new(cookies)
|
25
|
+
# wraps the instance variable in the +cookies_manager+ instance method
|
26
|
+
define_singleton_method :cookies_manager, proc { @_cookies_manager }
|
27
|
+
# makes the +cookies_manager+ method available to all views as a helper method
|
28
|
+
self.class.helper_method :cookies_manager
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.included(base)
|
32
|
+
base.extend ClassMethods
|
33
|
+
end
|
34
|
+
|
25
35
|
end
|
26
36
|
end
|
27
37
|
|
28
|
-
# Automatically add all ControllerAdditions methods to
|
29
|
-
ActionController
|
38
|
+
# Automatically add all ControllerAdditions methods to controllers
|
39
|
+
if defined? ActionController
|
40
|
+
ActionController::Base.class_eval do
|
41
|
+
include CookiesManager::ControllerAdditions
|
42
|
+
end
|
43
|
+
end
|
data/spec/README.rdoc
CHANGED
@@ -8,14 +8,7 @@
|
|
8
8
|
bundle
|
9
9
|
rake
|
10
10
|
|
11
|
-
Some of the specs, covering race conditions in multithreaded contexts, pause in purpose some threads for a few seconds through AOP to test critical sections. Those "slow" tests can be easily skipped by passing +skip_slow+ as an argument to the +rake+ command:
|
12
|
-
|
13
|
-
rake skip_slow
|
14
|
-
|
15
|
-
However, I recommend running all the specs whenever you change the code.
|
16
|
-
|
17
11
|
== Ruby versions
|
18
12
|
|
19
|
-
Currently, the specs require Ruby 1.
|
13
|
+
Currently, the specs require Ruby 1.9.3
|
20
14
|
|
21
|
-
Ruby 1.9.x support is coming up next.
|
@@ -12,7 +12,6 @@ module MacrosForCookiesManager
|
|
12
12
|
end
|
13
13
|
|
14
14
|
include MacrosForCookiesManager
|
15
|
-
include Aquarium::Aspects
|
16
15
|
|
17
16
|
describe CookiesManager::Base do
|
18
17
|
before(:all) do
|
@@ -26,41 +25,37 @@ describe CookiesManager::Base do
|
|
26
25
|
describe "#write" do
|
27
26
|
describe "#write + pack data" do
|
28
27
|
context "when write complex data" do
|
29
|
-
before {
|
30
|
-
specify {
|
31
|
-
specify { unpack(cookies['my_key']).should eql @complex_data }
|
28
|
+
before { subject.write('my_key', @complex_data) }
|
29
|
+
specify { unpack(cookies.signed['my_key']).should eql @complex_data }
|
32
30
|
end
|
33
31
|
context "when write nil value" do
|
34
|
-
before {
|
35
|
-
specify {
|
36
|
-
specify { unpack(cookies['my_key']).should be_nil}
|
32
|
+
before { subject.write('my_key', nil) }
|
33
|
+
specify { unpack(cookies.signed['my_key']).should be_nil}
|
37
34
|
end
|
38
35
|
end
|
39
36
|
describe "write without packing data" do
|
40
37
|
context "when write simple data" do
|
41
|
-
before {
|
42
|
-
specify {
|
43
|
-
specify { cookies['my_key'].should eql @simple_data }
|
38
|
+
before { subject.write('my_key', @simple_data, :skip_pack => true) }
|
39
|
+
specify { cookies.signed['my_key'].should eql @simple_data }
|
44
40
|
end
|
45
41
|
context "when write nil value" do
|
46
|
-
before {
|
47
|
-
specify {
|
48
|
-
specify { cookies['my_key'].should be_nil}
|
42
|
+
before { subject.write('my_key', nil, :skip_pack => true) }
|
43
|
+
specify { cookies.signed['my_key'].should be_nil}
|
49
44
|
end
|
50
45
|
end
|
51
46
|
describe "#write + set expiration date" do
|
52
47
|
before { subject.write(@key = 'my_key', @complex_data, :expires => (@expiration_date = 2.hours.from_now)) }
|
53
|
-
specify {
|
48
|
+
specify { cookies.instance_eval {@set_cookies['my_key'][:expires]}.should == @expiration_date }
|
54
49
|
end
|
55
50
|
describe "#write with nil key" do
|
56
51
|
before { subject.write(nil, @complex_data) }
|
57
|
-
specify { unpack(cookies[nil]).should eql @complex_data }
|
58
|
-
specify { unpack(cookies['']).should eql @complex_data }
|
52
|
+
specify { unpack(cookies.signed[nil]).should eql @complex_data }
|
53
|
+
specify { unpack(cookies.signed['']).should eql @complex_data }
|
59
54
|
end
|
60
55
|
describe "#write with empty string key" do
|
61
56
|
before { subject.write('', @complex_data) }
|
62
|
-
specify { unpack(cookies[nil]).should eql @complex_data }
|
63
|
-
specify { unpack(cookies['']).should eql @complex_data }
|
57
|
+
specify { unpack(cookies.signed[nil]).should eql @complex_data }
|
58
|
+
specify { unpack(cookies.signed['']).should eql @complex_data }
|
64
59
|
end
|
65
60
|
end
|
66
61
|
|
@@ -85,20 +80,20 @@ describe CookiesManager::Base do
|
|
85
80
|
end
|
86
81
|
describe "#read some data previously stored directly into the cookies hash" do
|
87
82
|
context "when reading non-nil data" do
|
88
|
-
before { cookies['my_key'] = {:value => @complex_data} }
|
83
|
+
before { cookies.signed['my_key'] = {:value => @complex_data} }
|
89
84
|
specify { subject.read('my_key', :skip_unpack => true).should eql @complex_data }
|
90
85
|
end
|
91
86
|
context "when reading a nil value" do
|
92
|
-
before { cookies['my_key'] = {:value => nil } }
|
87
|
+
before { cookies.signed['my_key'] = {:value => nil } }
|
93
88
|
specify { subject.read('my_key').should be_nil }
|
94
89
|
end
|
95
90
|
context "when reading some data stored with a nil key" do
|
96
|
-
before { cookies[nil] = {:value => @complex_data} }
|
91
|
+
before { cookies.signed[nil] = {:value => @complex_data} }
|
97
92
|
specify { subject.read(nil, :skip_unpack => true).should eql @complex_data }
|
98
93
|
specify { subject.read('', :skip_unpack => true).should eql @complex_data }
|
99
94
|
end
|
100
95
|
context "when reading some data stored with an empty string key" do
|
101
|
-
before { cookies[''] = {:value => @complex_data} }
|
96
|
+
before { cookies.signed[''] = {:value => @complex_data} }
|
102
97
|
specify { subject.read(nil, :skip_unpack => true).should eql @complex_data }
|
103
98
|
specify { subject.read('', :skip_unpack => true).should eql @complex_data }
|
104
99
|
end
|
@@ -107,7 +102,7 @@ describe CookiesManager::Base do
|
|
107
102
|
context "when read some simple data" do
|
108
103
|
before do
|
109
104
|
subject.write('my_key', @simple_data)
|
110
|
-
cookies['my_key'] = {:value => (@new_simple_data = "some new data")}
|
105
|
+
cookies.signed['my_key'] = {:value => (@new_simple_data = "some new data")}
|
111
106
|
end
|
112
107
|
specify { subject.read('my_key', :skip_unpack => true).should eql @new_simple_data }
|
113
108
|
end
|
@@ -115,7 +110,7 @@ describe CookiesManager::Base do
|
|
115
110
|
before do
|
116
111
|
subject.write('my_key', @complex_data)
|
117
112
|
@new_complex_data = @complex_data.merge(:some_new_item => 'it modifies the data')
|
118
|
-
cookies['my_key'] = {:value => pack(@new_complex_data)}
|
113
|
+
cookies.signed['my_key'] = {:value => pack(@new_complex_data)}
|
119
114
|
end
|
120
115
|
specify { subject.read('my_key').should eql @new_complex_data }
|
121
116
|
specify { unpack(subject.read('my_key', :skip_unpack => true)).should eql @new_complex_data } #if :skip_unpack option is set, we need to unpack the data manually
|
@@ -125,7 +120,7 @@ describe CookiesManager::Base do
|
|
125
120
|
shared_examples_for "reading with indifferent access key" do
|
126
121
|
specify { subject.read(:my_key).should eql @complex_data }
|
127
122
|
specify { subject.read('my_key').should eql @complex_data }
|
128
|
-
specify { unpack(cookies['my_key']).should eql @complex_data }
|
123
|
+
specify { unpack(cookies.signed['my_key']).should eql @complex_data }
|
129
124
|
end
|
130
125
|
context "when data has been written with a key of type symbol" do
|
131
126
|
before { subject.write(:my_key, @complex_data) }
|
@@ -150,7 +145,7 @@ describe CookiesManager::Base do
|
|
150
145
|
it_should_behave_like "when deleting existing data"
|
151
146
|
end
|
152
147
|
context "when data has been previously stored directly into the cookies hash" do
|
153
|
-
before { cookies['my_key'] = {:value => @complex_data} }
|
148
|
+
before { cookies.signed['my_key'] = {:value => @complex_data} }
|
154
149
|
it_should_behave_like "when deleting existing data"
|
155
150
|
end
|
156
151
|
end
|
@@ -186,7 +181,7 @@ describe CookiesManager::Base do
|
|
186
181
|
|
187
182
|
describe "#symbol/string indifferent keys in options hash" do
|
188
183
|
describe "#read" do
|
189
|
-
before { cookies['my_key'] = pack(@complex_data) }
|
184
|
+
before { cookies.signed['my_key'] = pack(@complex_data) }
|
190
185
|
specify { subject.read('my_key', :unpack => true).should eql @complex_data }
|
191
186
|
specify { subject.read('my_key', 'unpack' => true).should eql @complex_data }
|
192
187
|
end
|
@@ -195,11 +190,11 @@ describe CookiesManager::Base do
|
|
195
190
|
subject.write('key1', @simple_data, :skip_pack => true)
|
196
191
|
subject.write('key2', @simple_data, 'skip_pack' => true)
|
197
192
|
end
|
198
|
-
specify { cookies['key1'].should eql @simple_data }
|
199
|
-
specify { cookies['key2'].should eql @simple_data }
|
193
|
+
specify { cookies.signed['key1'].should eql @simple_data }
|
194
|
+
specify { cookies.signed['key2'].should eql @simple_data }
|
200
195
|
end
|
201
196
|
describe "#delete" do
|
202
|
-
before { cookies['my_key'] = pack(@complex_data) }
|
197
|
+
before { cookies.signed['my_key'] = pack(@complex_data) }
|
203
198
|
specify { subject.delete('my_key', :unpack => true).should eql @complex_data }
|
204
199
|
specify { subject.delete('my_key', 'unpack' => true).should eql @complex_data }
|
205
200
|
end
|
@@ -217,16 +212,16 @@ describe CookiesManager::Base do
|
|
217
212
|
describe "#cache in sync with the cookies" do
|
218
213
|
describe "#accessing some data that has been read at least once by the CookiesManager" do
|
219
214
|
before do
|
220
|
-
cookies['my_key'] = pack(@complex_data)
|
221
|
-
subject.read('my_key').should eql @complex_data # this should store the data in the cache, thus sparing the need for future
|
222
|
-
dont_allow(
|
215
|
+
cookies.signed['my_key'] = pack(@complex_data)
|
216
|
+
subject.read('my_key').should eql @complex_data # this should store the data in the cache, thus sparing the need for future unpacking
|
217
|
+
dont_allow(subject).unpack # this makes sure unpacking is never done (i.e. the cache is used)
|
223
218
|
end
|
224
219
|
it_should_behave_like 'when accessing the data'
|
225
220
|
end
|
226
221
|
describe "#accessing some data that has been written through the CookiesManager" do
|
227
222
|
before do
|
228
|
-
subject.write('my_key', @complex_data) # this should store the data in the cache, thus sparing the need for future
|
229
|
-
dont_allow(
|
223
|
+
subject.write('my_key', @complex_data) # this should store the data in the cache, thus sparing the need for future unpacking
|
224
|
+
dont_allow(subject).unpack # this makes sure unpacking is never done (i.e. the cache is used)
|
230
225
|
end
|
231
226
|
it_should_behave_like 'when accessing the data'
|
232
227
|
end
|
@@ -234,129 +229,30 @@ describe CookiesManager::Base do
|
|
234
229
|
describe "#cache out of sync" do
|
235
230
|
before do
|
236
231
|
subject.write('my_key', @original_data = ['my', 'original', 'array'])
|
237
|
-
cookies['my_key'] = pack(@complex_data) # this causes the cache to be out of sync, thus causing future reads to unmarshall the data from the cookies
|
238
|
-
mock.proxy(
|
232
|
+
cookies.signed['my_key'] = pack(@complex_data) # this causes the cache to be out of sync, thus causing future reads to unmarshall the data from the cookies
|
233
|
+
mock.proxy(subject).unpack.with_any_args # this makes sure unpacking is invoked
|
239
234
|
end
|
240
235
|
it_should_behave_like 'when accessing the data'
|
241
236
|
describe "#automatic cache resynchronization on read" do
|
242
237
|
before do
|
243
|
-
subject.read('my_key').should eql @complex_data # this causes
|
244
|
-
dont_allow(
|
238
|
+
subject.read('my_key').should eql @complex_data # this causes unpacking, and cache resynchronization
|
239
|
+
dont_allow(subject).unpack # this makes sure we don't unmarshall anymore at this point (i.e. the cache is now in sync and can be used)
|
245
240
|
end
|
246
241
|
specify { subject.read('my_key').should eql @complex_data}
|
247
242
|
end
|
248
243
|
end
|
249
244
|
end
|
250
245
|
|
251
|
-
describe "#
|
252
|
-
|
253
|
-
p "Inside the critical section, when calling cookies#{method_name}, the #{thread_name} thread pauses for #{sleep(2)} seconds, to make the other thread wait at the entrance of the critical section..."
|
254
|
-
end
|
255
|
-
|
256
|
-
def print_wait_before_action(action)
|
257
|
-
p "Before calling ##{action}, wait for #{sleep(1)} seconds to let the #{thread_name} thread lock the critical section..."
|
258
|
-
end
|
259
|
-
|
260
|
-
def run_thread
|
261
|
-
Thread.new do
|
262
|
-
Thread.current["name"] = thread_name
|
263
|
-
yield
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
def build_aspect(method_name)
|
268
|
-
Aspect.new :around, :calls_to => method_name, :on_objects => subject.cookies do |join_point, object, *args|
|
269
|
-
print_inside_critical_section(join_point.method_name) if Thread.current["name"] == thread_name
|
270
|
-
join_point.proceed
|
271
|
-
end
|
272
|
-
end
|
273
|
-
|
274
|
-
before { subject.write('my_key', @original_data = 'original data') }
|
275
|
-
after { @aspect.unadvise }
|
276
|
-
|
277
|
-
describe "#a thread is reading with a key" do
|
278
|
-
let(:thread_name) { :reader }
|
279
|
-
before do
|
280
|
-
@aspect = build_aspect('[]')
|
281
|
-
@reader = run_thread { @result = subject.read('my_key') }
|
282
|
-
end
|
283
|
-
shared_examples_for 'when another thread wants to access the data with the same key while reading' do
|
284
|
-
it "should wait until the reader finishes reading" do
|
285
|
-
@reader.join
|
286
|
-
@result.should eql @original_data
|
287
|
-
end
|
288
|
-
end
|
289
|
-
context "when another thread wants to write some new data with the same key" do
|
290
|
-
before do
|
291
|
-
print_wait_before_action(:write)
|
292
|
-
subject.write('my_key', @complex_data)
|
293
|
-
end
|
294
|
-
it_should_behave_like 'when another thread wants to access the data with the same key while reading'
|
295
|
-
end
|
296
|
-
context "when another thread wants to delete with the same key" do
|
297
|
-
before do
|
298
|
-
print_wait_before_action(:delete)
|
299
|
-
subject.delete('my_key')
|
300
|
-
end
|
301
|
-
it_should_behave_like 'when another thread wants to access the data with the same key while reading'
|
302
|
-
end
|
303
|
-
end
|
304
|
-
describe "#a thread is writing with a key" do
|
305
|
-
let(:thread_name) { :writer }
|
246
|
+
describe "#cookies tampering" do
|
247
|
+
describe "#when tampering a cookie value" do
|
306
248
|
before do
|
307
|
-
|
308
|
-
|
249
|
+
subject.write('my_key', @complex_data)
|
250
|
+
cookies['my_key'] = 'some new value' # note that we intentionally don't call the method 'signed' on the cookies hash in order to tamper the cookies
|
309
251
|
end
|
310
|
-
|
311
|
-
it 'should wait until the writer finishes writing' do
|
312
|
-
@writer.join
|
313
|
-
@result.should eql @complex_data
|
314
|
-
end
|
315
|
-
end
|
316
|
-
context "when another thread wants to read with same key" do
|
317
|
-
before do
|
318
|
-
print_wait_before_action(:read)
|
319
|
-
@result = subject.read('my_key')
|
320
|
-
end
|
321
|
-
it_should_behave_like 'when another thread wants to access the data with the same key while writing'
|
322
|
-
end
|
323
|
-
context "when another thread wants to delete with the same key" do
|
324
|
-
before do
|
325
|
-
print_wait_before_action(:delete)
|
326
|
-
@result = subject.delete('my_key')
|
327
|
-
end
|
328
|
-
it_should_behave_like 'when another thread wants to access the data with the same key while writing'
|
329
|
-
end
|
252
|
+
specify { subject.read('my_key').should be_nil}
|
330
253
|
end
|
331
|
-
|
332
|
-
|
333
|
-
before do
|
334
|
-
@aspect = build_aspect(:delete)
|
335
|
-
@deletor = run_thread { @delete_result = subject.delete('my_key') }
|
336
|
-
end
|
337
|
-
shared_examples_for 'when another thread wants to access the data with the same key while deleting' do
|
338
|
-
it 'should wait until the deletor finishes deleting' do
|
339
|
-
@deletor.join
|
340
|
-
@delete_result.should eql @original_data
|
341
|
-
end
|
342
|
-
end
|
343
|
-
context "when another thread wants to read with the same key" do
|
344
|
-
before do
|
345
|
-
print_wait_before_action(:read)
|
346
|
-
@read_result = subject.read('my_key')
|
347
|
-
end
|
348
|
-
it_should_behave_like 'when another thread wants to access the data with the same key while deleting'
|
349
|
-
specify { @read_result.should be_nil }
|
350
|
-
end
|
351
|
-
context "when another thread wants to write with the same key" do
|
352
|
-
before do
|
353
|
-
print_wait_before_action(:write)
|
354
|
-
subject.write('my_key', @complex_data)
|
355
|
-
end
|
356
|
-
it_should_behave_like 'when another thread wants to access the data with the same key while deleting'
|
357
|
-
end
|
358
|
-
end
|
359
|
-
end
|
254
|
+
end
|
255
|
+
|
360
256
|
|
361
257
|
end
|
362
258
|
|
@@ -5,7 +5,7 @@ describe CookiesManager::ControllerAdditions do
|
|
5
5
|
context "when calling the class method :load_cookies_manager on the controller class" do
|
6
6
|
before do
|
7
7
|
mock(TestController).helper_method(:cookies_manager) # makes sure the :cookies_manager method is declared as a helper (to make it available to the views, for example)
|
8
|
-
mock(TestController).before_filter {
|
8
|
+
mock(TestController).before_filter(:build_cookies_manager) { controller.build_cookies_manager } # makes sure a before_filter is set AND calls the related method on our controller instance
|
9
9
|
TestController.load_cookies_manager # the class method we want to test
|
10
10
|
end
|
11
11
|
it "should create a :cookies_manager instance method" do
|
data/spec/spec_helper.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
require 'rubygems'
|
2
1
|
require 'bundler/setup'
|
3
2
|
require 'action_controller'
|
3
|
+
require 'active_support/all'
|
4
4
|
Bundler.require(:default)
|
5
5
|
|
6
6
|
RSpec.configure do |config|
|
@@ -11,19 +11,11 @@ RSpec.configure do |config|
|
|
11
11
|
config.mock_with :rr
|
12
12
|
end
|
13
13
|
|
14
|
-
# A test controller with a cookies hash (this works in rails 2.3.14 but needs to be adapted for versions of rails >= 3.x.x)
|
15
14
|
class TestController < ActionController::Base
|
16
15
|
|
17
|
-
def
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
def response
|
22
|
-
@response ||= ActionController::Response.new
|
23
|
-
end
|
24
|
-
|
25
|
-
def initialize
|
26
|
-
self.cookies = ActionController::CookieJar.new(self)
|
16
|
+
def initialize(*args)
|
17
|
+
super(*args)
|
18
|
+
self.cookies = ActionDispatch::Cookies::CookieJar.new('b57121a9239fe9e55d46c534c7af7218')
|
27
19
|
end
|
28
20
|
|
29
21
|
protected
|
metadata
CHANGED
@@ -1,13 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cookies_manager
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash: 19
|
5
4
|
prerelease:
|
6
|
-
|
7
|
-
- 0
|
8
|
-
- 2
|
9
|
-
- 2
|
10
|
-
version: 0.2.2
|
5
|
+
version: 0.3.0
|
11
6
|
platform: ruby
|
12
7
|
authors:
|
13
8
|
- Christophe Levand
|
@@ -15,7 +10,7 @@ autorequire:
|
|
15
10
|
bindir: bin
|
16
11
|
cert_chain: []
|
17
12
|
|
18
|
-
date:
|
13
|
+
date: 2012-05-06 00:00:00 +02:00
|
19
14
|
default_executable:
|
20
15
|
dependencies:
|
21
16
|
- !ruby/object:Gem::Dependency
|
@@ -26,28 +21,18 @@ dependencies:
|
|
26
21
|
requirements:
|
27
22
|
- - ~>
|
28
23
|
- !ruby/object:Gem::Version
|
29
|
-
|
30
|
-
segments:
|
31
|
-
- 2
|
32
|
-
- 3
|
33
|
-
- 14
|
34
|
-
version: 2.3.14
|
24
|
+
version: 3.2.3
|
35
25
|
type: :runtime
|
36
26
|
version_requirements: *id001
|
37
27
|
- !ruby/object:Gem::Dependency
|
38
|
-
name:
|
28
|
+
name: activesupport
|
39
29
|
prerelease: false
|
40
30
|
requirement: &id002 !ruby/object:Gem::Requirement
|
41
31
|
none: false
|
42
32
|
requirements:
|
43
33
|
- - ~>
|
44
34
|
- !ruby/object:Gem::Version
|
45
|
-
|
46
|
-
segments:
|
47
|
-
- 0
|
48
|
-
- 4
|
49
|
-
- 4
|
50
|
-
version: 0.4.4
|
35
|
+
version: 3.2.3
|
51
36
|
type: :runtime
|
52
37
|
version_requirements: *id002
|
53
38
|
- !ruby/object:Gem::Dependency
|
@@ -58,12 +43,7 @@ dependencies:
|
|
58
43
|
requirements:
|
59
44
|
- - ~>
|
60
45
|
- !ruby/object:Gem::Version
|
61
|
-
|
62
|
-
segments:
|
63
|
-
- 2
|
64
|
-
- 7
|
65
|
-
- 0
|
66
|
-
version: 2.7.0
|
46
|
+
version: 2.9.0
|
67
47
|
type: :development
|
68
48
|
version_requirements: *id003
|
69
49
|
- !ruby/object:Gem::Dependency
|
@@ -74,11 +54,6 @@ dependencies:
|
|
74
54
|
requirements:
|
75
55
|
- - ~>
|
76
56
|
- !ruby/object:Gem::Version
|
77
|
-
hash: 31
|
78
|
-
segments:
|
79
|
-
- 1
|
80
|
-
- 0
|
81
|
-
- 4
|
82
57
|
version: 1.0.4
|
83
58
|
type: :development
|
84
59
|
version_requirements: *id004
|
@@ -124,18 +99,12 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
124
99
|
requirements:
|
125
100
|
- - ">="
|
126
101
|
- !ruby/object:Gem::Version
|
127
|
-
hash: 3
|
128
|
-
segments:
|
129
|
-
- 0
|
130
102
|
version: "0"
|
131
103
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
132
104
|
none: false
|
133
105
|
requirements:
|
134
106
|
- - ">="
|
135
107
|
- !ruby/object:Gem::Version
|
136
|
-
hash: 3
|
137
|
-
segments:
|
138
|
-
- 0
|
139
108
|
version: "0"
|
140
109
|
requirements: []
|
141
110
|
|