Skip to content
This repository
Browse code

Integrate ActionController::Parameters from StrongParameters gem

  • Loading branch information...
commit 36d3ca645e1b5d4018a814f8cb94a8d3090fb531 1 parent 61be340
Guillermo Iguaran authored July 12, 2012
1  actionpack/lib/action_controller.rb
@@ -32,6 +32,7 @@ module ActionController
32 32
     autoload :Rescue
33 33
     autoload :Responder
34 34
     autoload :Streaming
  35
+    autoload :StrongParameters
35 36
     autoload :Testing
36 37
     autoload :UrlFor
37 38
   end
1  actionpack/lib/action_controller/base.rb
@@ -205,6 +205,7 @@ def self.without_modules(*modules)
205 205
       Caching,
206 206
       MimeResponds,
207 207
       ImplicitRender,
  208
+      StrongParameters,
208 209
 
209 210
       Cookies,
210 211
       Flash,
120  actionpack/lib/action_controller/metal/strong_parameters.rb
... ...
@@ -0,0 +1,120 @@
  1
+require 'active_support/concern'
  2
+require 'active_support/core_ext/hash/indifferent_access'
  3
+require 'active_support/rescuable'
  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 require(key)
  30
+      self[key].presence || raise(ActionController::ParameterMissing.new(key))
  31
+    end
  32
+
  33
+    alias :required :require
  34
+
  35
+    def permit(*filters)
  36
+      params = self.class.new
  37
+
  38
+      filters.each do |filter|
  39
+        case filter
  40
+        when Symbol, String then
  41
+          params[filter] = self[filter] if has_key?(filter)
  42
+        when Hash then
  43
+          self.slice(*filter.keys).each do |key, value|
  44
+            return unless value
  45
+
  46
+            key = key.to_sym
  47
+
  48
+            params[key] = each_element(value) do |value|
  49
+              # filters are a Hash, so we expect value to be a Hash too
  50
+              next if filter.is_a?(Hash) && !value.is_a?(Hash)
  51
+
  52
+              value = self.class.new(value) if !value.respond_to?(:permit)
  53
+
  54
+              value.permit(*Array.wrap(filter[key]))
  55
+            end
  56
+          end
  57
+        end
  58
+      end
  59
+
  60
+      params.permit!
  61
+    end
  62
+
  63
+    def [](key)
  64
+      convert_hashes_to_parameters(key, super)
  65
+    end
  66
+
  67
+    def fetch(key, *args)
  68
+      convert_hashes_to_parameters(key, super)
  69
+    rescue KeyError
  70
+      raise ActionController::ParameterMissing.new(key)
  71
+    end
  72
+
  73
+    def slice(*keys)
  74
+      self.class.new(super)
  75
+    end
  76
+
  77
+    def dup
  78
+      super.tap do |duplicate|
  79
+        duplicate.instance_variable_set :@permitted, @permitted
  80
+      end
  81
+    end
  82
+
  83
+    private
  84
+      def convert_hashes_to_parameters(key, value)
  85
+        if value.is_a?(Parameters) || !value.is_a?(Hash)
  86
+          value
  87
+        else
  88
+          # Convert to Parameters on first access
  89
+          self[key] = self.class.new(value)
  90
+        end
  91
+      end
  92
+
  93
+      def each_element(object)
  94
+        if object.is_a?(Array)
  95
+          object.map { |el| yield el }.compact
  96
+        else
  97
+          yield object
  98
+        end
  99
+      end
  100
+  end
  101
+
  102
+  module StrongParameters
  103
+    extend ActiveSupport::Concern
  104
+    include ActiveSupport::Rescuable
  105
+
  106
+    included do
  107
+      rescue_from(ActionController::ParameterMissing) do |parameter_missing_exception|
  108
+        render :text => "Required parameter missing: #{parameter_missing_exception.param}", :status => :bad_request
  109
+      end
  110
+    end
  111
+
  112
+    def params
  113
+      @_params ||= Parameters.new(request.parameters)
  114
+    end
  115
+
  116
+    def params=(val)
  117
+      @_params = val.is_a?(Hash) ? Parameters.new(val) : val
  118
+    end
  119
+  end
  120
+end
94  actionpack/test/controller/parameters/nested_parameters_test.rb
... ...
@@ -0,0 +1,94 @@
  1
+require 'action_controller/metal/strong_parameters'
  2
+
  3
+class NestedParametersTest < ActiveSupport::TestCase
  4
+  test "permitted nested parameters" do
  5
+    params = ActionController::Parameters.new({
  6
+      book: {
  7
+        title: "Romeo and Juliet",
  8
+        authors: [{
  9
+          name: "William Shakespeare",
  10
+          born: "1564-04-26"
  11
+        }, {
  12
+          name: "Christopher Marlowe"
  13
+        }],
  14
+        details: {
  15
+          pages: 200,
  16
+          genre: "Tragedy"
  17
+        }
  18
+      },
  19
+      magazine: "Mjallo!"
  20
+    })
  21
+
  22
+    permitted = params.permit book: [ :title, { authors: [ :name ] }, { details: :pages } ]
  23
+
  24
+    assert permitted.permitted?
  25
+    assert_equal "Romeo and Juliet", permitted[:book][:title]
  26
+    assert_equal "William Shakespeare", permitted[:book][:authors][0][:name]
  27
+    assert_equal "Christopher Marlowe", permitted[:book][:authors][1][:name]
  28
+    assert_equal 200, permitted[:book][:details][:pages]
  29
+    assert_nil permitted[:book][:details][:genre]
  30
+    assert_nil permitted[:book][:authors][1][:born]
  31
+    assert_nil permitted[:magazine]
  32
+  end
  33
+
  34
+  test "nested arrays with strings" do
  35
+    params = ActionController::Parameters.new({
  36
+      :book => {
  37
+        :genres => ["Tragedy"]
  38
+      }
  39
+    })
  40
+
  41
+    permitted = params.permit :book => :genres
  42
+    assert_equal ["Tragedy"], permitted[:book][:genres]
  43
+  end
  44
+
  45
+  test "permit may specify symbols or strings" do
  46
+    params = ActionController::Parameters.new({
  47
+      :book => {
  48
+        :title => "Romeo and Juliet",
  49
+        :author => "William Shakespeare"
  50
+      },
  51
+      :magazine => "Shakespeare Today"
  52
+    })
  53
+
  54
+    permitted = params.permit({:book => ["title", :author]}, "magazine")
  55
+    assert_equal "Romeo and Juliet", permitted[:book][:title]
  56
+    assert_equal "William Shakespeare", permitted[:book][:author]
  57
+    assert_equal "Shakespeare Today", permitted[:magazine]
  58
+  end
  59
+
  60
+  test "nested array with strings that should be hashes" do
  61
+    params = ActionController::Parameters.new({
  62
+      book: {
  63
+        genres: ["Tragedy"]
  64
+      }
  65
+    })
  66
+
  67
+    permitted = params.permit book: { genres: :type }
  68
+    assert_empty permitted[:book][:genres]
  69
+  end
  70
+
  71
+  test "nested array with strings that should be hashes and additional values" do
  72
+    params = ActionController::Parameters.new({
  73
+      book: {
  74
+        title: "Romeo and Juliet",
  75
+        genres: ["Tragedy"]
  76
+      }
  77
+    })
  78
+
  79
+    permitted = params.permit book: [ :title, { genres: :type } ]
  80
+    assert_equal "Romeo and Juliet", permitted[:book][:title]
  81
+    assert_empty permitted[:book][:genres]
  82
+  end
  83
+
  84
+  test "nested string that should be a hash" do
  85
+    params = ActionController::Parameters.new({
  86
+      book: {
  87
+        genre: "Tragedy"
  88
+      }
  89
+    })
  90
+
  91
+    permitted = params.permit book: { genre: :type }
  92
+    assert_nil permitted[:book][:genre]
  93
+  end
  94
+end
9  actionpack/test/controller/parameters/parameters_require_test.rb
... ...
@@ -0,0 +1,9 @@
  1
+require 'action_controller/metal/strong_parameters'
  2
+
  3
+class ParametersRequireTest < ActiveSupport::TestCase
  4
+  test "required parameters must be present not merely not nil" do
  5
+    assert_raises(ActionController::ParameterMissing) do
  6
+      ActionController::Parameters.new(person: {}).require(:person)
  7
+    end
  8
+  end
  9
+end
60  actionpack/test/controller/parameters/parameters_taint_test.rb
... ...
@@ -0,0 +1,60 @@
  1
+require 'action_controller/metal/strong_parameters'
  2
+
  3
+class ParametersTaintTest < ActiveSupport::TestCase
  4
+  setup do
  5
+    @params = ActionController::Parameters.new({ person: { 
  6
+      age: "32", name: { first: "David", last: "Heinemeier Hansson" }
  7
+    }})
  8
+  end
  9
+
  10
+  test "fetch raises ParameterMissing exception" do
  11
+    e = assert_raises(ActionController::ParameterMissing) do
  12
+      @params.fetch :foo
  13
+    end
  14
+    assert_equal :foo, e.param
  15
+  end
  16
+
  17
+  test "fetch doesnt raise ParameterMissing exception if there is a default" do
  18
+    assert_nothing_raised do
  19
+      assert_equal "monkey", @params.fetch(:foo, "monkey")
  20
+      assert_equal "monkey", @params.fetch(:foo) { "monkey" }
  21
+    end
  22
+  end
  23
+
  24
+  test "permitted is sticky on accessors" do
  25
+    assert !@params.slice(:person).permitted?
  26
+    assert !@params[:person][:name].permitted?
  27
+
  28
+    @params.each { |key, value| assert(value.permitted?) if key == :person }
  29
+
  30
+    assert !@params.fetch(:person).permitted?
  31
+
  32
+    assert !@params.values_at(:person).first.permitted?
  33
+  end
  34
+
  35
+  test "permitted is sticky on mutators" do
  36
+    assert !@params.delete_if { |k| k == :person }.permitted?
  37
+    assert !@params.keep_if { |k,v| k == :person }.permitted?
  38
+  end
  39
+
  40
+  test "permitted is sticky beyond merges" do
  41
+    assert !@params.merge(a: "b").permitted?
  42
+  end
  43
+
  44
+  test "modifying the parameters" do
  45
+    @params[:person][:hometown] = "Chicago"
  46
+    @params[:person][:family] = { brother: "Jonas" }
  47
+
  48
+    assert_equal "Chicago", @params[:person][:hometown]
  49
+    assert_equal "Jonas", @params[:person][:family][:brother]
  50
+  end
  51
+
  52
+  test "permitting parameters that are not there should not include the keys" do
  53
+    assert !@params.permit(:person, :funky).has_key?(:funky)
  54
+  end
  55
+
  56
+  test "permit state is kept on a dup" do
  57
+    @params.permit!
  58
+    assert_equal @params.permitted?, @params.dup.permitted?
  59
+  end
  60
+end
30  actionpack/test/controller/required_params_test.rb
... ...
@@ -0,0 +1,30 @@
  1
+require 'abstract_unit'
  2
+
  3
+class BooksController < ActionController::Base
  4
+  def create
  5
+    params.require(:book).require(: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
+
  26
+  test "missing parameters will be mentioned in the return" do
  27
+    post :create, { magazine: { name: "Mjallo!" } }
  28
+    assert_equal "Required parameter missing: book", response.body
  29
+  end
  30
+end
25  actionpack/test/controller/tainted_params_test.rb
... ...
@@ -0,0 +1,25 @@
  1
+require 'abstract_unit'
  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 notes on commit 36d3ca6

Please sign in to comment.
Something went wrong with that request. Please try again.