-
Notifications
You must be signed in to change notification settings - Fork 21.6k
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
Improve the upgrade path of Strong Parameters #28734
Conversation
I'm really excited to see I have concerns about backporting the last commit to 5.0. The change makes sense in the context of |
The plan is to |
I agree the error is good -- it's much better than silently not working. Good job! |
# # => ActionController::ForbiddenParameters: converting a unpermitted parameters to hash is not allowed | ||
class ForbiddenParameters < StandardError | ||
def initialize | ||
super("converting an unpermitted parameters to hash is not allowed") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"unable to convert unpermitted parameters to hash", maybe? "not allowed" sounds like we're being difficult.
# params = ActionController::Parameters.new(a: "123", b: "456") | ||
# params.to_h | ||
# # => ActionController::ForbiddenParameters: converting a unpermitted parameters to hash is not allowed | ||
class ForbiddenParameters < StandardError |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Forbidden doesn't sound quite right.. how about UnfilteredParameters?
@@ -257,7 +312,7 @@ def to_h | |||
# params.to_unsafe_h | |||
# # => {"name"=>"Senjougahara Hitagi", "oddity" => "Heavy stone crab"} | |||
def to_unsafe_h | |||
convert_parameters_to_hashes(@parameters, :to_unsafe_h) | |||
permit!.to_h |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this should mutate the original params object.. otherwise params.to_unsafe_h
in one place that's being careful some other way could leave a later params.to_h
exposed to danger.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh. That is right. I was wondering why we implemented like that and now I know it. We are missing some test coverage about this case. I'll add. Do you have preference between dup the paramters and mutate it or the implementation like it was before?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If it's just a simple dup that seems fine.. if it would need to be a deep dup, I think it's probably worth the effort of the more complicated version.
That's quite a breaking change to push to stable version. As a developer I would be extremely surprised by this introduced between At least this should be a config variable, with |
# safe_params = params.permit(:name) | ||
# safe_params.to_hash # => {"name"=>"Senjougahara Hitagi"} | ||
def to_hash | ||
to_h.to_hash |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This will make our code cleaner, thanks! 👍
@kirs that's a good point.. but on the other hand, is anyone going to be knowingly using the current behaviour? It seems a rather long-winded way to spell The low-impact alternative would be to introduce a warning, and optionally offer the raise as a non-default config option. I'm dubious that the exception would cause people problems (as opposed to identifying places they didn't realise were silently eating input), but as 5.0 is fairly mature at this point -- and about to become legacy -- maybe we should go with the more conservative option anyway. I'm not sure whether this should go through the deprecation infrastructure (gives people more control etc, even though it's not strictly a deprecation), or straight to Kernel#warn. |
Yeah... if people is getting this exception after upgrading to 5.0.3 it means that they had a bug that they didn't anticipated in their code and in my opinion an exception is better than leaving the bug hidden. We can go in the deprecation path just to be safe with an option to raise on this deprecation. |
Yes the deprecation path might be best, like we have |
56481d7
to
a4f8d89
Compare
Fixed all the points for this branch and 5.1. I'll open a new PR with the changes to 5.0 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Left a few comments about the docs :)
end | ||
end | ||
|
||
# Returns a safe <tt>Hash</tt> representation of this parameter |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
the parameters
# | ||
# params = ActionController::Parameters.new({ | ||
# name: 'David', | ||
# nationality: 'Danish'}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
maybe have })
be on a new line, like above?
# | ||
# params = ActionController::Parameters.new({ | ||
# name: 'David', | ||
# nationality: 'Danish'}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
same comment -- maybe have })
be on a new line, like above?
# # => ActionController::UnfilteredParameters: unable to convert unpermitted parameters to hash | ||
class UnfilteredParameters < StandardError | ||
def initialize | ||
super("unable to convert unpermitted parameters to hash") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It would be friendly to the developer to suggest how they're supposed to fix the error (by using permit)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
An exception don't need to explain what users have to do. It depends on the context and since we don't know what is the context we can't recommend something. But, it needs to be clear about what it means. Is the exception message and documentation clear about what it means?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
+1 exceptions should clearly state what's wrong, but leave speculation about how the developer might fix it to someone/something else
a4f8d89
to
8d3bf94
Compare
@maclover7 thanks! Done |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Like this a lot! 👍
@@ -43,6 +43,18 @@ def initialize(params) # :nodoc: | |||
end | |||
end | |||
|
|||
# Raised when a Parameters instance is not marked as permitted and | |||
# an operation to transform to hash is called. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
"to transform it to a hash."
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
# | ||
# safe_params = params.permit(:name) | ||
# safe_params.to_h # => {"name"=>"Senjougahara Hitagi"} | ||
def to_h | ||
if permitted? | ||
convert_parameters_to_hashes(@parameters, :to_h) | ||
else | ||
slice(*self.class.always_permitted_parameters).permit!.to_h |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How do we honor always_permitted_parameters
now? Should it be deprecated?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is still needed when you permit let say [:user][:name]
in a controller and the action_on_unpermitted_parameters
is either :log
or :raise
. that way :controller
and :action
will not raise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah, it's handled in unpermitted_parameters!
. Never mind 😊
# name: 'David', | ||
# nationality: 'Danish' | ||
# }) | ||
# params.to_query('user') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should the new documentation here use double quoted strings and no hash braces on the new
call to fit our Rubocop rules?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
assert_equal({ "controller" => "users", "action" => "create" }, params.to_h) | ||
assert_instance_of Hash, params.to_hash | ||
assert_not_kind_of ActionController::Parameters, params.to_hash | ||
assert_equal({ "crab" => "Senjougahara Hitagi" }, params.to_hash) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't Ruby implicitly call to_hash
on the params
here? Should we test an implicit case?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
8d3bf94
to
41c7fec
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Had one last comment, otherwise 👍
Before we returned either an empty hash or only the always permitted parameters (:controller and :action by default). The previous behavior was dangerous because in order to get the attributes users usually fallback to use to_unsafe_h that could potentially introduce security issues. The to_unsafe_h API is also not good since Parameters is a object that quacks like a Hash but not in all cases since to_h would return an empty hash and users were forced to check if to_unsafe_h is defined or if the instance is a ActionController::Parameters in order to work with it. This end up coupling a lot of libraries and parts of the application with something that is from the controller layer.
Now methods that implicit convert objects to a hash will be able to work without requiring the users to change their implementation. This method will return a Hash instead of a HashWithIndefirentAccess to mimic the same implementation of HashWithIndefirentAccess#to_hash.
Previously it was raising an error because it may be unsafe to use those methods in a unpermitted parameter. Now we delegate to to_h that already raise an error when the Parameters instance is not permitted. This also fix a bug when using `#to_query` in a hash that contains a `ActionController::Parameters` instance and was returning the name of the class in the string.
We are talking about a list of parameters even so we need to use plural. Even if we were talking about the instance of the Parameters object we would have to use the capital and monospaced font.
a4b11a6
to
1d5ea4a
Compare
There's another stand-alone instance of the existing message in rails/actionview/lib/action_view/helpers/url_helper.rb Lines 625 to 626 in a2ab5ff
|
1d5ea4a
to
06923ca
Compare
Good catch. Fixed |
Since this protection is now in Parameters we can use it instead of reimplementing again.
06923ca
to
93034ad
Compare
Improve the upgrade path of Strong Parameters
Improve the upgrade path of Strong Parameters
Some changes were made in this PR to improve the upgrade path of Strong Parameters not inheriting from hash anymore.
Raise exception when calling
to_h
in an unpermittedParameters
Before we returned either an empty hash or only the always permitted parameters (:controller and :action by default).
The previous behavior was dangerous because in order to get the attributes users usually fallback to use
to_unsafe_h
that could potentially introduce security issues.The
to_unsafe_h
API is also not good since Parameters is a object that quacks like aHash
but not in all cases sinceto_h
would return an empty hash and users were forced to check ifto_unsafe_h
is defined or if the instance is aActionController::Parameters
in order to work with it. This end up coupling a lot of libraries and parts of the application with something that is from the controller layer.Add ActionController::Parameters#to_hash to implicit conversion
Now methods that implicit convert objects to a hash will be able to work without requiring the users to change their implementation.
This method will return a Hash instead of a
HashWithIndefirentAccess
to mimic the same implementation ofHashWithIndefirentAccess#to_hash
.Implement ActionController::Parameters#to_query and #to_param
Previously it was raising an error because it may be unsafe to use those methods in a unpermitted parameter. Now we delegate to to_h that already raise an error when the Parameters instance is not permitted.
This also fix a bug when using
#to_query
in a hash that contains aActionController::Parameters
instance and was returning the name of the class in the string.Backport
The plan is to backport this to 5.1 and part of it to 5.0.
Backport to 5.0
The plan is to backport the first and the last change fully.
The
#to_hash
change I want to just undeprecate it for now since it is already callingto_hash
in the internal array and the result includes all the parameters in the case were they are not permitted.Also implement
respond_to_missing
since we have amethod_missing
.cc @maartenvg @nicolaslupien @trevorturk