padrino-cookies 0.1.0 → 0.1.1

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/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