Skip to content

Commit 6be9c49

Browse files
committed
Provide context when logging unpermitted parameters
Currently, the payload of the unpermitted_parameters.action_controller events emitted by StrongParameters does not provide enough information for developers to understand which controller and action received the unpermitted parameters. This PR modifies ActionController::Parameters to allow callers to specify a "context" which is included in the logging payload. *Implementation Strategy* Since the ActionController::Parameters class is only loosely coupled with controllers and can technically be used in any context, this PR expects the caller to provide logging context. Since StrongParameters is caller in Rails and has access to the request object I chose to provide a payload similar to the start_processing.action_controller event.
1 parent d612542 commit 6be9c49

File tree

6 files changed

+45
-24
lines changed

6 files changed

+45
-24
lines changed

actionpack/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
* Expand payload of `unpermitted_parameters.action_controller` instrumentation to allow subscribers to
2+
know which controller action received unpermitted parameters.
3+
4+
*bbuchalter*
5+
16
* Add `ActionController::Live#send_stream` that makes it more convenient to send generated streams:
27

38
```ruby

actionpack/lib/action_controller/log_subscriber.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ def send_data(event)
5656
def unpermitted_parameters(event)
5757
debug do
5858
unpermitted_keys = event.payload[:keys]
59-
color("Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{unpermitted_keys.map { |e| ":#{e}" }.join(", ")}", RED)
59+
display_unpermitted_keys = unpermitted_keys.map { |e| ":#{e}" }.join(", ")
60+
context = event.payload[:context].map { |k, v| "#{k}: #{v}" }.join(", ")
61+
color("Unpermitted parameter#{'s' if unpermitted_keys.size > 1}: #{display_unpermitted_keys}. Context: { #{context} }", RED)
6062
end
6163
end
6264

actionpack/lib/action_controller/metal/strong_parameters.rb

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -106,11 +106,13 @@ def initialize # :nodoc:
106106
#
107107
# * +permit_all_parameters+ - If it's +true+, all the parameters will be
108108
# permitted by default. The default is +false+.
109-
# * +action_on_unpermitted_parameters+ - Allow to control the behavior when parameters
110-
# that are not explicitly permitted are found. The values can be +false+ to just filter them
111-
# out, <tt>:log</tt> to additionally write a message on the logger, or <tt>:raise</tt> to raise
112-
# ActionController::UnpermittedParameters exception. The default value is <tt>:log</tt>
113-
# in test and development environments, +false+ otherwise.
109+
# * +action_on_unpermitted_parameters+ - Controls behavior when parameters that are not explicitly
110+
# permitted are found. The default value is <tt>:log</tt> in test and development environments,
111+
# +false+ otherwise. The values can be:
112+
# * +false+ to take no action.
113+
# * <tt>:log</tt> to emit an <tt>ActiveSupport::Notifications.instrument</tt> event on the
114+
# <tt>unpermitted_parameters.action_controller</tt> topic and log at the DEBUG level.
115+
# * <tt>:raise</tt> to raise a <tt>ActionController::UnpermittedParameters</tt> exception.
114116
#
115117
# Examples:
116118
#
@@ -277,8 +279,9 @@ def nested_attribute?(key, value) # :nodoc:
277279
# params = ActionController::Parameters.new(name: "Francesco")
278280
# params.permitted? # => true
279281
# Person.new(params) # => #<Person id: nil, name: "Francesco">
280-
def initialize(parameters = {})
282+
def initialize(parameters = {}, logging_context = {})
281283
@parameters = parameters.with_indifferent_access
284+
@logging_context = logging_context
282285
@permitted = self.class.permit_all_parameters
283286
end
284287

@@ -968,7 +971,7 @@ def unpermitted_parameters!(params)
968971
case self.class.action_on_unpermitted_parameters
969972
when :log
970973
name = "unpermitted_parameters.action_controller"
971-
ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys)
974+
ActiveSupport::Notifications.instrument(name, keys: unpermitted_keys, context: @logging_context)
972975
when :raise
973976
raise ActionController::UnpermittedParameters.new(unpermitted_keys)
974977
end
@@ -1184,7 +1187,15 @@ module StrongParameters
11841187
# Returns a new ActionController::Parameters object that
11851188
# has been instantiated with the <tt>request.parameters</tt>.
11861189
def params
1187-
@_params ||= Parameters.new(request.parameters)
1190+
@_params ||= begin
1191+
context = {
1192+
controller: self.class.name,
1193+
action: action_name,
1194+
request: request,
1195+
params: request.filtered_parameters
1196+
}
1197+
Parameters.new(request.parameters, context)
1198+
end
11881199
end
11891200

11901201
# Assigns the given +value+ to the +params+ hash. If +value+

actionpack/test/controller/parameters/log_on_unpermitted_params_test.rb

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,22 +13,21 @@ def teardown
1313
end
1414

1515
test "logs on unexpected param" do
16-
params = ActionController::Parameters.new(
17-
book: { pages: 65 },
18-
fishing: "Turnips")
16+
request_params = { book: { pages: 65 }, fishing: "Turnips" }
17+
context = { "action" => "my_action", "controller" => "my_controller" }
18+
params = ActionController::Parameters.new(request_params, context)
1919

20-
assert_logged("Unpermitted parameter: :fishing") do
20+
assert_logged("Unpermitted parameter: :fishing. Context: { action: my_action, controller: my_controller }") do
2121
params.permit(book: [:pages])
2222
end
2323
end
2424

2525
test "logs on unexpected params" do
26-
params = ActionController::Parameters.new(
27-
book: { pages: 65 },
28-
fishing: "Turnips",
29-
car: "Mersedes")
26+
request_params = { book: { pages: 65 }, fishing: "Turnips", car: "Mersedes" }
27+
context = { "action" => "my_action", "controller" => "my_controller" }
28+
params = ActionController::Parameters.new(request_params, context)
3029

31-
assert_logged("Unpermitted parameters: :fishing, :car") do
30+
assert_logged("Unpermitted parameters: :fishing, :car. Context: { action: my_action, controller: my_controller }") do
3231
params.permit(book: [:pages])
3332
end
3433
end
@@ -37,7 +36,7 @@ def teardown
3736
params = ActionController::Parameters.new(
3837
book: { pages: 65, title: "Green Cats and where to find then." })
3938

40-
assert_logged("Unpermitted parameter: :title") do
39+
assert_logged("Unpermitted parameter: :title. Context: { }") do
4140
params.permit(book: [:pages])
4241
end
4342
end
@@ -46,7 +45,7 @@ def teardown
4645
params = ActionController::Parameters.new(
4746
book: { pages: 65, title: "Green Cats and where to find then.", author: "G. A. Dog" })
4847

49-
assert_logged("Unpermitted parameters: :title, :author") do
48+
assert_logged("Unpermitted parameters: :title, :author. Context: { }") do
5049
params.permit(book: [:pages])
5150
end
5251
end

guides/source/active_support_instrumentation.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -280,9 +280,10 @@ INFO. Additional keys may be added by the caller.
280280

281281
### unpermitted_parameters.action_controller
282282

283-
| Key | Value |
284-
| ------- | ---------------- |
285-
| `:keys` | Unpermitted keys |
283+
| Key | Value |
284+
| ------------- | --------------------------------------------------------------------- |
285+
| `:key` | The unpermitted keys |
286+
| `:context` | Hash with the following keys: :controller, :action, :params, :request |
286287

287288
Action Dispatch
288289
---------------

guides/source/configuring.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -526,7 +526,10 @@ The schema dumper adds two additional configuration options:
526526
527527
* `config.action_controller.permit_all_parameters` sets all the parameters for mass assignment to be permitted by default. The default value is `false`.
528528
529-
* `config.action_controller.action_on_unpermitted_parameters` enables logging or raising an exception if parameters that are not explicitly permitted are found. Set to `:log` or `:raise` to enable. The default value is `:log` in development and test environments, and `false` in all other environments.
529+
* `config.action_controller.action_on_unpermitted_parameters` controls behavior when parameters that are not explicitly permitted are found. The default value is `:log` in test and development environments, `false` otherwise. The values can be:
530+
* `false` to take no action
531+
* `:log` to emit an `ActiveSupport::Notifications.instrument` event on the `unpermitted_parameters.action_controller` topic and log at the DEBUG level
532+
* `:raise` to raise a `ActionController::UnpermittedParameters` exception
530533
531534
* `config.action_controller.always_permitted_parameters` sets a list of permitted parameters that are permitted by default. The default values are `['controller', 'action']`.
532535

0 commit comments

Comments
 (0)