signed_form 0.0.1 → 0.1.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 +0 -2
- data/.yardopts +3 -0
- data/Gemfile +3 -3
- data/README.md +26 -11
- data/Rakefile +2 -9
- data/lib/signed_form.rb +0 -1
- data/lib/signed_form/action_controller/permit_signed_params.rb +11 -1
- data/lib/signed_form/action_view/form_helper.rb +4 -1
- data/lib/signed_form/errors.rb +1 -0
- data/lib/signed_form/form_builder.rb +13 -3
- data/lib/signed_form/hmac.rb +2 -0
- data/lib/signed_form/version.rb +2 -3
- data/signed_form.gemspec +2 -2
- data/spec/form_builder_spec.rb +15 -2
- data/spec/hmac_spec.rb +0 -1
- data/spec/permit_signed_params_spec.rb +31 -1
- data/spec/spec_helper.rb +2 -2
- metadata +8 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 13a5bc0a03b358342f6a21ab3f109424efdc19aa
|
4
|
+
data.tar.gz: 7e3edc29f8b6ebb848f1d928802bc0f6b5cae985
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: c86f3310a481303031dbb197d4d383ea4e18f62b14eaa3a630bc2280147e55c61aadd624d8251de6549cfd1d5c7f58253f9c584cfd9e2cb2b128a3887361dedb
|
7
|
+
data.tar.gz: 40a338b8e5fece5f63a84feff4107f18c9007d2e5105803d584d0aee4c421ea4ccc2e54de6234b3030ac9c4df77f14bbc8bd13186f85eceb7b7079af48c1dc61
|
data/.travis.yml
CHANGED
data/.yardopts
ADDED
data/Gemfile
CHANGED
@@ -10,11 +10,11 @@ when /master/
|
|
10
10
|
gem "rails", github: "rails/rails"
|
11
11
|
when /3-2-stable/
|
12
12
|
gem "rails", github: "rails/rails", branch: "3-2-stable"
|
13
|
+
gem "strong_parameters"
|
13
14
|
when /3-1-stable/
|
14
15
|
gem "rails", github: "rails/rails", branch: "3-1-stable"
|
15
|
-
|
16
|
-
gem "rails", github: "rails/rails", branch: "3-0-stable"
|
16
|
+
gem "strong_parameters"
|
17
17
|
else
|
18
18
|
gem "rails", ENV['RAILS_VERSION']
|
19
|
+
gem "strong_parameters"
|
19
20
|
end
|
20
|
-
|
data/README.md
CHANGED
@@ -30,7 +30,7 @@ What this looks like:
|
|
30
30
|
```
|
31
31
|
|
32
32
|
``` ruby
|
33
|
-
|
33
|
+
UsersController < ApplicationController
|
34
34
|
def create
|
35
35
|
@user = User.find params[:id]
|
36
36
|
@user.update_attributes params[:user]
|
@@ -44,24 +44,19 @@ That's it. You're done. Need to add a field? Pop it in the form. You don't need
|
|
44
44
|
Of course, you're free to continue using the standard `form_for`. `SignedForm` is strictly opt-in. It won't change the
|
45
45
|
way you use standard forms.
|
46
46
|
|
47
|
-
## Alpha Quality Software
|
48
|
-
|
49
|
-
This software should not be considered production ready. At this time it is only suitable for experimentation.
|
50
|
-
|
51
|
-
Now that I've made that disclaimer, you should know that SignedForm is functional.
|
52
|
-
|
53
47
|
## Requirements
|
54
48
|
|
55
49
|
SignedForm requires:
|
56
50
|
|
57
51
|
* Ruby 1.9 or later
|
58
|
-
* Ruby on Rails 4 or 3 ([strong_parameters](https://github.com/rails/strong_parameters) gem
|
52
|
+
* Ruby on Rails 4 or Rails 3 (3.0 not supported) ([strong_parameters](https://github.com/rails/strong_parameters) gem
|
53
|
+
required for Rails 3)
|
59
54
|
|
60
55
|
## Installation
|
61
56
|
|
62
57
|
Add this line to your application's Gemfile:
|
63
58
|
|
64
|
-
gem 'signed_form'
|
59
|
+
gem 'signed_form'
|
65
60
|
|
66
61
|
And then execute:
|
67
62
|
|
@@ -72,8 +67,8 @@ gem. Please set it up as instructed on the linked GitHub repo.
|
|
72
67
|
|
73
68
|
If you're using Rails 4, it works out of the box.
|
74
69
|
|
75
|
-
You'll need to include `SignedForm::ActionController::PermitSignedParams` in the controller(s) you want to use
|
76
|
-
be done application wide by adding the `include` to your ApplicationController.
|
70
|
+
You'll need to include `SignedForm::ActionController::PermitSignedParams` in the controller(s) you want to use
|
71
|
+
SignedForm with. This can be done application wide by adding the `include` to your ApplicationController.
|
77
72
|
|
78
73
|
``` ruby
|
79
74
|
ApplicationController < ActionController::Base
|
@@ -108,6 +103,26 @@ a new secret in the event you remove access to an attribute in a form.
|
|
108
103
|
My above initializer example errs on the side of caution, generating a new secret key every time the app starts up. Only
|
109
104
|
you can decide what is right for you with respect to the secret key.
|
110
105
|
|
106
|
+
### Multiple Access Points
|
107
|
+
|
108
|
+
Take for example the case where you have an administrative backend. You might have `/admin/users/edit`. Users can also
|
109
|
+
change some information about themselves though, so there's `/users/edit` as well. Now you have an admin that gets
|
110
|
+
demoted, but still has a user account. If that admin were to retain a form signature from `/admin/users/edit` they could
|
111
|
+
use that signature to modify the same fields from `/users/edit`. As a means of preventing such access SignedForm provides
|
112
|
+
the `sign_destination` option to `signed_form_for`. Example:
|
113
|
+
|
114
|
+
``` erb
|
115
|
+
<%= signed_form_for(@user, sign_destination: true) do |f| %>
|
116
|
+
<%= f.text_field :name %>
|
117
|
+
<!-- ... -->
|
118
|
+
<% end %>
|
119
|
+
```
|
120
|
+
|
121
|
+
With `sign_destination` enabled, a form generated with a destination of `/admin/users/5` for example will only be
|
122
|
+
accepted at that end point. The form would not be accepted at `/users/5`. So in the event you would like to use
|
123
|
+
SignedForm on forms for the same resource, but different access levels, you have protection against the form being used
|
124
|
+
elsewhere.
|
125
|
+
|
111
126
|
### Caching
|
112
127
|
|
113
128
|
Another consideration to be aware of is caching. If you cache a form, and then change the secret key that form will
|
data/Rakefile
CHANGED
@@ -1,13 +1,6 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
-
require "rdoc/task"
|
3
2
|
|
4
3
|
desc 'Generate documentation.'
|
5
|
-
|
6
|
-
|
7
|
-
rdoc.title = 'SignedForm'
|
8
|
-
|
9
|
-
rdoc.options << '--line-numbers'
|
10
|
-
rdoc.rdoc_files.include('README.md')
|
11
|
-
rdoc.rdoc_files.include('lib/**/*.rb')
|
4
|
+
task :rdoc do
|
5
|
+
system 'yardoc'
|
12
6
|
end
|
13
|
-
|
data/lib/signed_form.rb
CHANGED
@@ -1,10 +1,17 @@
|
|
1
1
|
module SignedForm
|
2
2
|
module ActionController
|
3
|
+
|
4
|
+
# This module is required for parameter verification on the controller.
|
5
|
+
# Include it in controllers that will be receiving signed forms.
|
3
6
|
module PermitSignedParams
|
4
7
|
def self.included(base)
|
5
8
|
base.prepend_before_filter :permit_signed_form_data
|
9
|
+
|
10
|
+
gem 'strong_parameters' unless defined?(::ActionController::Parameters)
|
6
11
|
end
|
7
12
|
|
13
|
+
protected
|
14
|
+
|
8
15
|
def permit_signed_form_data
|
9
16
|
return if request.method == 'GET' || params['form_signature'].blank?
|
10
17
|
|
@@ -13,7 +20,11 @@ module SignedForm
|
|
13
20
|
signature ||= ''
|
14
21
|
|
15
22
|
raise Errors::InvalidSignature, "Form signature is not valid" unless SignedForm::HMAC.verify_hmac signature, data
|
23
|
+
|
16
24
|
allowed_attributes = Marshal.load Base64.strict_decode64(data)
|
25
|
+
options = allowed_attributes.delete(:__options__)
|
26
|
+
|
27
|
+
raise Errors::InvalidURL if options && (!options[:method].to_s.casecmp(request.method) || options[:url] != request.fullpath)
|
17
28
|
|
18
29
|
allowed_attributes.each do |k, v|
|
19
30
|
params[k] = params.require(k).permit(*v)
|
@@ -22,4 +33,3 @@ module SignedForm
|
|
22
33
|
end
|
23
34
|
end
|
24
35
|
end
|
25
|
-
|
@@ -1,6 +1,10 @@
|
|
1
1
|
module SignedForm
|
2
2
|
module ActionView
|
3
3
|
module FormHelper
|
4
|
+
|
5
|
+
# This is a wrapper around ActionView's form_for helper.
|
6
|
+
#
|
7
|
+
# @option options :sign_destination [Boolean] Only the URL given/created will be allowed to receive the form.
|
4
8
|
def signed_form_for(record, options = {}, &block)
|
5
9
|
options[:builder] ||= SignedForm::FormBuilder
|
6
10
|
|
@@ -14,4 +18,3 @@ module SignedForm
|
|
14
18
|
end
|
15
19
|
|
16
20
|
ActionView::Base.send :include, SignedForm::ActionView::FormHelper
|
17
|
-
|
data/lib/signed_form/errors.rb
CHANGED
@@ -11,7 +11,7 @@ module SignedForm
|
|
11
11
|
end
|
12
12
|
end
|
13
13
|
|
14
|
-
def initialize(*)
|
14
|
+
def initialize(*)
|
15
15
|
super
|
16
16
|
if options[:signed_attributes_object]
|
17
17
|
self.signed_attributes_object = options[:signed_attributes_object]
|
@@ -23,12 +23,16 @@ module SignedForm
|
|
23
23
|
|
24
24
|
def form_signature_tag
|
25
25
|
signed_attributes.each { |k,v| v.uniq! if v.is_a?(Array) }
|
26
|
+
signed_attributes[:__options__] = { method: options[:html][:method], url: options[:url] } if options[:sign_destination]
|
26
27
|
encoded_data = Base64.strict_encode64 Marshal.dump(signed_attributes)
|
27
|
-
signature = SignedForm::HMAC
|
28
|
+
signature = SignedForm::HMAC.create_hmac(encoded_data)
|
28
29
|
token = "#{encoded_data}--#{signature}"
|
29
30
|
%(<input type="hidden" name="form_signature" value="#{token}" />\n).html_safe
|
30
31
|
end
|
31
32
|
|
33
|
+
# Wrapper for Rails fields_for
|
34
|
+
#
|
35
|
+
# @see http://api.rubyonrails.org/classes/ActionView/Helpers/FormBuilder.html#method-i-fields_for
|
32
36
|
def fields_for(record_name, record_object = nil, fields_options = {}, &block)
|
33
37
|
hash = {}
|
34
38
|
array = []
|
@@ -46,6 +50,13 @@ module SignedForm
|
|
46
50
|
content
|
47
51
|
end
|
48
52
|
|
53
|
+
# This method is used to add additional fields to sign. A usecase for this may be if you want to add fields later with javascript.
|
54
|
+
#
|
55
|
+
# @example
|
56
|
+
# <%= signed_form_for(@user) do |f| %>
|
57
|
+
# <% f.add_signed_fields :name, :address
|
58
|
+
# <% end %>
|
59
|
+
#
|
49
60
|
def add_signed_fields(*fields)
|
50
61
|
signed_attributes_object.push(*fields)
|
51
62
|
end
|
@@ -58,4 +69,3 @@ module SignedForm
|
|
58
69
|
include Methods
|
59
70
|
end
|
60
71
|
end
|
61
|
-
|
data/lib/signed_form/hmac.rb
CHANGED
data/lib/signed_form/version.rb
CHANGED
data/signed_form.gemspec
CHANGED
@@ -21,9 +21,9 @@ Gem::Specification.new do |spec|
|
|
21
21
|
spec.add_development_dependency "bundler", "~> 1.3"
|
22
22
|
spec.add_development_dependency "rake"
|
23
23
|
spec.add_development_dependency "rspec", "~> 2.13"
|
24
|
-
spec.add_development_dependency "activemodel", ">= 3.
|
24
|
+
spec.add_development_dependency "activemodel", ">= 3.1"
|
25
25
|
|
26
|
-
spec.add_dependency "actionpack", ">= 3.
|
26
|
+
spec.add_dependency "actionpack", ">= 3.1"
|
27
27
|
|
28
28
|
spec.required_ruby_version = '>= 1.9'
|
29
29
|
end
|
data/spec/form_builder_spec.rb
CHANGED
@@ -36,12 +36,25 @@ describe SignedForm::FormBuilder do
|
|
36
36
|
end
|
37
37
|
|
38
38
|
regex = '<form.*>.*<input type="hidden" name="form_signature" ' \
|
39
|
-
'value="
|
39
|
+
'value="\w+={0,2}--\w+".*/>.*' \
|
40
40
|
'<input.*name="user\[name\]".*/>.*' \
|
41
41
|
'</form>'
|
42
42
|
|
43
43
|
content.should =~ Regexp.new(regex, Regexp::MULTILINE)
|
44
44
|
end
|
45
|
+
|
46
|
+
it "should set a target" do
|
47
|
+
content = signed_form_for(User.new, sign_destination: true) do |f|
|
48
|
+
f.text_field :name
|
49
|
+
end
|
50
|
+
|
51
|
+
data = get_data_from_form(content)
|
52
|
+
data.size.should == 2
|
53
|
+
data.should include(:__options__)
|
54
|
+
data[:__options__].should include(:method, :url)
|
55
|
+
data[:__options__][:method].should == :post
|
56
|
+
data[:__options__][:url].should == '/users'
|
57
|
+
end
|
45
58
|
end
|
46
59
|
|
47
60
|
describe "form inputs" do
|
@@ -52,6 +65,7 @@ describe SignedForm::FormBuilder do
|
|
52
65
|
end
|
53
66
|
|
54
67
|
data = get_data_from_form(content)
|
68
|
+
data.size.should == 1
|
55
69
|
data['user'].size.should == 1
|
56
70
|
data['user'].should include(:name)
|
57
71
|
end
|
@@ -135,4 +149,3 @@ describe SignedForm::FormBuilder do
|
|
135
149
|
end
|
136
150
|
end
|
137
151
|
end
|
138
|
-
|
data/spec/hmac_spec.rb
CHANGED
@@ -2,6 +2,8 @@ require 'spec_helper'
|
|
2
2
|
|
3
3
|
class Controller < ActionController::Base
|
4
4
|
include SignedForm::ActionController::PermitSignedParams
|
5
|
+
|
6
|
+
public :permit_signed_form_data
|
5
7
|
end
|
6
8
|
|
7
9
|
describe SignedForm::ActionController::PermitSignedParams do
|
@@ -10,7 +12,7 @@ describe SignedForm::ActionController::PermitSignedParams do
|
|
10
12
|
before do
|
11
13
|
SignedForm::HMAC.secret_key = "abc123"
|
12
14
|
|
13
|
-
Controller.any_instance.stub(request: double('request', method: 'POST'))
|
15
|
+
Controller.any_instance.stub(request: double('request', method: 'POST', fullpath: '/users'))
|
14
16
|
Controller.any_instance.stub(params: { "user" => { name: "Erich Menge", occupation: 'developer' } })
|
15
17
|
end
|
16
18
|
|
@@ -33,4 +35,32 @@ describe SignedForm::ActionController::PermitSignedParams do
|
|
33
35
|
params.should_receive(:permit).with(:name).and_return(params)
|
34
36
|
controller.permit_signed_form_data
|
35
37
|
end
|
38
|
+
|
39
|
+
it "should verify current url matches targeted url" do
|
40
|
+
params = controller.params
|
41
|
+
|
42
|
+
data = Base64.strict_encode64(Marshal.dump("user" => [:name], :__options__ => { method: 'post', url: '/users' }))
|
43
|
+
signature = SignedForm::HMAC.create_hmac(data)
|
44
|
+
|
45
|
+
params['form_signature'] = "#{data}--#{signature}"
|
46
|
+
|
47
|
+
params.stub(:require).with('user').and_return(params)
|
48
|
+
params.stub(:permit).with(:name).and_return(params)
|
49
|
+
controller.request.should_receive(:fullpath).and_return '/users'
|
50
|
+
controller.permit_signed_form_data
|
51
|
+
end
|
52
|
+
|
53
|
+
it "should reject if url doesn't match" do
|
54
|
+
params = controller.params
|
55
|
+
|
56
|
+
data = Base64.strict_encode64(Marshal.dump("user" => [:name], :__options__ => { method: 'post', url: '/admin' }))
|
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)
|
63
|
+
|
64
|
+
expect { controller.permit_signed_form_data }.to raise_error(SignedForm::Errors::InvalidURL)
|
65
|
+
end
|
36
66
|
end
|
data/spec/spec_helper.rb
CHANGED
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: signed_form
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Erich Menge
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2013-
|
11
|
+
date: 2013-04-04 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: bundler
|
@@ -58,28 +58,28 @@ dependencies:
|
|
58
58
|
requirements:
|
59
59
|
- - '>='
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version: '3.
|
61
|
+
version: '3.1'
|
62
62
|
type: :development
|
63
63
|
prerelease: false
|
64
64
|
version_requirements: !ruby/object:Gem::Requirement
|
65
65
|
requirements:
|
66
66
|
- - '>='
|
67
67
|
- !ruby/object:Gem::Version
|
68
|
-
version: '3.
|
68
|
+
version: '3.1'
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: actionpack
|
71
71
|
requirement: !ruby/object:Gem::Requirement
|
72
72
|
requirements:
|
73
73
|
- - '>='
|
74
74
|
- !ruby/object:Gem::Version
|
75
|
-
version: '3.
|
75
|
+
version: '3.1'
|
76
76
|
type: :runtime
|
77
77
|
prerelease: false
|
78
78
|
version_requirements: !ruby/object:Gem::Requirement
|
79
79
|
requirements:
|
80
80
|
- - '>='
|
81
81
|
- !ruby/object:Gem::Version
|
82
|
-
version: '3.
|
82
|
+
version: '3.1'
|
83
83
|
description: Rails signed form security
|
84
84
|
email:
|
85
85
|
- erichmenge@gmail.com
|
@@ -90,6 +90,7 @@ files:
|
|
90
90
|
- .gitignore
|
91
91
|
- .rspec
|
92
92
|
- .travis.yml
|
93
|
+
- .yardopts
|
93
94
|
- Gemfile
|
94
95
|
- LICENSE.txt
|
95
96
|
- README.md
|
@@ -135,3 +136,4 @@ test_files:
|
|
135
136
|
- spec/hmac_spec.rb
|
136
137
|
- spec/permit_signed_params_spec.rb
|
137
138
|
- spec/spec_helper.rb
|
139
|
+
has_rdoc:
|