signed_form 0.1.2 → 0.2.0
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.
- checksums.yaml +4 -4
- data/.travis.yml +5 -1
- data/.yardopts +0 -1
- data/Changes.md +30 -0
- data/Gemfile +2 -0
- data/README.md +152 -39
- data/app/views/signed_form/expired_form.html +25 -0
- data/lib/signed_form.rb +31 -0
- data/lib/signed_form/action_controller/permit_signed_params.rb +9 -16
- data/lib/signed_form/action_view/form_helper.rb +19 -5
- data/lib/signed_form/digest_stores.rb +7 -0
- data/lib/signed_form/digest_stores/memory_store.rb +7 -0
- data/lib/signed_form/digest_stores/null_store.rb +9 -0
- data/lib/signed_form/digestor.rb +67 -0
- data/lib/signed_form/engine.rb +5 -0
- data/lib/signed_form/errors.rb +4 -2
- data/lib/signed_form/form_builder.rb +79 -65
- data/lib/signed_form/gate_keeper.rb +50 -0
- data/lib/signed_form/hmac.rb +8 -10
- data/lib/signed_form/test_helper.rb +17 -0
- data/lib/signed_form/version.rb +2 -2
- data/signed_form.gemspec +1 -0
- data/spec/digestor_spec.rb +81 -0
- data/spec/fixtures/views/_fields.html.erb +2 -0
- data/spec/fixtures/views/form.html.erb +4 -0
- data/spec/form_builder_spec.rb +137 -30
- data/spec/hmac_spec.rb +13 -23
- data/spec/permit_signed_params_spec.rb +60 -43
- data/spec/spec_helper.rb +14 -2
- metadata +32 -3
data/spec/form_builder_spec.rb
CHANGED
@@ -20,18 +20,35 @@ class Widget
|
|
20
20
|
end
|
21
21
|
end
|
22
22
|
|
23
|
+
class ControllerRenderer < AbstractController::Base
|
24
|
+
include AbstractController::Rendering
|
25
|
+
self.view_paths = [ActionView::FileSystemResolver.new(File.join(File.dirname(__FILE__), 'fixtures', 'views'))]
|
26
|
+
|
27
|
+
view_context_class.class_eval do
|
28
|
+
def url_for(*args)
|
29
|
+
'/users'
|
30
|
+
end
|
31
|
+
|
32
|
+
def protect_against_forgery?
|
33
|
+
false
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
class MockBuilder < ActionView::Helpers::FormBuilder; end
|
39
|
+
|
23
40
|
describe SignedForm::FormBuilder do
|
24
41
|
include SignedFormViewHelper
|
25
42
|
|
26
|
-
before { SignedForm
|
27
|
-
|
43
|
+
before { SignedForm.secret_key = "abc123" }
|
44
|
+
before { SignedForm.options[:digest] = false }
|
28
45
|
|
29
46
|
let(:user) { User.new }
|
30
47
|
let(:widget) { Widget.new }
|
31
48
|
|
32
|
-
describe "
|
49
|
+
describe "form_for tag" do
|
33
50
|
it "should build a form with signature" do
|
34
|
-
content =
|
51
|
+
content = form_for(User.new, signed: true) do |f|
|
35
52
|
f.text_field :name
|
36
53
|
end
|
37
54
|
|
@@ -43,17 +60,73 @@ describe SignedForm::FormBuilder do
|
|
43
60
|
content.should =~ Regexp.new(regex, Regexp::MULTILINE)
|
44
61
|
end
|
45
62
|
|
46
|
-
it "should
|
47
|
-
content =
|
63
|
+
it "should not sign if no option is present" do
|
64
|
+
content = form_for(User.new) do |f|
|
48
65
|
f.text_field :name
|
49
66
|
end
|
50
67
|
|
68
|
+
content.should_not =~ /form_signature/
|
69
|
+
end
|
70
|
+
|
71
|
+
context "signed default" do
|
72
|
+
before { SignedForm.options[:signed] = true }
|
73
|
+
|
74
|
+
it "should sign a form when the default option is set" do
|
75
|
+
content = form_for(User.new) do |f|
|
76
|
+
f.text_field :name
|
77
|
+
end
|
78
|
+
|
79
|
+
content.should =~ /form_signature/
|
80
|
+
end
|
81
|
+
|
82
|
+
it "should allow the form to be overriden" do
|
83
|
+
content = form_for(User.new, signed: false) do |f|
|
84
|
+
f.text_field :name
|
85
|
+
end
|
86
|
+
|
87
|
+
content.should_not =~ /form_signature/
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
describe "third party builders" do
|
93
|
+
it "should build a signed form" do
|
94
|
+
content = form_for(User.new, signed: true, builder: MockBuilder) do |f|
|
95
|
+
f.text_field :name
|
96
|
+
end
|
51
97
|
data = get_data_from_form(content)
|
52
|
-
data.
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
98
|
+
data['user'].should include(:name)
|
99
|
+
end
|
100
|
+
|
101
|
+
it "should raise if a builder isn't supported" do
|
102
|
+
expect { form_for(User.new, signed: true, builder: Class.new) {} }.to raise_error
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "sign_destination" do
|
107
|
+
after do
|
108
|
+
@data.should include(:_options_)
|
109
|
+
@data[:_options_].should include(:method, :url)
|
110
|
+
@data[:_options_][:method].should == :post
|
111
|
+
@data[:_options_][:url].should == '/users'
|
112
|
+
end
|
113
|
+
|
114
|
+
it "should set a target" do
|
115
|
+
content = form_for(User.new, signed: true, sign_destination: true) do |f|
|
116
|
+
f.text_field :name
|
117
|
+
end
|
118
|
+
|
119
|
+
@data = get_data_from_form(content)
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should set a target when the default options are enabled" do
|
123
|
+
SignedForm.options[:sign_destination] = true
|
124
|
+
|
125
|
+
content = form_for(User.new, signed: true) do |f|
|
126
|
+
f.text_field :name
|
127
|
+
end
|
128
|
+
|
129
|
+
@data = get_data_from_form(content)
|
57
130
|
end
|
58
131
|
end
|
59
132
|
|
@@ -69,18 +142,16 @@ describe SignedForm::FormBuilder do
|
|
69
142
|
:object, :radio_button, :parent_builder,
|
70
143
|
:collection_check_boxes, :grouped_collection_select, :select,
|
71
144
|
:collection_select, :collection_radio_buttons, :time_select,
|
72
|
-
:datetime_select, :time_zone_select, :date_select]
|
145
|
+
:datetime_select, :time_zone_select, :date_select, :search_field]
|
73
146
|
|
74
147
|
after do
|
75
|
-
@data.should be
|
76
|
-
@data.size.should == 1
|
77
148
|
@data['user'].size.should == 1
|
78
149
|
@data['user'].should include(:name)
|
79
150
|
end
|
80
151
|
|
81
152
|
fields.each do |field|
|
82
153
|
it "should add to the allowed attributes when #{field} is used" do
|
83
|
-
content =
|
154
|
+
content = form_for(User.new, signed: true) do |f|
|
84
155
|
f.send field, :name
|
85
156
|
end
|
86
157
|
|
@@ -89,7 +160,7 @@ describe SignedForm::FormBuilder do
|
|
89
160
|
end
|
90
161
|
|
91
162
|
it "should add to the allowed attributes when collection_check_boxes is used", action_pack: /4\.\d+/ do
|
92
|
-
content =
|
163
|
+
content = form_for(User.new, signed: true) do |f|
|
93
164
|
f.collection_check_boxes :name, ['a', 'b'], :to_s, :to_s
|
94
165
|
end
|
95
166
|
|
@@ -100,7 +171,7 @@ describe SignedForm::FormBuilder do
|
|
100
171
|
continent = Struct.new('Continent', :continent_name, :countries)
|
101
172
|
country = Struct.new('Country', :country_id, :country_name)
|
102
173
|
|
103
|
-
content =
|
174
|
+
content = form_for(User.new, signed: true) do |f|
|
104
175
|
f.grouped_collection_select(:name, [continent.new("<Africa>", [country.new("<sa>", "<South Africa>")])],
|
105
176
|
:countries, :continent_name, :country_id, :country_name)
|
106
177
|
end
|
@@ -109,7 +180,7 @@ describe SignedForm::FormBuilder do
|
|
109
180
|
end
|
110
181
|
|
111
182
|
it "should add to the allowed attributes when select is used" do
|
112
|
-
content =
|
183
|
+
content = form_for(User.new, signed: true) do |f|
|
113
184
|
f.select :name, %w(a b)
|
114
185
|
end
|
115
186
|
|
@@ -117,7 +188,7 @@ describe SignedForm::FormBuilder do
|
|
117
188
|
end
|
118
189
|
|
119
190
|
it "should add to the allowed attributes when collection_select is used" do
|
120
|
-
content =
|
191
|
+
content = form_for(User.new, signed: true) do |f|
|
121
192
|
f.collection_select :name, %w(a b), :to_s, :to_s
|
122
193
|
end
|
123
194
|
|
@@ -125,7 +196,7 @@ describe SignedForm::FormBuilder do
|
|
125
196
|
end
|
126
197
|
|
127
198
|
it "should add to the allowed attributes when collection_radio_buttons is used", action_pack: /4\.\d+/ do
|
128
|
-
content =
|
199
|
+
content = form_for(User.new, signed: true) do |f|
|
129
200
|
f.collection_radio_buttons :name, %w(a b), :to_s, :to_s
|
130
201
|
end
|
131
202
|
|
@@ -133,7 +204,7 @@ describe SignedForm::FormBuilder do
|
|
133
204
|
end
|
134
205
|
|
135
206
|
it "should add to the allowed attributes when date_select is used" do
|
136
|
-
content =
|
207
|
+
content = form_for(User.new, signed: true) do |f|
|
137
208
|
f.date_select :name
|
138
209
|
end
|
139
210
|
|
@@ -141,7 +212,7 @@ describe SignedForm::FormBuilder do
|
|
141
212
|
end
|
142
213
|
|
143
214
|
it "should add to the allowed attributes when time_select is used" do
|
144
|
-
content =
|
215
|
+
content = form_for(User.new, signed: true) do |f|
|
145
216
|
f.time_select :name
|
146
217
|
end
|
147
218
|
|
@@ -149,7 +220,7 @@ describe SignedForm::FormBuilder do
|
|
149
220
|
end
|
150
221
|
|
151
222
|
it "should add to the allowed attributes when datetime_select is used" do
|
152
|
-
content =
|
223
|
+
content = form_for(User.new, signed: true) do |f|
|
153
224
|
f.datetime_select :name
|
154
225
|
end
|
155
226
|
|
@@ -157,7 +228,7 @@ describe SignedForm::FormBuilder do
|
|
157
228
|
end
|
158
229
|
|
159
230
|
it "should add to the allowed attributes when time_zone_select is used" do
|
160
|
-
content =
|
231
|
+
content = form_for(User.new, signed: true) do |f|
|
161
232
|
f.time_zone_select :name
|
162
233
|
end
|
163
234
|
|
@@ -165,7 +236,7 @@ describe SignedForm::FormBuilder do
|
|
165
236
|
end
|
166
237
|
|
167
238
|
it "should add to the allowed attributes when radio_button is used" do
|
168
|
-
content =
|
239
|
+
content = form_for(User.new, signed: true) do |f|
|
169
240
|
f.radio_button :name, ['bar']
|
170
241
|
end
|
171
242
|
|
@@ -175,7 +246,7 @@ describe SignedForm::FormBuilder do
|
|
175
246
|
|
176
247
|
describe "add_signed_fields" do
|
177
248
|
it "should add fields to the marshaled data" do
|
178
|
-
content =
|
249
|
+
content = form_for(User.new, signed: true) do |f|
|
179
250
|
f.add_signed_fields :name, :address
|
180
251
|
end
|
181
252
|
|
@@ -189,7 +260,7 @@ describe SignedForm::FormBuilder do
|
|
189
260
|
it "should nest attributes" do
|
190
261
|
user.stub(widgets: [widget])
|
191
262
|
|
192
|
-
content =
|
263
|
+
content = form_for(user, signed: true) do |f|
|
193
264
|
f.fields_for :widgets do |ff|
|
194
265
|
ff.text_field :name
|
195
266
|
end
|
@@ -200,7 +271,7 @@ describe SignedForm::FormBuilder do
|
|
200
271
|
end
|
201
272
|
|
202
273
|
it "should deeply nest attributes" do
|
203
|
-
content =
|
274
|
+
content = form_for(:author, url: '/', signed: true) do |f|
|
204
275
|
f.fields_for :books do |ff|
|
205
276
|
ff.text_field :name
|
206
277
|
ff.check_box :hardcover
|
@@ -218,7 +289,7 @@ describe SignedForm::FormBuilder do
|
|
218
289
|
end
|
219
290
|
|
220
291
|
specify "nested arrays should not have duplicates" do
|
221
|
-
content =
|
292
|
+
content = form_for(:author, url: '/', signed: true) do |f|
|
222
293
|
f.fields_for :books do |ff|
|
223
294
|
ff.text_field :name
|
224
295
|
ff.text_field :name
|
@@ -230,7 +301,7 @@ describe SignedForm::FormBuilder do
|
|
230
301
|
end
|
231
302
|
|
232
303
|
specify "attribute arrays should not have duplicates" do
|
233
|
-
content =
|
304
|
+
content = form_for(:author, url: '/', signed: true) do |f|
|
234
305
|
f.text_field :name
|
235
306
|
f.text_field :name
|
236
307
|
end
|
@@ -239,4 +310,40 @@ describe SignedForm::FormBuilder do
|
|
239
310
|
data[:author].size.should == 1
|
240
311
|
end
|
241
312
|
end
|
313
|
+
|
314
|
+
describe "form digests" do
|
315
|
+
before { SignedForm.options[:digest] = true }
|
316
|
+
|
317
|
+
let (:controller) { ControllerRenderer.new }
|
318
|
+
|
319
|
+
it "should append a digest to the marshaled data" do
|
320
|
+
controller.render template: 'form'
|
321
|
+
|
322
|
+
data = get_data_from_form(controller.response_body)
|
323
|
+
data[:_options_].should include(:digest)
|
324
|
+
end
|
325
|
+
|
326
|
+
it "should not digest if the option is disabled" do
|
327
|
+
SignedForm.options[:digest] = false
|
328
|
+
|
329
|
+
controller.render template: 'form'
|
330
|
+
data = get_data_from_form(controller.response_body)
|
331
|
+
data[:_options_].should_not include(:digest)
|
332
|
+
end
|
333
|
+
|
334
|
+
it "should get the digest from the view paths" do
|
335
|
+
controller.render template: 'form'
|
336
|
+
data = get_data_from_form(controller.response_body)
|
337
|
+
digestor = data[:_options_][:digest]
|
338
|
+
digestor.view_paths = controller.view_paths
|
339
|
+
digestor.to_s.should == "6a161ab9978322e8251d809b3558ab1a"
|
340
|
+
end
|
341
|
+
|
342
|
+
it "should set a grace period" do
|
343
|
+
controller.render template: 'form'
|
344
|
+
data = get_data_from_form(controller.response_body)
|
345
|
+
data[:_options_].should include(:digest_expiration)
|
346
|
+
(Time.now..(Time.now + SignedForm.options[:digest_grace_period])).should cover(data[:_options_][:digest_expiration])
|
347
|
+
end
|
348
|
+
end
|
242
349
|
end
|
data/spec/hmac_spec.rb
CHANGED
@@ -1,34 +1,24 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
3
|
describe SignedForm::HMAC do
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
end
|
4
|
+
it 'should raise if no key is given' do
|
5
|
+
expect { SignedForm::HMAC.new }.to raise_error(SignedForm::Errors::NoSecretKey)
|
6
|
+
end
|
8
7
|
|
9
|
-
|
10
|
-
|
11
|
-
after { SignedForm::HMAC.secret_key = nil }
|
8
|
+
describe 'create' do
|
9
|
+
let(:hmac) { SignedForm::HMAC.new(secret_key: "superdupersecret") }
|
12
10
|
|
13
|
-
|
14
|
-
|
15
|
-
|
11
|
+
it 'should create a hex signature' do
|
12
|
+
hmac.create("my signed message").length.should == 40
|
13
|
+
hmac.create("my signed message").should == "93c1ecd4c10122cbf873ca6cf9eff08888565054"
|
16
14
|
end
|
17
15
|
end
|
18
16
|
|
19
|
-
describe '
|
20
|
-
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
context 'when a key is present' do
|
25
|
-
before { SignedForm::HMAC.secret_key = "superdupersecret" }
|
26
|
-
after { SignedForm::HMAC.secret_key = nil }
|
27
|
-
|
28
|
-
let(:signature) { SignedForm::HMAC.create_hmac "My super secret" }
|
17
|
+
describe 'verify' do
|
18
|
+
let(:hmac) { SignedForm::HMAC.new(secret_key: "superdupersecret") }
|
19
|
+
let(:signature) { hmac.create "My super secret" }
|
29
20
|
|
30
|
-
|
31
|
-
|
32
|
-
end
|
21
|
+
specify { hmac.verify(signature, "My super secret").should be_true }
|
22
|
+
specify { hmac.verify(signature, "My bad secret").should_not be_true }
|
33
23
|
end
|
34
24
|
end
|
@@ -4,77 +4,94 @@ class Controller < ActionController::Base
|
|
4
4
|
include SignedForm::ActionController::PermitSignedParams
|
5
5
|
|
6
6
|
public :permit_signed_form_data
|
7
|
+
|
8
|
+
def view_paths
|
9
|
+
[File.join(File.dirname(__FILE__), 'fixtures', 'views')]
|
10
|
+
end
|
7
11
|
end
|
8
12
|
|
9
13
|
describe SignedForm::ActionController::PermitSignedParams do
|
10
14
|
let(:controller) { Controller.new }
|
15
|
+
let(:hmac) { SignedForm::HMAC.new(secret_key: SignedForm.secret_key) }
|
16
|
+
let(:params) { controller.params }
|
17
|
+
let(:template) { double(view_paths: [File.join(File.dirname(__FILE__), 'fixtures', 'views')], virtual_path: 'form') }
|
18
|
+
let(:digestor) { SignedForm::Digestor.new(template) }
|
19
|
+
|
20
|
+
def marshal_and_sign(data)
|
21
|
+
encoded_data = Base64.strict_encode64(Marshal.dump(data))
|
22
|
+
signature = hmac.create(encoded_data)
|
23
|
+
"#{encoded_data}--#{signature}"
|
24
|
+
end
|
11
25
|
|
12
26
|
before do
|
13
|
-
SignedForm
|
27
|
+
SignedForm.secret_key = "abc123"
|
14
28
|
|
15
29
|
Controller.any_instance.stub(request: double('request', method: 'POST', request_method: 'POST', fullpath: '/users', url: '/users'))
|
16
30
|
Controller.any_instance.stub(params: { "user" => { name: "Erich Menge", occupation: 'developer' } })
|
17
|
-
end
|
18
31
|
|
19
|
-
|
32
|
+
params.stub(:[]).and_call_original
|
33
|
+
params.stub(:[]).with('user').and_return(params)
|
34
|
+
end
|
20
35
|
|
21
36
|
it "should raise if signature isn't valid" do
|
22
|
-
|
37
|
+
params['form_signature'] = "bad signature"
|
23
38
|
expect { controller.permit_signed_form_data }.to raise_error(SignedForm::Errors::InvalidSignature)
|
24
39
|
end
|
25
40
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
data = Base64.strict_encode64(Marshal.dump("user" => [:name]))
|
30
|
-
signature = SignedForm::HMAC.create_hmac(data)
|
31
|
-
|
32
|
-
params['form_signature'] = "#{data}--#{signature}"
|
33
|
-
|
34
|
-
params.should_receive(:require).with('user').and_return(params)
|
35
|
-
params.should_receive(:permit).with(:name).and_return(params)
|
36
|
-
controller.permit_signed_form_data
|
37
|
-
end
|
38
|
-
|
39
|
-
it "should verify current url matches targeted url" do
|
40
|
-
params = controller.params
|
41
|
+
context "when the parameters are good" do
|
42
|
+
before { params.should_receive(:permit).with(:name).and_return(params) }
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
+
it "should permit attributes that are allowed" do
|
45
|
+
params['form_signature'] = marshal_and_sign "user" => [:name]
|
46
|
+
controller.permit_signed_form_data
|
47
|
+
end
|
44
48
|
|
45
|
-
|
49
|
+
it "should verify current url matches targeted url" do
|
50
|
+
params['form_signature'] = marshal_and_sign("user" => [:name], :_options_ => { method: 'post', url: '/users' })
|
46
51
|
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
controller.permit_signed_form_data
|
52
|
+
controller.request.should_receive(:fullpath).and_return '/users'
|
53
|
+
controller.permit_signed_form_data
|
54
|
+
end
|
51
55
|
end
|
52
56
|
|
53
57
|
it "should reject if url doesn't match" do
|
54
|
-
params =
|
55
|
-
|
56
|
-
|
57
|
-
signature = SignedForm::HMAC.create_hmac(data)
|
58
|
-
|
59
|
-
params['form_signature'] = "#{data}--#{signature}"
|
60
|
-
|
61
|
-
params.stub(:require).with('user').and_return(params)
|
62
|
-
params.stub(:permit).with(:name).and_return(params)
|
58
|
+
params['form_signature'] = marshal_and_sign("user" => [:name], :_options_ => { method: 'post', url: '/admin' })
|
59
|
+
expect { controller.permit_signed_form_data }.to raise_error(SignedForm::Errors::InvalidURL)
|
60
|
+
end
|
63
61
|
|
62
|
+
it "should reject if the method doesn't match" do
|
63
|
+
params['form_signature'] = marshal_and_sign("user" => [:name], :_options_ => { method: 'put', url: '/users' })
|
64
64
|
expect { controller.permit_signed_form_data }.to raise_error(SignedForm::Errors::InvalidURL)
|
65
65
|
end
|
66
66
|
|
67
|
-
|
68
|
-
|
67
|
+
context "when the digest is bad" do
|
68
|
+
before { digestor.stub(:to_s).and_return "bad" }
|
69
|
+
|
70
|
+
it "should not reject if inside grace period" do
|
71
|
+
params['form_signature'] = marshal_and_sign("user" => [:name], :_options_ => { digest: digestor, digest_expiration: Time.now + 20 })
|
72
|
+
expect { controller.permit_signed_form_data }.not_to raise_error(SignedForm::Errors::ExpiredForm)
|
73
|
+
end
|
69
74
|
|
70
|
-
|
71
|
-
|
75
|
+
it "should reject if outside the grace period" do
|
76
|
+
params['form_signature'] = marshal_and_sign("user" => [:name], :_options_ => { digest: digestor, digest_expiration: Time.now - 20 })
|
77
|
+
expect { controller.permit_signed_form_data }.to raise_error(SignedForm::Errors::ExpiredForm)
|
78
|
+
end
|
72
79
|
|
73
|
-
|
80
|
+
it "should reject if no grace period" do
|
81
|
+
params['form_signature'] = marshal_and_sign("user" => [:name], :_options_ => { digest: digestor })
|
82
|
+
expect { controller.permit_signed_form_data }.to raise_error(SignedForm::Errors::ExpiredForm)
|
83
|
+
end
|
84
|
+
end
|
74
85
|
|
75
|
-
|
76
|
-
|
86
|
+
context "when the digest is good" do
|
87
|
+
it "should not reject if outside grace period" do
|
88
|
+
params['form_signature'] = marshal_and_sign("user" => [:name], :_options_ => { digest: digestor, digest_expiration: Time.now - 20 })
|
89
|
+
expect { controller.permit_signed_form_data }.not_to raise_error(SignedForm::Errors::ExpiredForm)
|
90
|
+
end
|
77
91
|
|
78
|
-
|
92
|
+
it "should not reject if no grace period" do
|
93
|
+
params['form_signature'] = marshal_and_sign("user" => [:name], :_options_ => { digest: digestor })
|
94
|
+
expect { controller.permit_signed_form_data }.not_to raise_error(SignedForm::Errors::ExpiredForm)
|
95
|
+
end
|
79
96
|
end
|
80
97
|
end
|