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