signed_form 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ddf93bef4e43341d61d7f414960eec2b5823eca9
4
- data.tar.gz: a6e68f939dc46cb096fd6db9425ceca4559b682c
3
+ metadata.gz: 13a5bc0a03b358342f6a21ab3f109424efdc19aa
4
+ data.tar.gz: 7e3edc29f8b6ebb848f1d928802bc0f6b5cae985
5
5
  SHA512:
6
- metadata.gz: 5665ef2bc0cf38caa6b908f3424c0b4f0a9fe5db124900556e457a7ca9891abec70be1fe75ed74311159ed3b89c85756fd45d0b9f639f16a913a54b815833a49
7
- data.tar.gz: ecafd68f4aa85fbc3edb3321e4306c8603677ed30b6862c9b46e7dd4a72a5a193888e444d02dd4de2f10c8b27a303f199f550ba8c9ddf333c54685be163dc513
6
+ metadata.gz: c86f3310a481303031dbb197d4d383ea4e18f62b14eaa3a630bc2280147e55c61aadd624d8251de6549cfd1d5c7f58253f9c584cfd9e2cb2b128a3887361dedb
7
+ data.tar.gz: 40a338b8e5fece5f63a84feff4107f18c9007d2e5105803d584d0aee4c421ea4ccc2e54de6234b3030ac9c4df77f14bbc8bd13186f85eceb7b7079af48c1dc61
data/.travis.yml CHANGED
@@ -7,8 +7,6 @@ rvm:
7
7
  - 2.0.0
8
8
 
9
9
  env:
10
- - RAILS_VERSION=3-0-stable
11
10
  - RAILS_VERSION=3-1-stable
12
11
  - RAILS_VERSION=3-2-stable
13
12
  - RAILS_VERSION=master
14
-
data/.yardopts ADDED
@@ -0,0 +1,3 @@
1
+ --exclude features
2
+ --no-private
3
+ --markup markdown
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
- when /3-0-stable/
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
- UserController < ApplicationController
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 required for Rails 3)
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', '~> 0.0.1'
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 SignedForm with. This can
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
- RDoc::Task.new(:rdoc) do |rdoc|
6
- rdoc.rdoc_dir = 'rdoc'
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
@@ -7,4 +7,3 @@ require "signed_form/form_builder"
7
7
  require "signed_form/hmac"
8
8
  require "signed_form/action_view/form_helper"
9
9
  require "signed_form/action_controller/permit_signed_params"
10
-
@@ -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
-
@@ -2,5 +2,6 @@ module SignedForm
2
2
  module Errors
3
3
  class NoSecretKey < StandardError; end
4
4
  class InvalidSignature < StandardError; end
5
+ class InvalidURL < StandardError; end
5
6
  end
6
7
  end
@@ -11,7 +11,7 @@ module SignedForm
11
11
  end
12
12
  end
13
13
 
14
- def initialize(*) #:nodoc:#
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::create_hmac(encoded_data)
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
-
@@ -22,6 +22,8 @@ module SignedForm
22
22
  secure_compare OpenSSL::HMAC.hexdigest(OpenSSL::Digest::SHA1.new, secret_key, data), signature
23
23
  end
24
24
 
25
+ private
26
+
25
27
  # After the Rack implementation
26
28
  def secure_compare(a, b)
27
29
  return false unless a.bytesize == b.bytesize
@@ -1,9 +1,8 @@
1
1
  module SignedForm
2
2
  MAJOR = 0
3
- MINOR = 0
4
- PATCH = 1
3
+ MINOR = 1
4
+ PATCH = 0
5
5
  PRE = nil
6
6
 
7
7
  VERSION = [MAJOR, MINOR, PATCH, PRE].compact.join '.'
8
8
  end
9
-
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.0"
24
+ spec.add_development_dependency "activemodel", ">= 3.1"
25
25
 
26
- spec.add_dependency "actionpack", ">= 3.0"
26
+ spec.add_dependency "actionpack", ">= 3.1"
27
27
 
28
28
  spec.required_ruby_version = '>= 1.9'
29
29
  end
@@ -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="BAh7BkkiCXVzZXIGOgZFRlsGOgluYW1l--e8f61481cb89382653c1f9de617e9a47e22c7da5".*/>.*' \
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
@@ -32,4 +32,3 @@ describe SignedForm::HMAC do
32
32
  end
33
33
  end
34
34
  end
35
-
@@ -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
@@ -30,11 +30,11 @@ module SignedFormViewHelper
30
30
  end
31
31
 
32
32
  def user_path(*)
33
- '/'
33
+ '/users'
34
34
  end
35
35
 
36
36
  def polymorphic_path(*)
37
- '/'
37
+ '/users'
38
38
  end
39
39
 
40
40
  def _routes(*)
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.1
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-03-27 00:00:00.000000000 Z
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.0'
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.0'
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.0'
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.0'
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: