Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: false positive on unpermitted parameters #3097

Merged
merged 26 commits into from
Aug 9, 2024

Conversation

Paul-Bob
Copy link
Contributor

@Paul-Bob Paul-Bob commented Aug 6, 2024

Description

Fixes #3095

TLDR

This PR ensures that unnecessary and "false positive" warnings about unpermitted parameters are not raised by using only the permit strong parameters method where we're permitting. We were using the same permit method in places where permitting params weren't necessary and we could use slice or extract!

Context

When configuring self.extra_params on a resource and submitting a form, the permit method is utilized in two distinct stages:

  1. In the Base Controller: All parameters, including both base and extra params, are processed through Strong Parameters, ensuring they are permitted.

  2. On the Resource: When updating the record, permit is called again but only for the extra params. This step isolates these extra params from the combined base and extra params hash.

Actual approach

Instead of using permit on params to select the extra parameters, we now use slice, which returns only the specified extra parameters. In order to use slice we need to pass only the keys from extra_params. That's safe since the params at this point are already permitted (by using also the hashes values from extra_params) and there is no chance to pick unpermitted ones.

Previous approach

We temporarily disable the action on unpermitted parameters during this second call. This is necessary because permit is applied exclusively to the extra params, which means any base params are excluded and would otherwise trigger errors or logs for being unpermitted.

This approach does not impact the rest of the application because the action on unpermitted parameters is only temporarily disabled within this specific context. After handling the extra parameters, we restore the original setting for handling unpermitted parameters.

Checklist:

  • I have performed a self-review of my own code
  • I have commented my code, particularly in hard-to-understand areas
  • I have made corresponding changes to the documentation
  • I have added tests that prove my fix is effective or that my feature works

@Paul-Bob Paul-Bob marked this pull request as draft August 6, 2024 11:45
@github-actions github-actions bot added the Fix label Aug 6, 2024
Copy link

codeclimate bot commented Aug 6, 2024

Code Climate has analyzed commit 949121d and detected 0 issues on this pull request.

View more on Code Climate.

@Paul-Bob Paul-Bob self-assigned this Aug 6, 2024
@Paul-Bob Paul-Bob requested a review from adrianthedev August 6, 2024 11:58
@Paul-Bob Paul-Bob marked this pull request as ready for review August 6, 2024 11:58
@Paul-Bob Paul-Bob requested a review from binarygit August 6, 2024 12:16
@binarygit
Copy link
Contributor

binarygit commented Aug 6, 2024

Thanks for working on this Paul. I spent a little time trying to understand this and this is tricky. Kudos to you for coming up with the solution. I also have a small idea I want to share:

Could we do something like this?

 #base.rb
 def fill_record(record, params, extra_params_keys: [], fields: nil)
         ...
         if extra_params.present?
          # get_key_value returns a hash of objects like permit does...but without permitting
          record.assign_attributes params.get_key_value(extra_params_keys)
        end

We'll need to code up the get_key_value method (I checked the API and couldn't find anything similar) which essentially replicates permit but without the permitting part. It just takes our array of keys (eg: [:fish_type, :something_else, {:properties=>[], :information=>[:name, :history], :reviews_attributes=>[:body, :user_id]}]) and then returns us the key_value pair like permit does.

Do you think something like this would work?

@Paul-Bob
Copy link
Contributor Author

Paul-Bob commented Aug 6, 2024

We'll need to code up the get_key_value method

I thought about it but changed my mind after checking the permit source code.

PS: I can think about 3 possible solutions to this:

1 - Maintaining a method that behaves like permit without raising any log / error ( not ideal )
2 - The actual changes ( not ideal but IMO better than 1 )
3 - Make a PR to rails that allows to specify action_on_unpermitted_parameters, something like:

record.assign_attributes params.permit(extra_params, action_on_unpermitted_parameters: false)

@adrianthedev and @binarygit WDYT about making a PR to rails with these changes? (need to be tested and improved, it's just a DRAFT commit)

Copy link
Collaborator

@adrianthedev adrianthedev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To-do:

  • let's fill out this PR with what we spoke on the call
  • let's PR to Rails to suport permit_without_warnings

Copy link
Contributor

@binarygit binarygit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.

spec/rails_helper.rb Outdated Show resolved Hide resolved
spec/features/avo/custom_fields_in_resource_tools_spec.rb Outdated Show resolved Hide resolved
spec/features/avo/custom_fields_in_resource_tools_spec.rb Outdated Show resolved Hide resolved
spec/features/avo/custom_fields_in_resource_tools_spec.rb Outdated Show resolved Hide resolved
spec/features/avo/custom_fields_in_resource_tools_spec.rb Outdated Show resolved Hide resolved
spec/features/avo/custom_fields_in_resource_tools_spec.rb Outdated Show resolved Hide resolved
spec/support/avo_helpers.rb Outdated Show resolved Hide resolved
spec/support/avo_helpers.rb Outdated Show resolved Hide resolved
spec/support/avo_helpers.rb Outdated Show resolved Hide resolved
spec/system/avo/create_via_belongs_to_spec.rb Outdated Show resolved Hide resolved
spec/system/avo/create_via_belongs_to_spec.rb Outdated Show resolved Hide resolved
spec/support/avo_helpers.rb Outdated Show resolved Hide resolved
spec/support/avo_helpers.rb Outdated Show resolved Hide resolved
spec/support/avo_helpers.rb Outdated Show resolved Hide resolved
@Paul-Bob Paul-Bob marked this pull request as draft August 7, 2024 14:00
Copy link
Contributor Author

@Paul-Bob Paul-Bob left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just added a couple of comments with a brief change explanation

@@ -64,7 +64,7 @@ def handle
private

def action_params
params.permit(:authenticity_token, :resource_name, :action_id, :button, fields: {})
@action_params ||= params.permit(:id, :authenticity_token, :resource_name, :action_id, :button, fields: {})
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Memoizing and permitting id.

The id is present when an action is clicked from a show page.

@@ -132,7 +132,7 @@ def set_reflection_field
end

def attachment_id
params[:related_id] || params.require(:fields).permit(:related_id)[:related_id]
params[:related_id] || params.dig(:fields, :related_id)
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require and permit is not really necessary here, we just want to access the related_id

@@ -200,7 +200,7 @@ def through_reflection?
end

def additional_params
@additional_params ||= params[:fields].permit(@attach_fields&.map(&:id))
@additional_params ||= params[:fields].slice(*@attach_fields&.map(&:id))
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Applying permit only on the attach_fields would raise the error of the unpermitted parameters for the remaining parameters.

permit is unnecessary here because fill_record enures that only params that match attach_fields ids will be sent to the record.

Comment on lines +146 to +151
# TODO: fix "found unpermitted parameter: :user_id-dummy"
# tags field passes a dummy param generated from a fake input which is always unpermitted
with_temporary_class_option(ActionController::Parameters, :action_on_unpermitted_parameters, :log) do
sleep 0.3
click_on "Run"
end
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using a tags field will always raise the unpermitted error since it is generating a dummy param that isn't permitted anywhere.

@Paul-Bob Paul-Bob marked this pull request as ready for review August 7, 2024 14:38
Copy link
Contributor

@binarygit binarygit left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM 😄 . Thanks Paul!

@@ -2,7 +2,7 @@
<%
classes = 'absolute inset-auto left-1/2 top-1/2 transform -translate-x-1/2 -translate-y-1/2'
label = t 'avo.failed_to_load'
src_url = params[:src].present? && !params[:src].starts_with?('http://') ? params.permit(:src).fetch(:src) : nil
src_url = params[:src].present? && !params[:src].starts_with?('http://') ? params[:src] : nil
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's sanitize the src_url below where we display it. There's a security advisory there.

@@ -461,8 +461,12 @@ def fill_record(record, params, extra_params: [], fields: nil)

# Write the user configured extra params to the record
if extra_params.present?
# Pick only the extra params
# params at this point are already permited, only need the keys to access them
extra_attributes = params.slice(*flatten_keys(extra_params))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about we params to permitted_params so it's clear to anyone who's reading this and wondering why we are slicing instead of permitting.

Copy link
Collaborator

@adrianthedev adrianthedev left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I left a couple of pieces of feedback.

GG! Looking forward to not showing unpermitted params useless to users.

Thanks!

@Paul-Bob Paul-Bob merged commit d41d3a7 into main Aug 9, 2024
21 of 22 checks passed
@Paul-Bob Paul-Bob deleted the fix/false_positive_on_unnpermitted_parameters branch August 9, 2024 15:00
Copy link
Contributor

github-actions bot commented Aug 9, 2024

This PR has been merged into main. The functionality will be available in the next release.

Please check the release guide for more information.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Permit params only once per request
3 participants