-
Notifications
You must be signed in to change notification settings - Fork 72
[WIP] [PoC] Migrate from protected attributes to strong parameters #4238
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
Changes from 6 commits
4ca06ef
f30629d
cbe7b54
97be0c3
305efc5
60f881c
a4a6540
dc62875
563faf7
b4c8211
30d5255
24a509b
0ddc0c5
269a25e
81f7063
ca63300
17c7232
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,7 +15,7 @@ def update | |
| # TODO: I think this controller is used only on provider side | ||
| user.validate_fields! if current_account.buyer? | ||
|
|
||
| user.attributes = user_params | ||
| user.attributes = permitted_user_params | ||
| user.role = user_params.fetch(:role, user.role) if can?(:update_role, user) | ||
|
|
||
| if user.save | ||
|
|
@@ -80,10 +80,15 @@ def find_user | |
| @user = @account.users.find(params[:id]).decorate | ||
| end | ||
|
|
||
| DEFAULT_PARAMS = %i[username email password password_confirmation role].freeze | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This logic was actually problematic. When they are added in FieldsDefinitions, the fields appear in the Edit form and can be updated. Here is what the However, the further Hence, title, first_name, last_name and job_role can never be updated through the UI - I think it's a bug.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If I understand correctly, these special fields ('title', 'first_name', 'last_name' and 'job_role'), if defined as extra fields, need to be accepted at the lower level, not under This looks like a bug in either UI or in the way params are permitted. I would probably rather fix how UI pass them down but also checking for them and permitting them on the lower level is also good. I assume on the API side they already have to be passed udder extra fields?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, it depends on whether it's a UI controller or API. The comment is not about that, though.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What is the comment about then?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, the issue is not about where the parameters appear - at the "root" user level, or under The problem is that this
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hang on, once I fix the tests and push the latest updates, I'll explain the new implementation. It has some drawbacks too, but hopefully it can work.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So this explanation still sounds the same. And basically the fix is to either:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I hope this comment will clarify things 🙏 https://github.com/3scale/porta/pull/4238/changes#r2895692403 |
||
|
|
||
| def user_params | ||
| @user_params ||= params.require(:user).permit(*DEFAULT_PARAMS, extra_fields: [*user.defined_extra_fields_names]) | ||
| @user_params ||= params.require(:user) | ||
| end | ||
|
|
||
| def permitted_user_params | ||
| @permitted_user_params ||= begin | ||
| allowed_attrs = user.defined_builtin_fields_names | %i(password password_confirmation) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, basically, the solution I've chosen for now is to rely on the FieldsDefinitions defined for the corresponding model (User in this case). I'll try to explain... So, here is how the fields are defined for User. When a provider is created, the default fields definitions are created automatically, for User these are The union of required and optional fields (plus the "internal" ones - but there is only one for the Account model, not user) is referred to as "builtin" fields, as opposed to "extra" fields, that are completely custom and created by the user. So, I decided, rather than list the hard-coded list of fields (which, apart from username and email may or may not be among the User model fields), just take the actual builtin fields defined for the User model. There is a small downside in this approach - and it is that I had to adjust some tests to ensure that the default Fields Definitions are actually created (for example, with Also, it's worth noting that for the UI controllers I am splitting the In API controllers, on the contrary, all attributes - custom and "standard" appear at the same level, so for API controller params I'm using There is also the
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The idea is to accept all params flat at the
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
No... please read again - nesting under So, built-in and extra/custom treated separately. I don't see a point in changing this and resulting in many more changes - both in controllers and views.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Ok, thanks! That's also totally fine, I somehow looked only at this line and missed |
||
| user_params.permit(*allowed_attrs, extra_fields: user.defined_extra_fields_names) | ||
| end | ||
| end | ||
|
|
||
| def redirect_back_or_show_detail(**opts) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -66,7 +66,7 @@ def update_resource(user, attributes) | |
|
|
||
| user.class.transaction do | ||
| user.assign_attributes(attributes) | ||
| user.assign_attributes(protected_attributes, without_protection: can?(:update_role, user)) | ||
| user.assign_attributes(protected_attributes) if can?(:update_role, user) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Mmm, this is not exactly the same, but I guess it'll work the same way, just without the warning in the logs in the case the condition is Also, maybe we should rename it, |
||
|
|
||
| user.save | ||
| end | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,7 +53,10 @@ def can_create? | |
| end | ||
|
|
||
| def build_user | ||
| @user = @invitation.make_user(params[:user] || {}) | ||
| @user = @invitation.make_user | ||
| allowed_attrs = @user.defined_fields_names | %i(password password_confirmation) | ||
| user_params = params.fetch(:user, ActionController::Parameters.new).permit(*allowed_attrs) | ||
| @user.assign_attributes(user_params) | ||
|
Comment on lines
+56
to
+59
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Just a nitpick, maybe it'd be better to extract it to its own method
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, sure
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Well, actually, the difference here is that we rely on
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It should work like any other controller, for
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. OK
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did it here: 1cd9a16 |
||
|
|
||
| # This is just a sanity guard added when splitting invitation | ||
| # controllers. Remove when SURE. | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -275,6 +275,14 @@ def defined_extra_fields_names | |
| defined_extra_fields.map(&:name) | ||
| end | ||
|
|
||
| def defined_fields_names | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably need unit tests for these new methods. |
||
| defined_fields.map(&:name) | ||
| end | ||
|
|
||
| def defined_builtin_fields_names | ||
| defined_builtin_fields.map(&:name) | ||
| end | ||
|
|
||
| def defined_fields_hash | ||
| @defined_fields_hash ||= Hash[defined_fields.map { |f| [f.name.to_sym, f]}] | ||
| end | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -33,7 +33,8 @@ def sso_create | |
| redirect_to strategy.redirect_to_on_successful_login | ||
| else | ||
| user_data = strategy.user_data || {} | ||
| @user.assign_attributes(user_data.to_hash.compact) | ||
| user_attributes = user_data.to_hash.compact.slice(:username, :email) | ||
|
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I couldn't figure out what would be the scenario where we'd end up in this condition branch, and still
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If tests pass, good enough.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Actually, as I was replying to this other comment: https://github.com/3scale/porta/pull/4249/changes#r2926129192 I realized, that this slice is not needed, because |
||
| @user.assign_attributes(user_attributes) | ||
| session[:invitation_sso_uid] = user_data[:uid] | ||
| session[:invitation_sso_system_name] = strategy.authentication_provider.system_name | ||
| build_sso_authorization | ||
|
|
@@ -119,7 +120,10 @@ def check_invitation! | |
| end | ||
|
|
||
| def build_user | ||
| @user = @invitation.make_user(filter_readonly_params(params[:user], User)) | ||
| @user = @invitation.make_user | ||
| allowed_attrs = @user.defined_fields_names | %i(password password_confirmation) | ||
| user_params = filter_readonly_params(params[:user], User).permit(*allowed_attrs) | ||
| @user.assign_attributes(user_params) | ||
| end | ||
|
|
||
| def invitation_token | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -53,7 +53,7 @@ def collection | |
|
|
||
| def update_resource(user, attributes) | ||
| attributes.each do |attrs| | ||
| user.attributes = filter_readonly_params(attrs, User) | ||
| user.attributes = filter_readonly_params(attrs, User).permit! | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we permit everything here?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fixed in d8e75bc |
||
| user.role = attrs[:role] if can? :update_role, user | ||
| end | ||
| user.save | ||
|
|
||
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 check is used instead of the combination of these:
When
provider_keyis used (i.e.current_userisnil), the fields can be updated (verified by a new test).