strong_parameters 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.
@@ -0,0 +1,20 @@
1
+ Copyright 2012 David Heinemeier Hansson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,44 @@
1
+ = Strong Parameters
2
+
3
+ With this plugin Action Controller parameters are forbidden to be used in Active Model mass assignments until they have been whitelisted. This means you'll have to make a conscious choice about which attributes to allow for mass updating and thus prevent accidentally exposing that which shouldn't be exposed.
4
+
5
+ In addition, parameters can be marked as required and flow through a predefined raise/rescue flow to end up as a 400 Bad Request with no effort.
6
+
7
+ class PeopleController < ActionController::Base
8
+ # This will raise an ActiveModel::ForbiddenAttributes exception because it's using mass assignment
9
+ # without an explicit permit step.
10
+ def create
11
+ Person.create(params[:person])
12
+ end
13
+
14
+ # This will pass with flying colors as long as there's a person key in the parameters, otherwise
15
+ # it'll raise a ActionController::MissingParameter exception, which will get caught by
16
+ # ActionController::Base and turned into that 400 Bad Request reply.
17
+ def update
18
+ redirect_to current_account.people.find(params[:id]).tap do |person|
19
+ person.update_attributes!(person_params)
20
+ end
21
+ end
22
+
23
+ private
24
+ # Using a private method to encapsulate the permissible parameters is just a good pattern
25
+ # since you'll be able to reuse the same permit list between create and update. Also, you
26
+ # can specialize this method with per-user checking of permissible attributes.
27
+ def person_params
28
+ params.required(:person).permit(:name, :age)
29
+ end
30
+ end
31
+
32
+ Thanks to Nick Kallen for the permit idea!
33
+
34
+ == Todos
35
+
36
+ * Make this play nice with nested parameters [???]. Design:
37
+
38
+ params.permit(:name, friends: [ :name, { family: [ :name ] }])
39
+
40
+ * Automatically permit parameters coming from a signed form [Yehuda]
41
+
42
+ == Compatibility
43
+
44
+ Due to a testing issue, this plugin is only fully compatible with rails/3-2-stable rev 275ee0dc7b and forward as well as rails/master rev b49a7ddce1 and forward.
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'StrongParameters'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.rdoc')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+
24
+
25
+
26
+ Bundler::GemHelper.install_tasks
27
+
28
+ require 'rake/testtask'
29
+
30
+ Rake::TestTask.new(:test) do |t|
31
+ t.libs << 'lib'
32
+ t.libs << 'test'
33
+ t.pattern = 'test/**/*_test.rb'
34
+ t.verbose = false
35
+ end
36
+
37
+
38
+ task :default => :test
@@ -0,0 +1,81 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/hash/indifferent_access'
3
+ require 'action_controller'
4
+
5
+ module ActionController
6
+ class ParameterMissing < IndexError
7
+ attr_reader :param
8
+
9
+ def initialize(param)
10
+ @param = param
11
+ super("key not found: #{param}")
12
+ end
13
+ end
14
+
15
+ class Parameters < ActiveSupport::HashWithIndifferentAccess
16
+ attr_accessor :permitted
17
+ alias :permitted? :permitted
18
+
19
+ def initialize(attributes = nil)
20
+ super(attributes)
21
+ @permitted = false
22
+ end
23
+
24
+ def permit!
25
+ @permitted = true
26
+ self
27
+ end
28
+
29
+ def required(key)
30
+ self[key].presence || raise(ActionController::ParameterMissing.new(key))
31
+ end
32
+
33
+ def permit(*keys)
34
+ slice(*keys).permit!
35
+ end
36
+
37
+ def [](key)
38
+ convert_hashes_to_parameters(key, super)
39
+ end
40
+
41
+ def fetch(key)
42
+ unless block_given? || key?(key)
43
+ raise ActionController::ParameterMissing.new(key)
44
+ end
45
+
46
+ convert_hashes_to_parameters(key, super)
47
+ end
48
+
49
+ def slice(*keys)
50
+ self.class.new(super)
51
+ end
52
+
53
+ private
54
+ def convert_hashes_to_parameters(key, value)
55
+ if value.is_a?(Parameters) || !value.is_a?(Hash)
56
+ value
57
+ else
58
+ # Convert to Parameters on first access
59
+ self[key] = self.class.new(value)
60
+ end
61
+ end
62
+ end
63
+
64
+ module StrongParameters
65
+ extend ActiveSupport::Concern
66
+
67
+ included do
68
+ rescue_from(ActionController::ParameterMissing) { head :bad_request }
69
+ end
70
+
71
+ def params
72
+ @_params ||= Parameters.new(request.parameters)
73
+ end
74
+
75
+ def params=(val)
76
+ @_params = val.is_a?(Hash) ? Parameters.new(val) : val
77
+ end
78
+ end
79
+ end
80
+
81
+ ActionController::Base.send :include, ActionController::StrongParameters
@@ -0,0 +1,16 @@
1
+ module ActiveModel
2
+ class ForbiddenAttributes < StandardError
3
+ end
4
+
5
+ module ForbiddenAttributesProtection
6
+ def sanitize_for_mass_assignment(new_attributes, options = {})
7
+ if !new_attributes.respond_to?(:permitted?) || (new_attributes.respond_to?(:permitted?) && new_attributes.permitted?)
8
+ super
9
+ else
10
+ raise ActiveModel::ForbiddenAttributes
11
+ end
12
+ end
13
+ end
14
+ end
15
+
16
+ ActiveModel.autoload :ForbiddenAttributesProtection
@@ -0,0 +1,2 @@
1
+ require 'action_controller/parameters'
2
+ require 'active_model/forbidden_attributes_protection'
@@ -0,0 +1,3 @@
1
+ module StrongParameters
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,25 @@
1
+ require 'test_helper'
2
+
3
+ class BooksController < ActionController::Base
4
+ def create
5
+ params.required(:book).required(:name)
6
+ head :ok
7
+ end
8
+ end
9
+
10
+ class ActionControllerRequiredParamsTest < ActionController::TestCase
11
+ tests BooksController
12
+
13
+ test "missing required parameters will raise exception" do
14
+ post :create, { magazine: { name: "Mjallo!" } }
15
+ assert_response :bad_request
16
+
17
+ post :create, { book: { title: "Mjallo!" } }
18
+ assert_response :bad_request
19
+ end
20
+
21
+ test "required parameters that are present will not raise" do
22
+ post :create, { book: { name: "Mjallo!" } }
23
+ assert_response :ok
24
+ end
25
+ end
@@ -0,0 +1,25 @@
1
+ require 'test_helper'
2
+
3
+ class PeopleController < ActionController::Base
4
+ def create
5
+ render text: params[:person].permitted? ? "untainted" : "tainted"
6
+ end
7
+
8
+ def create_with_permit
9
+ render text: params[:person].permit(:name).permitted? ? "untainted" : "tainted"
10
+ end
11
+ end
12
+
13
+ class ActionControllerTaintedParamsTest < ActionController::TestCase
14
+ tests PeopleController
15
+
16
+ test "parameters are tainted" do
17
+ post :create, { person: { name: "Mjallo!" } }
18
+ assert_equal "tainted", response.body
19
+ end
20
+
21
+ test "parameters can be permitted and are then not tainted" do
22
+ post :create_with_permit, { person: { name: "Mjallo!" } }
23
+ assert_equal "untainted", response.body
24
+ end
25
+ end
@@ -0,0 +1,30 @@
1
+ require 'test_helper'
2
+
3
+ class Person
4
+ include ActiveModel::MassAssignmentSecurity
5
+ include ActiveModel::ForbiddenAttributesProtection
6
+
7
+ public :sanitize_for_mass_assignment
8
+ end
9
+
10
+ class ActiveModelMassUpdateProtectionTest < ActiveSupport::TestCase
11
+ test "forbidden attributes cannot be used for mass updating" do
12
+ assert_raises(ActiveModel::ForbiddenAttributes) do
13
+ Person.new.sanitize_for_mass_assignment(ActionController::Parameters.new(a: "b"))
14
+ end
15
+ end
16
+
17
+ test "permitted attributes can be used for mass updating" do
18
+ assert_nothing_raised do
19
+ assert_equal({ "a" => "b" },
20
+ Person.new.sanitize_for_mass_assignment(ActionController::Parameters.new(a: "b").permit(:a)))
21
+ end
22
+ end
23
+
24
+ test "regular attributes should still be allowed" do
25
+ assert_nothing_raised do
26
+ assert_equal({ a: "b" },
27
+ Person.new.sanitize_for_mass_assignment(a: "b"))
28
+ end
29
+ end
30
+ end
File without changes
@@ -0,0 +1,88 @@
1
+  (0.2ms) begin transaction
2
+  (0.0ms) rollback transaction
3
+  (0.0ms) begin transaction
4
+  (0.0ms) rollback transaction
5
+  (0.0ms) begin transaction
6
+  (0.0ms) rollback transaction
7
+  (0.0ms) begin transaction
8
+  (0.0ms) rollback transaction
9
+  (0.0ms) begin transaction
10
+  (0.0ms) rollback transaction
11
+  (0.0ms) begin transaction
12
+  (0.0ms) rollback transaction
13
+  (0.2ms) begin transaction
14
+  (0.0ms) rollback transaction
15
+  (0.0ms) begin transaction
16
+  (0.0ms) rollback transaction
17
+  (0.0ms) begin transaction
18
+  (0.0ms) rollback transaction
19
+  (0.0ms) begin transaction
20
+  (0.0ms) rollback transaction
21
+  (0.0ms) begin transaction
22
+  (0.0ms) rollback transaction
23
+  (0.0ms) begin transaction
24
+  (0.0ms) rollback transaction
25
+  (0.2ms) begin transaction
26
+  (0.0ms) rollback transaction
27
+  (0.0ms) begin transaction
28
+  (0.0ms) rollback transaction
29
+  (0.0ms) begin transaction
30
+  (0.0ms) rollback transaction
31
+  (0.0ms) begin transaction
32
+  (0.0ms) rollback transaction
33
+  (0.0ms) begin transaction
34
+  (0.0ms) rollback transaction
35
+  (0.0ms) begin transaction
36
+  (0.0ms) rollback transaction
37
+  (0.2ms) begin transaction
38
+  (0.0ms) rollback transaction
39
+  (0.0ms) begin transaction
40
+  (0.0ms) rollback transaction
41
+  (0.0ms) begin transaction
42
+  (0.0ms) rollback transaction
43
+  (0.0ms) begin transaction
44
+  (0.0ms) rollback transaction
45
+  (0.0ms) begin transaction
46
+  (0.0ms) rollback transaction
47
+  (0.0ms) begin transaction
48
+  (0.0ms) rollback transaction
49
+  (0.2ms) begin transaction
50
+  (0.0ms) rollback transaction
51
+  (0.0ms) begin transaction
52
+  (0.0ms) rollback transaction
53
+  (0.0ms) begin transaction
54
+  (0.0ms) rollback transaction
55
+  (0.0ms) begin transaction
56
+  (0.0ms) rollback transaction
57
+  (0.0ms) begin transaction
58
+  (0.0ms) rollback transaction
59
+  (0.0ms) begin transaction
60
+  (0.0ms) rollback transaction
61
+  (0.2ms) begin transaction
62
+  (0.0ms) rollback transaction
63
+  (0.0ms) begin transaction
64
+  (0.0ms) rollback transaction
65
+  (0.0ms) begin transaction
66
+  (0.0ms) rollback transaction
67
+  (0.0ms) begin transaction
68
+  (0.0ms) rollback transaction
69
+  (0.0ms) begin transaction
70
+  (0.0ms) rollback transaction
71
+  (0.0ms) begin transaction
72
+  (0.0ms) rollback transaction
73
+  (0.2ms) begin transaction
74
+  (0.0ms) rollback transaction
75
+  (0.0ms) begin transaction
76
+  (0.0ms) rollback transaction
77
+  (0.0ms) begin transaction
78
+  (0.0ms) rollback transaction
79
+  (0.0ms) begin transaction
80
+  (0.0ms) rollback transaction
81
+  (0.0ms) begin transaction
82
+  (0.0ms) rollback transaction
83
+  (0.0ms) begin transaction
84
+  (0.0ms) rollback transaction
85
+  (0.3ms) begin transaction
86
+  (0.0ms) rollback transaction
87
+  (0.3ms) begin transaction
88
+  (0.0ms) rollback transaction
@@ -0,0 +1,10 @@
1
+ require 'test_helper'
2
+ require 'action_controller/parameters'
3
+
4
+ class ParametersRequireTest < ActiveSupport::TestCase
5
+ test "required parameters must be present not merely not nil" do
6
+ assert_raises(ActionController::ParameterMissing) do
7
+ ActionController::Parameters.new(person: {}).required(:person)
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,45 @@
1
+ require 'test_helper'
2
+ require 'action_controller/parameters'
3
+
4
+ class ParametersTaintTest < ActiveSupport::TestCase
5
+ setup do
6
+ @params = ActionController::Parameters.new({ person: {
7
+ age: "32", name: { first: "David", last: "Heinemeier Hansson" }
8
+ }})
9
+ end
10
+
11
+ test "fetch raises ParameterMissing exception" do
12
+ e = assert_raises(ActionController::ParameterMissing) do
13
+ @params.fetch :foo
14
+ end
15
+ assert_equal :foo, e.param
16
+ end
17
+
18
+ test "permitted is sticky on accessors" do
19
+ assert !@params.slice(:person).permitted?
20
+ assert !@params[:person][:name].permitted?
21
+
22
+ @params.each { |key, value| assert(value.permitted?) if key == :person }
23
+
24
+ assert !@params.fetch(:person).permitted?
25
+
26
+ assert !@params.values_at(:person).first.permitted?
27
+ end
28
+
29
+ test "permitted is sticky on mutators" do
30
+ assert !@params.delete_if { |k| k == :person }.permitted?
31
+ assert !@params.keep_if { |k,v| k == :person }.permitted?
32
+ end
33
+
34
+ test "permitted is sticky beyond merges" do
35
+ assert !@params.merge(a: "b").permitted?
36
+ end
37
+
38
+ test "modifying the parameters" do
39
+ @params[:person][:hometown] = "Chicago"
40
+ @params[:person][:family] = { brother: "Jonas" }
41
+
42
+ assert_equal "Chicago", @params[:person][:hometown]
43
+ assert_equal "Jonas", @params[:person][:family][:brother]
44
+ end
45
+ end
@@ -0,0 +1,27 @@
1
+ # Configure Rails Environment
2
+ ENV["RAILS_ENV"] = "test"
3
+
4
+ require 'test/unit'
5
+ require 'strong_parameters'
6
+
7
+ module ActionController
8
+ SharedTestRoutes = ActionDispatch::Routing::RouteSet.new
9
+ SharedTestRoutes.draw do
10
+ match ':controller(/:action)'
11
+ end
12
+
13
+ class Base
14
+ include ActionController::Testing
15
+ include SharedTestRoutes.url_helpers
16
+ end
17
+
18
+ class ActionController::TestCase
19
+ setup do
20
+ @routes = SharedTestRoutes
21
+ end
22
+ end
23
+ end
24
+
25
+
26
+ # Load support files
27
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f }
metadata ADDED
@@ -0,0 +1,101 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: strong_parameters
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Heinemeier Hansson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-21 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: actionpack
16
+ requirement: &70323933135920 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.2.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70323933135920
25
+ - !ruby/object:Gem::Dependency
26
+ name: activemodel
27
+ requirement: &70323933135420 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: 3.2.0
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70323933135420
36
+ - !ruby/object:Gem::Dependency
37
+ name: rake
38
+ requirement: &70323933135040 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ! '>='
42
+ - !ruby/object:Gem::Version
43
+ version: '0'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70323933135040
47
+ description:
48
+ email:
49
+ - david@heinemeierhansson.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - lib/action_controller/parameters.rb
55
+ - lib/active_model/forbidden_attributes_protection.rb
56
+ - lib/strong_parameters/version.rb
57
+ - lib/strong_parameters.rb
58
+ - MIT-LICENSE
59
+ - Rakefile
60
+ - README.rdoc
61
+ - test/action_controller_required_params_test.rb
62
+ - test/action_controller_tainted_params_test.rb
63
+ - test/active_model_mass_assignment_taint_protection_test.rb
64
+ - test/dummy/db/test.sqlite3
65
+ - test/dummy/log/test.log
66
+ - test/parameters_require_test.rb
67
+ - test/parameters_taint_test.rb
68
+ - test/test_helper.rb
69
+ homepage:
70
+ licenses: []
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ none: false
77
+ requirements:
78
+ - - ! '>='
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ! '>='
85
+ - !ruby/object:Gem::Version
86
+ version: '0'
87
+ requirements: []
88
+ rubyforge_project:
89
+ rubygems_version: 1.8.15
90
+ signing_key:
91
+ specification_version: 3
92
+ summary: Permitted and required parameters for Action Pack
93
+ test_files:
94
+ - test/action_controller_required_params_test.rb
95
+ - test/action_controller_tainted_params_test.rb
96
+ - test/active_model_mass_assignment_taint_protection_test.rb
97
+ - test/dummy/db/test.sqlite3
98
+ - test/dummy/log/test.log
99
+ - test/parameters_require_test.rb
100
+ - test/parameters_taint_test.rb
101
+ - test/test_helper.rb