Skip to content

THREESCALE-12434: Migrate from protected attributes to strong parameters - Part 2#4249

Open
mayorova wants to merge 8 commits into
strong-params-part1from
strong-params-part2
Open

THREESCALE-12434: Migrate from protected attributes to strong parameters - Part 2#4249
mayorova wants to merge 8 commits into
strong-params-part1from
strong-params-part2

Conversation

@mayorova
Copy link
Copy Markdown
Contributor

@mayorova mayorova commented Mar 11, 2026

What this PR does / why we need it:

This is part 2 of the migration from protected attributes to strong parameters. See the first part in #4248

Protected attributes is an old Rails feature which was deprecated a long time ago. We were using protected_attributes_continued gem to keep it working, but now it's also discontinued and does not support Rails 7+, so it's a blocker for upgrading to Rails 7.2 for us.

This Part 2 handles the models that can have custom attributes through FieldsDefinitions - Account, User, Cinstance.

Which issue(s) this PR fixes

https://redhat.atlassian.net/browse/THREESCALE-12434

Verification steps

All tests should pass, and all features should work as before.

Special notes for your reviewer:

Copy link
Copy Markdown
Contributor

@jlledom jlledom left a comment

Choose a reason for hiding this comment

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

Some comments come from the other PR. Also, I still see a lot of

Comment thread app/controllers/master/api/providers_controller.rb Outdated
Comment thread app/lib/signup/account_manager.rb Outdated
Comment thread app/controllers/provider/admin/account/users_controller.rb Outdated
Comment thread app/lib/authentication/strategy/oauth2.rb
@mayorova mayorova force-pushed the strong-params-part2 branch 2 times, most recently from 38e277c to db25ecf Compare March 13, 2026 00:03
@account_params ||= begin
defined_fields_names = buyer_account.defined_fields_names
allowed_attrs = defined_fields_names - %w(billing_address) + %w(name)
nested_params = { extra_fields: buyer_account.defined_extra_fields_names }
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

So, this is different from other API controller, which have only plain parameters, i.e. not nested under extra_fields.
I only did this because there was an existing test in test/integration/admin/api/accounts_controller_test.rb which was passing extra params in this way:

params: update_params.merge(extra_fields: { my_field: 4 })

I also added a check that plain parameters would also work.

Now I'm not sure if other API controllers need to accept both ways too 🤔

@mayorova mayorova force-pushed the strong-params-part2 branch 6 times, most recently from a676b21 to 8b5a6de Compare March 17, 2026 15:36
@mayorova mayorova changed the base branch from master to strong-params-part1 March 17, 2026 17:19
@mayorova mayorova changed the title Strong params part2 THREESCALE-12434: Migrate from protected attributes to strong parameters - Part 2 Mar 17, 2026
@mayorova mayorova force-pushed the strong-params-part1 branch from 0b8ead4 to 6c69623 Compare March 18, 2026 15:35
@mayorova mayorova force-pushed the strong-params-part2 branch 3 times, most recently from 498f50b to 706cacb Compare March 18, 2026 17:47
@mayorova mayorova force-pushed the strong-params-part1 branch 3 times, most recently from a08b299 to d3304a9 Compare March 19, 2026 12:26
@mayorova mayorova force-pushed the strong-params-part2 branch 2 times, most recently from e2771a2 to 1abbc5c Compare March 19, 2026 15:32
@mayorova mayorova marked this pull request as ready for review March 19, 2026 15:34
@qltysh
Copy link
Copy Markdown

qltysh Bot commented Mar 19, 2026

❌ 26 blocking issues (26 total)

Tool Category Rule Count
rubocop Style Use 0o for octal literals. 12
reek Lint Provider::Admin::AccountsController#update_account_params has approx 9 statements 3
reek Lint DeveloperPortal::BaseController#filter_readonly_params refers to 'read_only_fields' more than self (maybe move it to another class?) 2
rubocop Lint Prefer response\.parsed\_body. 2
rubocop Lint Assignment Branch Condition size for create is too high. [<7, 31, 3> 31.92/20] 1
brakeman Vulnerability Specify exact keys allowed for mass assignment instead of using permit\! which allows any keys. 1
rubocop Lint Use ActionDispatch::IntegrationTest instead. 1
rubocop Lint Use find\_by\! instead of dynamic find\_by\_code\!. 1
rubocop Style Incorrect formatting, autoformat by running qlty fmt. 1
rubocop Style Trailing whitespace detected. 1
reek Lint ApplicationsControllerMethodsTest::TestController#params is a writable attribute 1

@qltysh one-click actions:

  • Auto-fix formatting (qlty fmt && git push)

@mayorova mayorova force-pushed the strong-params-part1 branch 2 times, most recently from 4b5523f to 5f946c2 Compare April 20, 2026 10:07
application.unflattened_attributes = application_params
application.user_key = params[:user_key] if params[:user_key]
application.application_id = params[:application_id] if params[:application_id]
application.assign_attributes(application_params)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

user_key and application_id used to be protected attributes. It is not needed to split their assignment anymore. Both are explicitly added to the permitted parameters.

def application_attributes
current_account.fields.for(Cinstance) + %w|user_key application_id|
allowed_attrs = current_account.defined_fields_names_for(Cinstance) +
%w[user_key application_id redirect_url first_traffic_at first_daily_traffic_at]
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

redirect_url first_traffic_at first_daily_traffic_at are part of the old current_account.fields.for(Cinstance)

end

def account_params
allowed_attrs = current_account.defined_fields_names_for(Account) - %w[billing_address]
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Setting billing_address as a plain value (e.g. a string) is not supported.
Not sure, however, if we need to add the complex handling like in app/controllers/admin/api/accounts_controller.rb

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Was it possible before?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

According to Claude, it accepted a hash before, so if we want to preserve old behavior, we must implement it like this:

def account_params
  allowed_attrs = current_account.defined_fields_names_for(Account) - %w[billing_address]
  nested = { annotations: {} }
  if current_account.defined_fields_names_for(Account).include?('billing_address')
    nested[:billing_address] = %i[name address1 address2 city country state zip phone]
  end
  params.permit(*allowed_attrs, **nested)
end

However, I'm fine with removing billing address entirely from the signup controller unless we know a scenario where this would be required.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I made a test for signups controller in master (similar to the one for the account controller), and unfortunately, the same behavior is supported... both nested hash and individual fields work for setting billing address.
So, I'll have to add the same logic.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

maybe admin/api/users_controller.rb as well? This is what Bob thinks

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

maybe admin/api/users_controller.rb as well? This is what Bob thinks

admin/api/users_controller.rb is for provider's users, so we don't need billing address there.

def flat_params
super.except(:id)
def user_params
defined_fields_names = current_account.provider_account.defined_fields_names_for(User)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

These endpoints are for managing the users of the provider account (current_account in this case), so we need to check the fields definitions configured on the provider's provider (aka master account), hence current_account.provider_account.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I wonder if it would be more understandable to have it as Account.master.defined_fields_names_for(User) in this case. But it is fine to stay, just a thought. I'm not sure which is better.

super.except(:id)
def user_params
defined_fields_names = current_account.provider_account.defined_fields_names_for(User)
permission_attrs = [:member_permission_service_ids, { member_permission_service_ids: [], member_permission_ids: [] }]
Copy link
Copy Markdown
Contributor Author

@mayorova mayorova Jun 2, 2026

Choose a reason for hiding this comment

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

member_permission_service_ids can be nil (meaning all services are enabled), or an array (including an empty array), so both plain and array-like parameters need to be permitted.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ideally that would be a code comment for future readers. Although I think we have tests for that so one will notice.

defined_fields_names = current_account.provider_account.defined_fields_names_for(User)
permission_attrs = [:member_permission_service_ids, { member_permission_service_ids: [], member_permission_ids: [] }]
allowed_attrs = defined_fields_names + %w[password password_confirmation cas_identifier]
allowed_attrs += permission_attrs if provider_key.present? || current_user.admin?
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Permission attributes were only restricted for admin users here:

attr_accessible :member_permission_service_ids, :member_permission_ids, as: %i[admin]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A bit worried about this, the security depends on we remembering to always add this check in all controllers that handle these attributes. Is this properly tested?

If we have to add such a check in controller, why not delegating this to cancancan like we do here?

if can?(:update_role, @user)
allowed_attrs += [:role, { member_permission_ids: [] }]
allowed_attrs += [:member_permission_service_ids, { member_permission_service_ids: [] }] if current_account.provider_can_use?(:service_permissions)
end

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I also thought about cancan, this role you found is really nice.

The issue though is this provider_key.present? which, if provider)key was used, results in no user being set thus cancan will reject the operation.

So it will be nice to have it but I would not require it for this PR.

On the other hand, previously provider_key auth was not handled, so not handling it in this PR will not be a regression.

An easy fix might be to set current_user to first admin when provider_key is used. But then this may require more investigation than necessary for this PR.

In summary, I'm ok with all options:

  • keep as is
  • use cancan and ignore provider_key
  • use cancan and alter provider_key auth to set an admin user (or whatever alternative solution we can come up with)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

But we already have a ApiAuthentication::ByProviderKey module, I assume if the request came with a provider key, we already use it to get the proper user, and the user will have the proper cancancan permissions, so it should just work without worrying about how the user was authenticated.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I checked this. When we authenticate via :provider_key, current_user is not set at all, so we can't use cancancan here. In fact, there are a few calls to authorize! in some actions, but the method is overwritten to just check if we are logged in...

What I wonder is: are we implementing all these ugly workarounds in each API controller in order to accept provider keys?

This is what claude says:

● So there are three different strategies in play:

  1. Override authorize! to skip cancancan when no user (the 4 controllers we found)
  2. Guard with if current_user — services_controller, signups_controller, web_hooks_failures_controller, api_docs_services_controller — cancancan is simply not called for provider_key requests
  3. Call authorize! unconditionally — access_tokens_controller, authentication_providers_controller, backend_apis_controller, backend_apis/base_controller, registry/policies_controller, services/backend_usages_controller — these would hit cancancan with a nil user and likely fail on provider_key auth
  4. Creative workaround — application_plans_controller builds its own Ability from current_account.admins.first, sidestepping the nil user entirely

This project is gonna kill me.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

This is what I told you :) that current_user is not set. But we can change the module to set first admin for example. Provider key means something like the provider root account. When you lost access to your admin for some reason, you can recover with the provider key.

I think that not so many controllers check user permissions. In all of them though, we would need special logic to handle provider_key auth. Again, a simple workaround might be setting current_user to the first admin in such case.

As this is not a regression though, I would suggest such improvements to be performed within another JIRA issue.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I understand you now. Yeah, I think setting current_user to the impersonation admin when using provider_key would probably work out of the box. But seems something to be done in a separate PR.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I don't quite understand what Claude is suggesting... But here are some points:

  1. I think the implementation was already confusing in the first place. There are two places where attr_accessible is set for these fields:
  • in models/user.rb:
    attr_accessible :member_permission_service_ids, :member_permission_ids, as: %i[admin]
    
  • in models/user/permissions.rb:
    attr_accessible :member_permission_service_ids, :member_permission_ids, :allowed_sections, :allowed_service_ids
    

I don't really understand how this is expected to work based on the protected_attributes_continued documentation, Claude and Bob are also confused.

But according to the tests, this is what happens:

user.update_with_flattened_attributes(flat_params, as: current_user.try(:role))

Indeed, if provider_key is used, current_user would be nil, so, assign_attributes will be called with as: nil, and then, the role defaults to :default value.

Probably, because of the missing role in attr_accessible in permissions.rb, :default is actually considered the role that can update the fields due to this default.

So, with as: :admin and as: nil the update works, and with as: :member it doesn't apparently.

  1. I ran the test 'set member permissions with provider key' on master, and the behavior is the same as in this branch. The tests 'set group permissions as an admin' and 'set group permissions as a member' also pass on both branches, so I think that effectively the behavior is the same.

  2. There are really just two controllers where these fields can potentially be set: the API one (this), and the UI one (app/controllers/provider/admin/account/users_controller.rb). In the API one we need to handle the provider key explicitly, and in the UI one it is handled using can?, because there is always a valid current user.
    So, I am not overly worried about:

the security depends on we remembering to always add this check in all controllers that handle these attributes

I am not expecting us to add more controllers for handling these fields.

Conclusion:

I think the current implementation achieves the same behavior as before, so I wouldn't complicate things any more.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yeah, not in this PR. What I thought was that in case we already have or add in the future permissions checks in other controllers, it might be worth having a current_user set with provider_key auth as well.

@account_params ||= params.require(:account).except(:user)
defined_builtin_fields_names = current_account.defined_builtin_fields_names_for(Account)
defined_extra_fields_names = current_account.defined_extra_fields_names_for(Account)
allowed_attrs = defined_builtin_fields_names - %w[billing_address country] + %w[country_id]
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

billing_address and country fields do not even appear in the form as editable:

  • for setting the country, there is a dropdown select with country_id as values
  • billing_address is showed, but greyed out (it can not be directly modified via UI)

The test 'update account, including optional built-in and custom fields' of test/integration/buyers/accounts_controller_test.rb verifies the behavior for these fields.

defined_builtin_fields_names = current_account.defined_builtin_fields_names_for(Account)
defined_extra_fields_names = current_account.defined_extra_fields_names_for(Account)
allowed_attrs = defined_builtin_fields_names - %w[billing_address country] + %w[country_id]
params.require(:account).permit(*allowed_attrs, extra_fields: defined_extra_fields_names)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Here and in other UI controllers - custom parameters (invented by the user) are submitted inside extra_fields, e.g. account[extra_fields][something]=value.
Other parameters (default or not) are submitted as account's attributes, e.g. account[org_name]=ok or account[vat_rate]=30

def permitted_user_params
fields_names = current_account.defined_fields_names_for(User)
extra_fields_names = current_account.defined_extra_fields_names_for(User)
user_params.permit(*fields_names, :password, :password_confirmation,
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We do not need to add role to the list, because it is not assigned massively anyway due to a permission check:

user.role = user_params.fetch(:role, user.role) if can?(:update_role, user)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

But maybe :role was being set in another line precisely because it was a protected attribute, not only because of the permission check. We could also use that permission check to decide whether we permit the attribute or not. The same we do in provider/admin/account/users_controller (see comment above). I don't have a strong opinion, though.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

user_params.fetch(:role, user.role) makes it perhaps harder to handle it like in the other controllers... unless they handle the same problem already, that I don't remember spotting but I might have missed it :)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

How about this? 2aec247

private

def user_params
flat_params.merge({signup_type: :minimal})
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This signup_type: :minimal was moved to signup_params method.

@user.validate_fields!

@user.assign_attributes(user_params)
@user.role = user_params.fetch(:role, @user.role)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No need to set role explicitly, because it's not a protected attribute anymore.

before_action :ensure_signup_possible

skip_before_action :login_required
skip_before_action :enable_analytics, only: :test
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removed because there is no :test action.


def signup_params
Signup::SignupParams.new(plans: [plan], user_attributes: user_params, account_attributes: account_params, validate_fields: true)
Signup::SignupParams.new(plans: [plan], user_attributes: user_params.merge(signup_type: :new_signup, username: :admin), account_attributes: account_params.merge(sample_data: true), validate_fields: true)
Copy link
Copy Markdown
Contributor Author

@mayorova mayorova Jun 3, 2026

Choose a reason for hiding this comment

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

Moved signup_type: :new_signup, username: :admin here from user_params.

I am just trying to follow the same pattern everywhere - user_params, account_params etc. just permit the parameters, and we add any extra fields in another place (either signup_params, or in the .update call itself.

Same for sample_data: true)


def account_params
params.require(:account).except(:user).merge(sample_data: true)
allowed_attrs = master.defined_builtin_fields_names_for(Account) + %w[name subdomain self_subdomain]
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This list (and the one for user params) was derived from checking how the signup form works: https://github.com/3scale/porta/blob/e80983fc2723d4281f688e4b3fdc2b3c5f251ccb/app/lib/fields/signup_form.rb

In any case, now this form is disabled by default both in on-premises and in SaaS:
#4242

def handle_cache_response
expires_in 1.hour, public: true
fresh_when etag: default_params, last_modified: System::Application.config.boot_time
fresh_when etag: params.permit!.to_h, last_modified: System::Application.config.boot_time
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Just maintaining the previous behavior.

Comment thread app/lib/fields/fields.rb
self.class.internal_fields
end

def special_fields
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Removing all things related to special fields. They were only used for fetching password password_confirmation attributes or user, but it's much cleaner IMO to just include them in the controller's permitted params list directly.
Especially, as some controllers just accept password, and some others - both password and password_confirmation.


def plan_id
@plan_id ||= params.require(:cinstance).permit(:plan_id).tap { |plan_params| plan_params.require(:plan_id) }[:plan_id]
@plan_id ||= cinstance_params.permit(:plan_id).require(:plan_id)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This simplified form seems to be doing the same as the original.

sso_attributes.all? { |_key, value| value.present? }
end

def create_sso_authorization
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This method was not used anywhere.

end

def account_params
allowed_attrs = site_account.defined_fields_names_for(Account) - %w[country vat_rate] + %w[country_id]
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

  1. Country is set via a dropdown select, where the values are country_id, so country (the actual name of the builtin field) is removed.
  2. vat_rate can not be updated by the developer by design.

Comment thread app/controllers/admin/api/signups_controller.rb Outdated

def user_params
defined_fields_names = current_account.defined_fields_names_for(User)
params.permit(*defined_fields_names, :password)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

same question about password_confirmation, should we allow in APIs or not. But it is fine as is.

Comment thread app/controllers/provider/signups_controller.rb
Copy link
Copy Markdown
Contributor

@akostadinov akostadinov left a comment

Choose a reason for hiding this comment

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

Awesome stuff! I added some questions but I suspect they are not very valid 👼

if defined_fields_names.include?('billing_address')
allowed_attrs += %w[billing_address_name billing_address_address1 billing_address_address2 billing_address_city
billing_address_country billing_address_state billing_address_zip billing_address_phone]
nested_params[:billing_address] = %i[name address1 address2 city country state zip phone]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why not same as above?

Suggested change
nested_params[:billing_address] = %i[name address1 address2 city country state zip phone]
nested_params[:billing_address] = %w[name address1 address2 city country state zip phone]

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not sure to be honest... But agree to make it more consistent.

def contract_params
allowed_attrs = current_account.defined_fields_names_for(Cinstance) +
%w[user_key application_id redirect_url first_traffic_at first_daily_traffic_at]
params.permit(*allowed_attrs)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Why adding ["user_key", "application_id"] if those were not permitted before?

end

def account_params
allowed_attrs = current_account.defined_fields_names_for(Account) - %w[billing_address]
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

According to Claude, it accepted a hash before, so if we want to preserve old behavior, we must implement it like this:

def account_params
  allowed_attrs = current_account.defined_fields_names_for(Account) - %w[billing_address]
  nested = { annotations: {} }
  if current_account.defined_fields_names_for(Account).include?('billing_address')
    nested[:billing_address] = %i[name address1 address2 city country state zip phone]
  end
  params.permit(*allowed_attrs, **nested)
end

However, I'm fine with removing billing address entirely from the signup controller unless we know a scenario where this would be required.

defined_fields_names = current_account.provider_account.defined_fields_names_for(User)
permission_attrs = [:member_permission_service_ids, { member_permission_service_ids: [], member_permission_ids: [] }]
allowed_attrs = defined_fields_names + %w[password password_confirmation cas_identifier]
allowed_attrs += permission_attrs if provider_key.present? || current_user.admin?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A bit worried about this, the security depends on we remembering to always add this check in all controllers that handle these attributes. Is this properly tested?

If we have to add such a check in controller, why not delegating this to cancancan like we do here?

if can?(:update_role, @user)
allowed_attrs += [:role, { member_permission_ids: [] }]
allowed_attrs += [:member_permission_service_ids, { member_permission_service_ids: [] }] if current_account.provider_can_use?(:service_permissions)
end

def permitted_user_params
fields_names = current_account.defined_fields_names_for(User)
extra_fields_names = current_account.defined_extra_fields_names_for(User)
user_params.permit(*fields_names, :password, :password_confirmation,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

But maybe :role was being set in another line precisely because it was a protected attribute, not only because of the permission check. We could also use that permission check to decide whether we permit the attribute or not. The same we do in provider/admin/account/users_controller (see comment above). I don't have a strong opinion, though.

Comment on lines -58 to -60
# This is just a sanity guard added when splitting invitation
# controllers. Remove when SURE.
raise 'Developer invitation used and worked on provider side!' unless @user.account.provider?
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Are you SURE?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Pretty SURE, yes 😬

Comment thread lib/developer_portal/app/controllers/developer_portal/signup_controller.rb Outdated
Comment thread app/controllers/provider/admin/user/personal_details_controller.rb
Comment thread app/controllers/buyers/accounts_controller.rb
@mayorova
Copy link
Copy Markdown
Contributor Author

mayorova commented Jun 5, 2026

@akostadinov @jlledom hopefully, I addressed your concerns in 3929c52

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants