signed_form 0.0.1.pre1
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 +7 -0
- data/.gitignore +17 -0
- data/.rspec +2 -0
- data/.travis.yml +7 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +98 -0
- data/Rakefile +1 -0
- data/lib/signed_form/action_controller/permit_signed_params.rb +23 -0
- data/lib/signed_form/action_view/form_helper.rb +17 -0
- data/lib/signed_form/errors.rb +6 -0
- data/lib/signed_form/form_builder.rb +43 -0
- data/lib/signed_form/hmac.rb +36 -0
- data/lib/signed_form/version.rb +9 -0
- data/lib/signed_form.rb +7 -0
- data/signed_form.gemspec +28 -0
- data/spec/signed_form/hmac_spec.rb +35 -0
- data/spec/spec_helper.rb +11 -0
- metadata +119 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a3cc9c5608222124cd6ed26784e343bec0c7d8c0
|
4
|
+
data.tar.gz: bed58f01bae751fefee75375fc9584a0fec4ea44
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: c2a685ab4786e2b6615f32d8431108fd1d4601cb180caeddb09c05de8b774e8f4f6a5066017fb59406b6687ac95baa5837350efa1560936ecb0986c512fa6eb3
|
7
|
+
data.tar.gz: fa2731445d7c927966bfb1177a11a120282a907e77c9e493527ec9ce75a9405183d0f6aa6084602ab27265c79d961dd89562a09006f2e85b8d68964f4e59a348
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 Erich Menge
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
# SignedForm
|
2
|
+
|
3
|
+
SignedForm brings new convenience and security to your Rails 4 or Rails 3 application.
|
4
|
+
|
5
|
+
## How It Works
|
6
|
+
|
7
|
+
Traditionally, when you create a form with Rails you enter your fields using something like `f.input :name` and etc.
|
8
|
+
Once you're done making your form you need to make sure that you've either set those parameters as accessible in the
|
9
|
+
model (Rails 3) or use `permit` (Rails 4). This is redundant. Why would you make a form for a user to fill out and
|
10
|
+
then not accept their input? Thus you need to always maintain this synchronization.
|
11
|
+
|
12
|
+
SignedForm generates a list of attributes that you have in your form and attaches them to be submitted with the form
|
13
|
+
along with a HMAC-SHA1 signature of those attributes to protect them from tampering. That means no more `permit` and
|
14
|
+
no more `attr_accessible`. It just works.
|
15
|
+
|
16
|
+
What this looks like:
|
17
|
+
|
18
|
+
``` erb
|
19
|
+
<%= signed_form_for(@user) do |f| %>
|
20
|
+
<%= f.text_field :name %>
|
21
|
+
<%= f.text_field :address %>
|
22
|
+
<%= f.submit %>
|
23
|
+
<% end %>
|
24
|
+
```
|
25
|
+
|
26
|
+
``` ruby
|
27
|
+
UserController < ApplicationController
|
28
|
+
def create
|
29
|
+
@user = User.find params[:id]
|
30
|
+
@user.update_attributes params[:user]
|
31
|
+
end
|
32
|
+
end
|
33
|
+
```
|
34
|
+
|
35
|
+
That's it. You're done. Need to add a field? Pop it in the form. You don't need to then update a list of attributes.
|
36
|
+
`signed_form_for` works just like the standard `form_for`.
|
37
|
+
|
38
|
+
Of course, you're free to continue using the standard `form_for`. `SignedForm` is strictly opt-in. It won't change the
|
39
|
+
way you use standard forms.
|
40
|
+
|
41
|
+
## Alpha Quality Software
|
42
|
+
|
43
|
+
Because of the security sensitive nature of this gem I'm releasing this as 0.0.1.pre1 until I can get some more eyeballs
|
44
|
+
on the code. This software should not be considered production ready. At this time it is only suitable for
|
45
|
+
experimentation.
|
46
|
+
|
47
|
+
Now that I've made that disclaimer, you should know that SignedForm is functional.
|
48
|
+
|
49
|
+
## Requirements
|
50
|
+
|
51
|
+
SignedForm requires:
|
52
|
+
|
53
|
+
* Ruby 1.9 or later
|
54
|
+
* Ruby on Rails 4 or 3 ([strong_parameters](https://github.com/rails/strong_parameters) gem required for Rails 3)
|
55
|
+
|
56
|
+
## Installation
|
57
|
+
|
58
|
+
Add this line to your application's Gemfile:
|
59
|
+
|
60
|
+
gem 'signed_form', '0.0.1.pre1'
|
61
|
+
|
62
|
+
And then execute:
|
63
|
+
|
64
|
+
$ bundle
|
65
|
+
|
66
|
+
If you're using Rails 3, you'll also need to install the [strong_parameters](https://github.com/rails/strong_parameters)
|
67
|
+
gem. Please set it up as instructed on the linked GitHub repo.
|
68
|
+
|
69
|
+
If you're using Rails 4, it works out of the box.
|
70
|
+
|
71
|
+
You'll also need to create an initializer:
|
72
|
+
|
73
|
+
$ echo 'SignedForm::HMAC.secret_key = SecureRandom.hex(64)' > config/initializers/signed_form.rb
|
74
|
+
|
75
|
+
**IMPORTANT** Please read below for information regarding this secret key.
|
76
|
+
|
77
|
+
## Special Considerations
|
78
|
+
|
79
|
+
If you're running only a single application server the above initializer should work great for you, with a couple of
|
80
|
+
caveats. If a user is in process of filling out a form and you restart your server, their form will be invalidated.
|
81
|
+
You could pick a secret key using `rake secret` and put that in the initializer instead, but then in the event you
|
82
|
+
remove a field someone could still access it using the old signature if some malicious person were to keep it around.
|
83
|
+
|
84
|
+
If you're running multiple application servers, the above initializer will not work. You'll need to keep the key in sync
|
85
|
+
between all the servers. The security caveat with that is that if you ever remove a field from a form without updating
|
86
|
+
that secret key, a malicious user could still access the field with the old signature. So you'll probably want to
|
87
|
+
choose a new secret in the event you remove access to an attribute in a form.
|
88
|
+
|
89
|
+
My above initializer example errs on the side of caution, generating a new secret key every time the app starts up. Only
|
90
|
+
you can decide what is right for you with respect to the secret key.
|
91
|
+
|
92
|
+
## Contributing
|
93
|
+
|
94
|
+
1. Fork it
|
95
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
96
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
97
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
98
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require "bundler/gem_tasks"
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module SignedForm
|
2
|
+
module PermitSignedParams
|
3
|
+
def permit_signed_form_data
|
4
|
+
return if request.method == 'GET' || params['form_signature'].blank?
|
5
|
+
|
6
|
+
data, signature = params['form_signature'].split('--', 2)
|
7
|
+
|
8
|
+
data ||= ''
|
9
|
+
signature ||= ''
|
10
|
+
|
11
|
+
raise Errors::InvalidSignature, "Form signature is not valid" unless SignedForm::HMAC.verify_hmac signature, data
|
12
|
+
allowed_attributes = Marshal.load Base64.strict_decode64(data)
|
13
|
+
|
14
|
+
allowed_attributes.each do |k, v|
|
15
|
+
params[k] = params.require(k).permit(*v)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
ActionController::Base.send :include, SignedForm::PermitSignedParams
|
22
|
+
ActionController::Base.prepend_before_filter :permit_signed_form_data
|
23
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SignedForm
|
2
|
+
module ActionView
|
3
|
+
module FormHelper
|
4
|
+
def signed_form_for(record, options = {}, &block)
|
5
|
+
options[:builder] = SignedForm::FormBuilder
|
6
|
+
|
7
|
+
form_for(record, options) do |f|
|
8
|
+
output = capture(f, &block)
|
9
|
+
f.form_signature_tag + output
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
ActionView::Base.send :include, SignedForm::ActionView::FormHelper
|
17
|
+
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module SignedForm
|
2
|
+
class FormBuilder < ::ActionView::Helpers::FormBuilder
|
3
|
+
attr_accessor :signed_attributes, :signed_attributes_object
|
4
|
+
|
5
|
+
# Rails 3 uses strings, Rails 4 uses symbols
|
6
|
+
(field_helpers.map(&:to_s) - %w(label fields_for)).each do |h|
|
7
|
+
define_method(h) do |field, *args|
|
8
|
+
signed_attributes_object << field
|
9
|
+
super(field, *args)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def initialize(*)
|
14
|
+
super
|
15
|
+
if options[:signed_attributes_object]
|
16
|
+
self.signed_attributes_object = options[:signed_attributes_object]
|
17
|
+
else
|
18
|
+
self.signed_attributes = HashWithIndifferentAccess.new(object_name => [])
|
19
|
+
self.signed_attributes_object = signed_attributes[object_name]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def form_signature_tag
|
24
|
+
encoded_data = Base64.strict_encode64 Marshal.dump(signed_attributes)
|
25
|
+
signature = SignedForm::HMAC::create_hmac(encoded_data)
|
26
|
+
token = "#{encoded_data}--#{signature}"
|
27
|
+
%(<input type="hidden" name="form_signature" value="#{token}" />\n).html_safe
|
28
|
+
end
|
29
|
+
|
30
|
+
def fields_for(record_name, record_object = nil, fields_options = {}, &block)
|
31
|
+
hash = HashWithIndifferentAccess.new
|
32
|
+
if nested_attributes_association?(record_name)
|
33
|
+
hash["#{record_name}_attributes"] = fields_options[:signed_attributes_object] = []
|
34
|
+
else
|
35
|
+
hash[record_name] = fields_options[:signed_attributes_object] = []
|
36
|
+
end
|
37
|
+
|
38
|
+
signed_attributes_object << hash
|
39
|
+
super
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require 'openssl'
|
2
|
+
|
3
|
+
module SignedForm
|
4
|
+
module HMAC
|
5
|
+
class << self
|
6
|
+
attr_accessor :secret_key
|
7
|
+
|
8
|
+
def create_hmac(data)
|
9
|
+
if secret_key.nil? || secret_key.empty?
|
10
|
+
raise Errors::NoSecretKey, "Please consult the README for instructions on creating a secret key"
|
11
|
+
end
|
12
|
+
|
13
|
+
OpenSSL::HMAC.hexdigest OpenSSL::Digest::SHA1.new, secret_key, data
|
14
|
+
end
|
15
|
+
|
16
|
+
def verify_hmac(signature, data)
|
17
|
+
if secret_key.nil? || secret_key.empty?
|
18
|
+
raise Errors::NoSecretKey, "Please consult the README for instructions on creating a secret key"
|
19
|
+
end
|
20
|
+
|
21
|
+
secure_compare OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret_key, data), signature
|
22
|
+
end
|
23
|
+
|
24
|
+
# After the Rack implementation
|
25
|
+
def secure_compare(a, b)
|
26
|
+
return false unless a.bytesize == b.bytesize
|
27
|
+
|
28
|
+
l = a.unpack("C*")
|
29
|
+
|
30
|
+
r, i = 0, -1
|
31
|
+
b.each_byte { |v| r |= v ^ l[i+=1] }
|
32
|
+
r == 0
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
data/lib/signed_form.rb
ADDED
data/signed_form.gemspec
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'signed_form/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |spec|
|
7
|
+
spec.name = "signed_form"
|
8
|
+
spec.version = SignedForm::VERSION
|
9
|
+
spec.authors = ["Erich Menge"]
|
10
|
+
spec.email = ["erichmenge@gmail.com"]
|
11
|
+
spec.description = %q{Rails signed form security}
|
12
|
+
spec.summary = %q{Rails signed form security}
|
13
|
+
spec.homepage = "https://github.com/erichmenge/signed_form"
|
14
|
+
spec.license = "MIT"
|
15
|
+
|
16
|
+
spec.files = `git ls-files`.split($/)
|
17
|
+
spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
|
18
|
+
spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
|
19
|
+
spec.require_paths = ["lib"]
|
20
|
+
|
21
|
+
spec.add_development_dependency "bundler", "~> 1.3"
|
22
|
+
spec.add_development_dependency "rake"
|
23
|
+
spec.add_development_dependency "rspec", "~> 2.13"
|
24
|
+
|
25
|
+
spec.add_dependency "actionpack", ">= 3.0"
|
26
|
+
|
27
|
+
spec.required_ruby_version = '>= 1.9'
|
28
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe SignedForm::HMAC do
|
4
|
+
describe 'create_hmac' do
|
5
|
+
it 'should raise if no key is given' do
|
6
|
+
expect { SignedForm::HMAC.create_hmac "foo" }.to raise_error(SignedForm::Errors::NoSecretKey)
|
7
|
+
end
|
8
|
+
|
9
|
+
context 'when a key is present' do
|
10
|
+
before { SignedForm::HMAC.secret_key = "superdupersecret" }
|
11
|
+
after { SignedForm::HMAC.secret_key = nil }
|
12
|
+
|
13
|
+
it 'should create a hex signature' do
|
14
|
+
SignedForm::HMAC.create_hmac("my signed message").length.should == 40
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe 'verify_hmac' do
|
20
|
+
it 'should raise if no key is given' do
|
21
|
+
expect { SignedForm::HMAC.verify_hmac 'foo', 'bar' }.to raise_error(SignedForm::Errors::NoSecretKey)
|
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" }
|
29
|
+
|
30
|
+
specify { SignedForm::HMAC.verify_hmac(signature, "My super secret").should be_true }
|
31
|
+
specify { SignedForm::HMAC.verify_hmac(signature, "My bad secret").should_not be_true }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'action_view'
|
2
|
+
require 'action_controller'
|
3
|
+
require 'signed_form'
|
4
|
+
|
5
|
+
RSpec.configure do |config|
|
6
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
7
|
+
config.run_all_when_everything_filtered = true
|
8
|
+
config.filter_run :focus
|
9
|
+
|
10
|
+
config.order = 'random'
|
11
|
+
end
|
metadata
ADDED
@@ -0,0 +1,119 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: signed_form
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1.pre1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Erich Menge
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2013-03-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: bundler
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ~>
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '1.3'
|
20
|
+
type: :development
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ~>
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rake
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - '>='
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ~>
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '2.13'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ~>
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '2.13'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: actionpack
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '3.0'
|
62
|
+
type: :runtime
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - '>='
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '3.0'
|
69
|
+
description: Rails signed form security
|
70
|
+
email:
|
71
|
+
- erichmenge@gmail.com
|
72
|
+
executables: []
|
73
|
+
extensions: []
|
74
|
+
extra_rdoc_files: []
|
75
|
+
files:
|
76
|
+
- .gitignore
|
77
|
+
- .rspec
|
78
|
+
- .travis.yml
|
79
|
+
- Gemfile
|
80
|
+
- LICENSE.txt
|
81
|
+
- README.md
|
82
|
+
- Rakefile
|
83
|
+
- lib/signed_form.rb
|
84
|
+
- lib/signed_form/action_controller/permit_signed_params.rb
|
85
|
+
- lib/signed_form/action_view/form_helper.rb
|
86
|
+
- lib/signed_form/errors.rb
|
87
|
+
- lib/signed_form/form_builder.rb
|
88
|
+
- lib/signed_form/hmac.rb
|
89
|
+
- lib/signed_form/version.rb
|
90
|
+
- signed_form.gemspec
|
91
|
+
- spec/signed_form/hmac_spec.rb
|
92
|
+
- spec/spec_helper.rb
|
93
|
+
homepage: https://github.com/erichmenge/signed_form
|
94
|
+
licenses:
|
95
|
+
- MIT
|
96
|
+
metadata: {}
|
97
|
+
post_install_message:
|
98
|
+
rdoc_options: []
|
99
|
+
require_paths:
|
100
|
+
- lib
|
101
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
102
|
+
requirements:
|
103
|
+
- - '>='
|
104
|
+
- !ruby/object:Gem::Version
|
105
|
+
version: '1.9'
|
106
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - '>'
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: 1.3.1
|
111
|
+
requirements: []
|
112
|
+
rubyforge_project:
|
113
|
+
rubygems_version: 2.0.0
|
114
|
+
signing_key:
|
115
|
+
specification_version: 4
|
116
|
+
summary: Rails signed form security
|
117
|
+
test_files:
|
118
|
+
- spec/signed_form/hmac_spec.rb
|
119
|
+
- spec/spec_helper.rb
|