padrino-cookies 0.1.0 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/README.md CHANGED
@@ -30,7 +30,6 @@ TODO
30
30
  -----
31
31
 
32
32
  * Additional documentation
33
- * Signed cookies
34
33
 
35
34
  Copyright
36
35
  ---------
@@ -1,9 +1,4 @@
1
1
  # encoding: UTF-8
2
- require 'active_support/core_ext/integer/time'
3
- require 'active_support/core_ext/numeric/time'
4
- require 'active_support/core_ext/numeric/bytes'
5
- require 'active_support/core_ext/date/calculations'
6
-
7
2
  module Padrino
8
3
  module Cookies
9
4
  class Jar
@@ -15,6 +10,12 @@ module Padrino
15
10
  @request = app.request
16
11
  @cookies = app.request.cookies
17
12
 
13
+ if app.settings.respond_to?(:cookie_secret)
14
+ @secret = app.settings.cookie_secret
15
+ elsif app.settings.respond_to?(:session_secret)
16
+ @secret = app.settings.session_secret
17
+ end
18
+
18
19
  @options = {
19
20
  path: '/',
20
21
  httponly: true,
@@ -29,7 +30,7 @@ module Padrino
29
30
  # Value of the cookie
30
31
  #
31
32
  # @example
32
- # cookies[:remembrance]
33
+ # cookie[:remembrance]
33
34
  # # => '71ab53190d2f863b5f3b12381d2d5986512f8e15b34d439e6b66e3daf41b5e35'
34
35
  #
35
36
  # @since 0.1.0
@@ -66,8 +67,11 @@ module Padrino
66
67
  # @option options [String] :domain
67
68
  # The scope in which this cookie is accessible
68
69
  #
70
+ # @raise [Overflow]
71
+ # Raised when the value of the cookie exceeds the maximum size
72
+ #
69
73
  # @example
70
- # cookies[:remembrance] = '71ab53190d2f863b5f3b12381d2d5986512f8e15b34d439e6b66e3daf41b5e35'
74
+ # cookie[:remembrance] = '71ab53190d2f863b5f3b12381d2d5986512f8e15b34d439e6b66e3daf41b5e35'
71
75
  #
72
76
  # @since 0.1.0
73
77
  # @api public
@@ -76,6 +80,8 @@ module Padrino
76
80
  options = { value: options }
77
81
  end
78
82
 
83
+ raise Overflow if options[:value].size > MAX_COOKIE_SIZE
84
+
79
85
  @response.set_cookie(name, @options.merge(options))
80
86
  @cookies[name.to_s] = options[:value]
81
87
  end
@@ -205,32 +211,79 @@ module Padrino
205
211
  # Sets a permanent cookie
206
212
  #
207
213
  # @example
208
- # cookies.permanent[:remembrance] = '71ab53190d2f863b5f3b12381d2d5986512f8e15b34d439e6b66e3daf41b5e35'
214
+ # cookie.permanent[:remembrance] = '71ab53190d2f863b5f3b12381d2d5986512f8e15b34d439e6b66e3daf41b5e35'
209
215
  #
210
216
  # @since 0.1.0
211
217
  # @api public
212
218
  def permanent
213
- @permanent ||= PermanentJar.new(self)
219
+ @permanent ||= PermanentJar.new(self, @secret)
220
+ end
221
+
222
+ ###
223
+ # Signs a cookie with a cryptographic hash so it cannot be tampered with
224
+ #
225
+ # @example
226
+ # cookie.signed[:remembrance] = '71ab53190d2f863b5f3b12381d2d5986512f8e15b34d439e6b66e3daf41b5e35'
227
+ #
228
+ # @since 0.1.1
229
+ # @api public
230
+ def signed
231
+ @signed ||= SignedJar.new(self, @secret)
214
232
  end
215
233
  end # Jar
216
234
 
217
235
  class PermanentJar # @private
218
- def initialize(parent_jar)
236
+ def initialize(parent_jar, secret)
219
237
  @parent_jar = parent_jar
238
+ @secret = secret
220
239
  end
221
240
 
222
- def []=(key, options)
223
- unless options.is_a?(Hash)
224
- options = { value: options }
225
- end
226
-
241
+ def []=(name, options)
242
+ options = { value: options } unless options.is_a?(Hash)
227
243
  options[:expires] = 1.year.from_now
228
- @parent_jar[key] = options
244
+ @parent_jar[name] = options
245
+ end
246
+
247
+ def signed
248
+ @signed ||= SignedJar.new(self, @secret)
229
249
  end
230
250
 
231
251
  def method_missing(method, *args, &block)
232
252
  @parent_jar.send(method, *args, &block)
233
253
  end
234
254
  end # PermanentJar
255
+
256
+ class SignedJar # @private
257
+ def initialize(parent_jar, secret)
258
+ if secret.blank? || secret.size < 64
259
+ raise ArgumentError, 'cookie_secret must be at least 64 characters long'
260
+ end
261
+
262
+ @parent_jar = parent_jar
263
+ @message_verifier = ActiveSupport::MessageVerifier.new(secret)
264
+ end
265
+
266
+ def [](name)
267
+ if value = @parent_jar[name]
268
+ @message_verifier.verify(value)
269
+ end
270
+ rescue
271
+ nil
272
+ end
273
+
274
+ def []=(name, options)
275
+ options = { value: options } unless options.is_a?(Hash)
276
+ options[:value] = @message_verifier.generate(options[:value])
277
+ @parent_jar[name] = options
278
+ end
279
+
280
+ def permanent
281
+ @permanent ||= PermanentJar.new(self, @secret)
282
+ end
283
+
284
+ def method_missing(method, *args, &block)
285
+ @parent_jar.send(method, *args, &block)
286
+ end
287
+ end # SignedJar
235
288
  end # Cookies
236
289
  end # Padrino
@@ -1,6 +1,6 @@
1
1
  # encoding: UTF-8
2
2
  module Padrino
3
3
  module Cookies
4
- VERSION = '0.1.0'
4
+ VERSION = '0.1.1'
5
5
  end
6
6
  end
@@ -1,9 +1,22 @@
1
1
  # encoding: UTF-8
2
+ require 'active_support/core_ext/integer/time'
3
+ require 'active_support/core_ext/numeric/time'
4
+ require 'active_support/core_ext/date/calculations'
5
+ require 'active_support/message_verifier'
6
+
2
7
  require 'padrino-core'
3
8
  FileSet.glob_require('padrino-cookies/**/*.rb', __FILE__)
4
9
 
5
10
  module Padrino
6
11
  module Cookies
12
+ MAX_COOKIE_SIZE = 4096
13
+
14
+ class Overflow < ArgumentError
15
+ def http_status
16
+ 500
17
+ end
18
+ end
19
+
7
20
  class << self
8
21
  # @private
9
22
  def registered(app)
data/spec/cookies_spec.rb CHANGED
@@ -5,6 +5,20 @@ describe Padrino::Cookies do
5
5
  route('foo=bar', 'bar=foo') { cookies }
6
6
  end
7
7
 
8
+ context :secret do
9
+ it 'should use :cookie_secret when set' do
10
+ app.set :cookie_secret, 'cookie_secret'
11
+ secret = cookies.instance_variable_get(:@secret)
12
+ secret.should == 'cookie_secret'
13
+ end
14
+
15
+ it 'should fall back to :session_secret when :cookie_secret is not set' do
16
+ app.set :session_secret, 'session_secret'
17
+ secret = cookies.instance_variable_get(:@secret)
18
+ secret.should == 'session_secret'
19
+ end
20
+ end
21
+
8
22
  context :[] do
9
23
  it 'can retrieve existing cookies' do
10
24
  cookies['foo'].should == 'bar'
@@ -33,6 +47,18 @@ describe Padrino::Cookies do
33
47
  cookies['test'].should == 'test'
34
48
  end
35
49
 
50
+ it 'should raise Overflow when more then 4096 bytes are used' do
51
+ cookies['test'] = 'C' * 4096
52
+ cookies['test'].should_not be_nil
53
+
54
+ expect { cookies['test'] = 'C' * 4097 }.to raise_error(Padrino::Cookies::Overflow)
55
+ end
56
+
57
+ it 'should accept a hash of options' do
58
+ cookies['test'] = { value: 'test', path: '/' }
59
+ cookies['test'].should == 'test'
60
+ end
61
+
36
62
  it 'should set the response headers when setting a cookie' do
37
63
  result = route do
38
64
  cookies['foo'] = 'bar'
@@ -86,7 +112,7 @@ describe Padrino::Cookies do
86
112
  end
87
113
  end
88
114
 
89
- describe :clear do
115
+ context :clear do
90
116
  it 'can delete all cookies that are set' do
91
117
  cookies['foo'].should == 'bar'
92
118
  cookies['bar'].should == 'foo'
@@ -110,7 +136,7 @@ describe Padrino::Cookies do
110
136
  end
111
137
  end
112
138
 
113
- describe :keys do
139
+ context :keys do
114
140
  it 'can give you a list of cookies that are set' do
115
141
  cookies.keys.should == ['bar', 'foo']
116
142
  end
@@ -161,11 +187,18 @@ describe Padrino::Cookies do
161
187
  end
162
188
 
163
189
  context :permanent do
190
+ before { app.set :cookie_secret, ('test' * 16) }
191
+
164
192
  it 'should add cookies to the parent jar' do
165
193
  cookies.permanent['baz'] = 'foo'
166
194
  cookies['baz'].should == 'foo'
167
195
  end
168
196
 
197
+ it 'should accept a hash of options' do
198
+ cookies.permanent['foo'] = { value: 'baz', path: '/' }
199
+ cookies['foo'].should == 'baz'
200
+ end
201
+
169
202
  it 'should set a cookie that expires in the distant future' do
170
203
  result = route do
171
204
  cookies.permanent['baz'] = 'foo'
@@ -174,5 +207,49 @@ describe Padrino::Cookies do
174
207
 
175
208
  result.should =~ /#{1.year.from_now.year}/
176
209
  end
210
+
211
+ it 'should allow you to chain signed cookies' do
212
+ result = route do
213
+ cookies.permanent.signed['foo'] = 'baz'
214
+ response['Set-Cookie']
215
+ end
216
+
217
+ result.should =~ /#{1.year.from_now.year}/
218
+ result.should =~ /6cbc7824dbfd7121efb019bb55f01be1e07bcf58/
219
+ end
220
+ end
221
+
222
+ context :signed do
223
+ before { app.set :cookie_secret, ('test' * 16) }
224
+
225
+ it 'should read signed values' do
226
+ cookies['foo'] = 'BAhJIghiYXoGOgZFRg==--6cbc7824dbfd7121efb019bb55f01be1e07bcf58'
227
+ cookies.signed['foo'].should == 'baz'
228
+ end
229
+
230
+ it 'should write signed values' do
231
+ cookies.signed['foo'] = 'baz'
232
+ cookies['foo'].should == 'BAhJIghiYXoGOgZFRg==--6cbc7824dbfd7121efb019bb55f01be1e07bcf58'
233
+ end
234
+
235
+ it 'should accept a hash of options' do
236
+ cookies.signed['foo'] = { value: 'baz', path: '/' }
237
+ cookies.signed['foo'].should == 'baz'
238
+ end
239
+
240
+ it 'should require a 64 character secret' do
241
+ app.set :cookie_secret, ('C' * 63)
242
+ expect { cookies.signed['foo'] = 'baz' }.to raise_error(ArgumentError)
243
+ end
244
+
245
+ it 'should allow you to chain permanent cookies' do
246
+ result = route do
247
+ cookies.signed.permanent['foo'] = 'baz'
248
+ response['Set-Cookie']
249
+ end
250
+
251
+ result.should =~ /#{1.year.from_now.year}/
252
+ result.should =~ /6cbc7824dbfd7121efb019bb55f01be1e07bcf58/
253
+ end
177
254
  end
178
255
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: padrino-cookies
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-03-02 00:00:00.000000000 Z
12
+ date: 2012-03-26 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: padrino-core
16
- requirement: &15464784 !ruby/object:Gem::Requirement
16
+ requirement: &13846392 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,10 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: *15464784
24
+ version_requirements: *13846392
25
25
  - !ruby/object:Gem::Dependency
26
26
  name: rspec
27
- requirement: &15464232 !ruby/object:Gem::Requirement
27
+ requirement: &13845648 !ruby/object:Gem::Requirement
28
28
  none: false
29
29
  requirements:
30
30
  - - ! '>='
@@ -32,10 +32,10 @@ dependencies:
32
32
  version: 2.0.0
33
33
  type: :development
34
34
  prerelease: false
35
- version_requirements: *15464232
35
+ version_requirements: *13845648
36
36
  - !ruby/object:Gem::Dependency
37
37
  name: rspec-html-matchers
38
- requirement: &15463920 !ruby/object:Gem::Requirement
38
+ requirement: &13844784 !ruby/object:Gem::Requirement
39
39
  none: false
40
40
  requirements:
41
41
  - - ! '>='
@@ -43,10 +43,10 @@ dependencies:
43
43
  version: '0'
44
44
  type: :development
45
45
  prerelease: false
46
- version_requirements: *15463920
46
+ version_requirements: *13844784
47
47
  - !ruby/object:Gem::Dependency
48
48
  name: rack-test
49
- requirement: &15463500 !ruby/object:Gem::Requirement
49
+ requirement: &13825020 !ruby/object:Gem::Requirement
50
50
  none: false
51
51
  requirements:
52
52
  - - ! '>='
@@ -54,7 +54,7 @@ dependencies:
54
54
  version: '0'
55
55
  type: :development
56
56
  prerelease: false
57
- version_requirements: *15463500
57
+ version_requirements: *13825020
58
58
  description: A plugin for the Padrino web framework which adds support for Rails like
59
59
  cookie manipulation
60
60
  email:
@@ -97,7 +97,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
97
97
  version: '0'
98
98
  requirements: []
99
99
  rubyforge_project:
100
- rubygems_version: 1.8.15
100
+ rubygems_version: 1.8.17
101
101
  signing_key:
102
102
  specification_version: 3
103
103
  summary: A plugin for the Padrino web framework which adds support for Rails like