Skip to content
This repository
Browse code

permits more scalar types

  • Loading branch information...
commit 4c21c676c1f7ea8895a59c2431df9a0339de21f0 1 parent cdea33c
Xavier Noria authored
8  README.rdoc
Source Rendered
@@ -29,15 +29,17 @@ In addition, parameters can be marked as required and flow through a predefined
29 29
         end
30 30
     end
31 31
 
32  
-== Atomic Values
  32
+== Permitted Scalar Values
33 33
 
34 34
 Given
35 35
 
36 36
     params.permit(:id)
37 37
 
38  
-the key +:id+ will pass the whitelisting if it appears in +params+ and it has a string or integer associated. Otherwise the key is going to be filtered out, so arrays, hashes, or any other objects cannot be injected.
  38
+the key +:id+ will pass the whitelisting if it appears in +params+ and it has a permitted scalar value associated. Otherwise the key is going to be filtered out, so arrays, hashes, or any other objects cannot be injected.
39 39
 
40  
-To declare that the value in +params+ must be an array of strings or integers map the key to an empty array:
  40
+The permitted scalar types are +String+, +Symbol+, +NilClass+, +Numeric+, +TrueClass+, +FalseClass+, +Date+, +Time+, +DateTime+, +StringIO+, and +IO+.
  41
+
  42
+To declare that the value in +params+ must be an array of permitted scalar values map the key to an empty array:
41 43
 
42 44
     params.permit(:id => [])
43 45
 
166  lib/action_controller/parameters.rb
... ...
@@ -1,3 +1,7 @@
  1
+require 'date'
  2
+require 'bigdecimal'
  3
+require 'stringio'
  4
+
1 5
 require 'active_support/concern'
2 6
 require 'active_support/core_ext/hash/indifferent_access'
3 7
 require 'action_controller'
@@ -12,79 +16,8 @@ def initialize(param)
12 16
     end
13 17
   end
14 18
 
15  
-  module Filtering
16  
-    def atomic?(value)
17  
-      # We allow Integer for backwards compatibility. Parameters coming from web
18  
-      # requests are strings, but it is not uncommon that users set integer IDs
19  
-      # in controller tests, where the hash is passed as is to the controller.
20  
-      #
21  
-      # Note that we short-circuit the common case first.
22  
-      value.is_a?(String) || value.is_a?(Integer)
23  
-    end
24  
-
25  
-    def array_of_atomics?(value)
26  
-      if value.is_a?(Array)
27  
-        value.all? {|_| atomic?(_)}
28  
-      end
29  
-    end
30  
-
31  
-    def atomic_filter(params, key)
32  
-      if has_key?(key) && atomic?(self[key])
33  
-        params[key] = self[key]
34  
-      end
35  
-
36  
-      keys.grep(/\A#{Regexp.escape(key.to_s)}\(\d+[if]?\)\z/).each do |key|
37  
-        if atomic?(self[key])
38  
-          params[key] = self[key]
39  
-        end
40  
-      end
41  
-    end
42  
-
43  
-    def hash_filter(params, filter)
44  
-      filter = filter.with_indifferent_access
45  
-
46  
-      # Slicing filters out non-declared keys.
47  
-      slice(*filter.keys).each do |key, value|
48  
-        return unless value
49  
-
50  
-        if filter[key] == []
51  
-          # Declaration {:comment_ids => []}.
52  
-          array_of_atomics_filter(params, key)
53  
-        else
54  
-          # Declaration {:user => :name} or {:user => [:name, :age, {:adress => ...}]}.
55  
-          params[key] = each_element(value) do |element|
56  
-            if element.is_a?(Hash)
57  
-              element = self.class.new(element) unless element.respond_to?(:permit)
58  
-              element.permit(*Array.wrap(filter[key]))
59  
-            end
60  
-          end
61  
-        end
62  
-      end
63  
-    end
64  
-
65  
-    def array_of_atomics_filter(params, key)
66  
-      if has_key?(key) && array_of_atomics?(self[key])
67  
-        params[key] = self[key]
68  
-      end
69  
-    end
70  
-
71  
-    def each_element(value)
72  
-      if value.is_a?(Array)
73  
-        value.map { |el| yield el }.compact
74  
-        # fields_for on an array of records uses numeric hash keys.
75  
-      elsif value.is_a?(Hash) && value.keys.all? { |k| k =~ /\A-?\d+\z/ }
76  
-        hash = value.class.new
77  
-        value.each { |k,v| hash[k] = yield v }
78  
-        hash
79  
-      else
80  
-        yield value
81  
-      end
82  
-    end
83  
-  end
84 19
 
85 20
   class Parameters < ActiveSupport::HashWithIndifferentAccess
86  
-    include Filtering
87  
-
88 21
     attr_accessor :permitted
89 22
     alias :permitted? :permitted
90 23
 
@@ -115,7 +48,7 @@ def permit(*filters)
115 48
       filters.each do |filter|
116 49
         case filter
117 50
         when Symbol, String
118  
-          atomic_filter(params, filter)
  51
+          permitted_scalar_filter(params, filter)
119 52
         when Hash then
120 53
           hash_filter(params, filter)
121 54
         end
@@ -159,6 +92,7 @@ def convert_value(value)
159 92
       end
160 93
 
161 94
     private
  95
+
162 96
       def convert_hashes_to_parameters(key, value)
163 97
         if value.is_a?(Parameters) || !value.is_a?(Hash)
164 98
           value
@@ -167,6 +101,94 @@ def convert_hashes_to_parameters(key, value)
167 101
           self[key] = self.class.new(value)
168 102
         end
169 103
       end
  104
+
  105
+      #
  106
+      # --- Filtering ----------------------------------------------------------
  107
+      #
  108
+
  109
+      # This is a white list of permitted scalar types that includes the ones
  110
+      # supported in XML and JSON requests.
  111
+      #
  112
+      # This list is in particular used to filter ordinary requests, String goes
  113
+      # as first element to quickly short-circuit the common case.
  114
+      #
  115
+      # If you modify this collection please update the README.
  116
+      PERMITTED_SCALAR_TYPES = [
  117
+        String,
  118
+        Symbol,
  119
+        NilClass,
  120
+        Numeric,
  121
+        TrueClass,
  122
+        FalseClass,
  123
+        Date,
  124
+        Time,
  125
+        # DateTimes are Dates, we document the type but avoid the redundant check.
  126
+        StringIO,
  127
+        IO,
  128
+      ]
  129
+
  130
+      def permitted_scalar?(value)
  131
+        PERMITTED_SCALAR_TYPES.any? {|type| value.is_a?(type)}
  132
+      end
  133
+
  134
+      def array_of_permitted_scalars?(value)
  135
+        if value.is_a?(Array)
  136
+          value.all? {|element| permitted_scalar?(element)}
  137
+        end
  138
+      end
  139
+
  140
+      def permitted_scalar_filter(params, key)
  141
+        if has_key?(key) && permitted_scalar?(self[key])
  142
+          params[key] = self[key]
  143
+        end
  144
+
  145
+        keys.grep(/\A#{Regexp.escape(key.to_s)}\(\d+[if]?\)\z/).each do |key|
  146
+          if permitted_scalar?(self[key])
  147
+            params[key] = self[key]
  148
+          end
  149
+        end
  150
+      end
  151
+
  152
+      def array_of_permitted_scalars_filter(params, key)
  153
+        if has_key?(key) && array_of_permitted_scalars?(self[key])
  154
+          params[key] = self[key]
  155
+        end
  156
+      end
  157
+
  158
+      def hash_filter(params, filter)
  159
+        filter = filter.with_indifferent_access
  160
+
  161
+        # Slicing filters out non-declared keys.
  162
+        slice(*filter.keys).each do |key, value|
  163
+          return unless value
  164
+
  165
+          if filter[key] == []
  166
+            # Declaration {:comment_ids => []}.
  167
+            array_of_permitted_scalars_filter(params, key)
  168
+          else
  169
+            # Declaration {:user => :name} or {:user => [:name, :age, {:adress => ...}]}.
  170
+            params[key] = each_element(value) do |element|
  171
+              if element.is_a?(Hash)
  172
+                element = self.class.new(element) unless element.respond_to?(:permit)
  173
+                element.permit(*Array.wrap(filter[key]))
  174
+              end
  175
+            end
  176
+          end
  177
+        end
  178
+      end
  179
+
  180
+      def each_element(value)
  181
+        if value.is_a?(Array)
  182
+          value.map { |el| yield el }.compact
  183
+          # fields_for on an array of records uses numeric hash keys.
  184
+        elsif value.is_a?(Hash) && value.keys.all? { |k| k =~ /\A-?\d+\z/ }
  185
+          hash = value.class.new
  186
+          value.each { |k,v| hash[k] = yield v }
  187
+          hash
  188
+        else
  189
+          yield value
  190
+        end
  191
+      end
170 192
   end
171 193
 
172 194
   module StrongParameters
42  test/parameters_permit_test.rb
@@ -7,7 +7,7 @@ def assert_filtered_out(params, key)
7 7
   end
8 8
 
9 9
   #
10  
-  # -- Basic interface ---------------------------------------------------------
  10
+  # --- Basic interface --------------------------------------------------------
11 11
   #
12 12
 
13 13
   # --- nothing ----------------------------------------------------------------
@@ -21,15 +21,23 @@ def assert_filtered_out(params, key)
21 21
 
22 22
   # --- key --------------------------------------------------------------------
23 23
 
24  
-  test 'key: atomic values' do
25  
-    params = ActionController::Parameters.new(:id => '1234')
26  
-    permitted = params.permit(:id)
27  
-    assert_equal '1234', permitted[:id]
  24
+  test 'key: permitted scalar values' do
  25
+    values  = ['a', :a, nil]
  26
+    values += [0, 1.0, 2**128, BigDecimal.new(1)]
  27
+    values += [true, false]
  28
+    values += [Date.today, Time.now, DateTime.now]
  29
+    values += [StringIO.new]
28 30
 
29  
-    %w(i f).each do |suffix|
30  
-      params = ActionController::Parameters.new("foo(000#{suffix})" => '5678')
31  
-      permitted = params.permit(:foo)
32  
-      assert_equal '5678', permitted["foo(000#{suffix})"]
  31
+    values.each do |value|
  32
+      params = ActionController::Parameters.new(:id => value)
  33
+      permitted = params.permit(:id)
  34
+      assert_equal value, permitted[:id]
  35
+
  36
+      %w(i f).each do |suffix|
  37
+        params = ActionController::Parameters.new("foo(000#{suffix})" => value)
  38
+        permitted = params.permit(:foo)
  39
+        assert_equal value, permitted["foo(000#{suffix})"]
  40
+      end
33 41
     end
34 42
   end
35 43
 
@@ -68,7 +76,7 @@ def assert_filtered_out(params, key)
68 76
     end
69 77
   end
70 78
 
71  
-  test 'key: non-atomic objects are filtered out' do
  79
+  test 'key: non-permitted scalar values are filtered out' do
72 80
     params = ActionController::Parameters.new(:id => Object.new)
73 81
     permitted = params.permit(:id)
74 82
     assert_filtered_out permitted, :id
@@ -94,7 +102,7 @@ def assert_filtered_out(params, key)
94 102
     assert_equal [], permitted[:id]
95 103
   end
96 104
 
97  
-  test 'key to empty array: arrays of atomics pass' do
  105
+  test 'key to empty array: arrays of permitted scalars pass' do
98 106
     [['foo'], [1], ['foo', 'bar'], [1, 2, 3]].each do |array|
99 107
       params = ActionController::Parameters.new(:id => array)
100 108
       permitted = params.permit(:id => [])
@@ -102,17 +110,17 @@ def assert_filtered_out(params, key)
102 110
     end
103 111
   end
104 112
 
105  
-  test 'key to empty array: atomic values do not pass' do
106  
-    ['foo', 1].each do |atomic|
107  
-      params = ActionController::Parameters.new(:id => atomic)
  113
+  test 'key to empty array: permitted scalar values do not pass' do
  114
+    ['foo', 1].each do |permitted_scalar|
  115
+      params = ActionController::Parameters.new(:id => permitted_scalar)
108 116
       permitted = params.permit(:id => [])
109 117
       assert_filtered_out permitted, :id
110 118
     end
111 119
   end
112 120
 
113  
-  test 'key to empty array: arrays of non-atomic do not pass' do
114  
-    [[Object.new], [[]], [[1]], [{}], [{:id => '1'}]].each do |non_atomic|
115  
-      params = ActionController::Parameters.new(:id => non_atomic)
  121
+  test 'key to empty array: arrays of non-permitted scalar do not pass' do
  122
+    [[Object.new], [[]], [[1]], [{}], [{:id => '1'}]].each do |non_permitted_scalar|
  123
+      params = ActionController::Parameters.new(:id => non_permitted_scalar)
116 124
       permitted = params.permit(:id => [])
117 125
       assert_filtered_out permitted, :id
118 126
     end

0 notes on commit 4c21c67

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