Skip to content
Open
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .reek.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,7 @@ directories:
"test":
InstanceVariableAssumption:
enabled: false
DuplicateMethodCall:
enabled: false
UncommunicativeVariableName:
enabled: false
12 changes: 12 additions & 0 deletions .rubocop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,13 @@ Rails:
Rails/RequestReferer:
EnforcedStyle: referrer

Rails/ActionOrder:
Enabled: false
Comment thread
akostadinov marked this conversation as resolved.

Rails/SkipsModelValidations:
Exclude:
- 'test/**/*_test.rb'

Lint:
Enabled: true

Expand All @@ -20,6 +27,7 @@ Metrics:
Metrics/BlockLength:
Exclude:
- 'test/**/*_test.rb'
- 'spec/**/*_spec.rb'

Lint/AssignmentInCondition:
AllowSafeAssignment: true
Expand Down Expand Up @@ -75,12 +83,16 @@ Layout/LineLength:

Metrics/AbcSize:
Max: 20
Exclude:
- 'test/**/*_test.rb'

Metrics/MethodLength:
Max: 20

Metrics/ClassLength:
Max: 200
Exclude:
- 'test/**/*_test.rb'

AllCops:
NewCops: enable
Expand Down
20 changes: 16 additions & 4 deletions app/controllers/admin/api/accounts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,9 +43,8 @@ def update

preload_to_present(buyer_account)

buyer_account.vat_rate = params[:vat_rate].to_f if params[:vat_rate]
buyer_account.settings.attributes = billing_params
buyer_account.update_with_flattened_attributes(flat_params)
buyer_account.update(account_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.

vat_rate used to be a protected attribute, no need to split anymore.
It will be included in the permitted parameters, if defined in fields definitions.


respond_with(buyer_account)
end
Expand Down Expand Up @@ -156,7 +155,20 @@ def find_buyer_account
end
end

def flat_params
super.except(:vat_rate, :id)
def account_params
defined_fields_names = current_account.defined_fields_names_for(Account)
allowed_attrs = defined_fields_names + %w[name]

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.

name is a legacy alias of org_name, so it's included as allowed.

nested_params = {
extra_fields: current_account.defined_extra_fields_names_for(Account),
annotations: {}
}

if defined_fields_names.include?('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.

There are two ways to set billing address: as individual fields, or as a nested hash. The test 'update billing_address' in test/integration/user-management-api/accounts_test.rb verifies that both formats work.

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.

end

params.permit(*allowed_attrs, **nested_params)
end
end
33 changes: 7 additions & 26 deletions app/controllers/admin/api/buyers_application_plans_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -25,31 +25,11 @@ def index
respond_with(bought_application_plans)
end

# swagger
## e = sapi.apis.add
## e.path = "/admin/api/accounts/{account_id}/application_plans/{id}/buy.xml"
## e.responseClass = "application"
## @desc = "Creates an application for a partner on a given application plan (deprecated)."
## e.description = @desc
#
## op = e.operations.add
## op.deprecated = true
## op.nickname = "buyer_app_plan_buy"
## op.httpMethod = "POST"
## op.summary = @desc
#
## @id = { :name => "id", :description => "ID of the application plan.", :dataType => "int", :allowMultiple => false, :required => true, :paramType => "path" }
#
## op.parameters.add @access_token
## op.parameters.add @account_id
## op.parameters.add @id
## op.parameters.add :name => "name", :description => "Name of the application to be created.", :dataType => "string", :allowMultiple => false, :required => true, :paramType => "query"
## op.parameters.add :name => "description", :description => "Description of the application to be created.", :dataType => "string", :allowMultiple => false, :required => true, :paramType => "query"
#

Comment thread
akostadinov marked this conversation as resolved.
# Creates an application for a partner on a given application plan (deprecated).
# POST /admin/api/accounts/{account_id}/application_plans/{id}/buy.xml
#TODO: deprecate this, beware there are clients using it
def buy
application = buyer.buy(application_plan, application_plan_params)
application = buyer.buy(application_plan, contract_params)
respond_with(application, serialize: application_plan)
end

Expand All @@ -63,8 +43,9 @@ def application_plan
@application_plan ||= accessible_application_plans.stock.find(params[:id])
end

def application_plan_params
attributes = current_account.fields.for(Cinstance)
params.slice(*attributes)
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 Author

Choose a reason for hiding this comment

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

With the default fields definitions, current_account.fields.for(Cinstance) would return:

 ["created_at", "updated_at", "description", "name", "redirect_url", "extra_fields", "create_origin", "first_traffic_at", "first_daily_traffic_at", "service_id", "accepted_at"]

So, in the new version these are missing (which I think is fine):

 ["created_at", "updated_at", "extra_fields", "create_origin", "service_id", "accepted_at"]

On the other hand, these are added:

["user_key", "application_id"]

which I am now not sure about 🤔

This is, however, a legacy endpoint that hopefully is not used by anyone, so I think it's not a big deal.

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?

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.

Hmm, I read this as if they were part of fields.for before but now are manually set. I don't see a problem to allow them, but agree also that if they were not previously allowed, then no need to allow them now.

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 they were not allowed to be mass assigned, but then they were assigned manually somewhere else. In that case, yes, they must be permitted here. But I don't know if that was the case. Just pointing out so @mayorova can confirm.

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, I've checked this, and user_key and application_id do indeed get set explicitly, however, as they are not listed in current_account.fields.for(Cinstance), in master these fields would not be set by the controller.

porta/app/models/plan.rb

Lines 384 to 385 in 0a34a66

contract.application_id = params["application_id"] if params["application_id"]
contract.user_key = params["user_key"] if params["user_key"]

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.

So basically something underneath sets them approved or not so we don't permit them? If they have to be set, then permitting is also fine. But whatever, if it works - fine.

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.

Well, this snippet in models/plan.rb sets them explicitly, but in the controller they are filtered out. So, I added the test 'buy with different application attributes' which verifies that when user_key and application_id are provided in the buy method, they are effectively ignored. The test passes both in master and in this branch, which means that we are keeping the same behavior as before.

end
end
21 changes: 7 additions & 14 deletions app/controllers/admin/api/buyers_applications_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,7 @@ def index
# POST /admin/api/accounts/{account_id}/applications.xml
def create
application = applications.new(user_account: buyer, plan: application_plan, create_origin: "api")
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.


Array(params[:application_key]).each do |key|
application.application_keys.build(value: key)
Expand All @@ -35,10 +33,7 @@ def show
# Application Update
# PUT /admin/api/accounts/{account_id}/applications/{id}.xml
def update
application.unflattened_attributes = flat_params
application.user_key = params[:user_key] if params[:user_key]

application.save
application.update(application_update_params)

respond_with application
end
Expand Down Expand Up @@ -124,15 +119,13 @@ def application_plan
end

def application_params
flat_params.slice(*application_attributes)
end

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)

params.permit(*allowed_attrs)
end

def flat_params
super.except(:account_id)
def application_update_params
application_params.except(:application_id)
Comment thread
akostadinov marked this conversation as resolved.
end

def find_or_create_service_contract
Expand Down
12 changes: 7 additions & 5 deletions app/controllers/admin/api/buyers_users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,7 @@ def create

authorize! :create, user

user.unflattened_attributes = flat_params
user.signup_type = :created_by_provider

user.save
user.update(user_params.merge(signup_type: :created_by_provider))

respond_with(user)
end
Expand All @@ -37,7 +34,7 @@ def show
def update
authorize! :update, user

user.update_with_flattened_attributes(flat_params)
user.update(user_params)
Comment thread
akostadinov marked this conversation as resolved.

respond_with(user)
end
Expand Down Expand Up @@ -123,4 +120,9 @@ def user
@user ||= buyer.users.find(params[:id])
end

private

def user_params
params.permit(*current_account.defined_fields_names_for(User), :password, :password_confirmation)
end
end
2 changes: 1 addition & 1 deletion app/controllers/admin/api/providers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ def show
# Provider Account Update
# PUT /admin/api/provider.xml
def update
current_account.update(provider_params, without_protection: true)
current_account.update(provider_params)
respond_with current_account
end

Expand Down
10 changes: 8 additions & 2 deletions app/controllers/admin/api/signups_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,13 @@ def create
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.

defined_user_fields = current_account.defined_fields_names_for(User)
params.permit(*defined_user_fields, :password)
Comment thread
akostadinov marked this conversation as resolved.
Outdated
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.

@akostadinov akostadinov Jun 6, 2026

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
update: app/controllers/admin/api/accounts_controller.rb, app/controllers/master/api/providers_controller.rb

Also app/controllers/admin/api/credit_cards_controller.rb
│ Key Parameters at Risk: │
│ - vat_rate - Dropped in accounts_controller │
│ - billing_address_city, billing_address_country, billing_address_state, │
│ billing_address_zip, billing_address_phone, billing_address_name, │
│ billing_address_address1, billing_address_address2 - Only permitted │
│ if billing_address is a defined field

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.

params.permit(*allowed_attrs, annotations: {})
end

def check_creation_errors
Expand All @@ -31,7 +37,7 @@ def check_creation_errors
end

def signup_params
Signup::SignupParams.new(plans: plans, user_attributes: user_params, account_attributes: flat_params, defaults: defaults)
Signup::SignupParams.new(plans: plans, user_attributes: user_params.merge(signup_type: :minimal), account_attributes: account_params, defaults: defaults)
end

def defaults
Expand Down
15 changes: 8 additions & 7 deletions app/controllers/admin/api/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,7 @@ def create

authorize! :create, user

user.unflattened_attributes = flat_params
user.signup_type = :created_by_provider

user.save
user.update(user_params.merge(signup_type: :created_by_provider))

respond_with(user)
end
Expand All @@ -39,7 +36,7 @@ def show
def update
authorize! :update, user

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

respond_with(user)
end
Expand Down Expand Up @@ -131,7 +128,11 @@ def can_create

private

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.

permission_attrs = [:member_permission_service_ids, { member_permission_service_ids: [], member_permission_ids: [] }]

@mayorova mayorova Jun 2, 2026

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.

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.

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.

params.permit(*allowed_attrs)
end
end
16 changes: 9 additions & 7 deletions app/controllers/buyers/accounts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,7 @@ def new
end

def update
vat = account_params[:vat_rate]
account.vat_rate = vat if vat # vat_rate is protected attribute

if account.update(account_params.except(:vat_rate))
if account.update(account_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.

vat_rate used to be a protected attribute, no need to split anymore.
It will be included in the permitted parameters, if defined in fields definitions.

redirect_to admin_buyers_account_path(account), success: t('.success')
else
render :edit
Expand Down Expand Up @@ -109,13 +106,18 @@ def signup_params
Signup::SignupParams.new(plans: [], user_attributes: user_params.merge(signup_type: :created_by_provider), account_attributes: account_params, validate_fields: false)
end

# TODO: using `permit` later
def account_params
@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.

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

end

def user_params
params.require(:account).fetch(:user, {})
defined_builtin_fields_names = current_account.defined_builtin_fields_names_for(User)
defined_extra_fields_names = current_account.defined_extra_fields_names_for(User)
allowed_attrs = defined_builtin_fields_names + %w[password]
Comment thread
akostadinov marked this conversation as resolved.
params.require(:account).fetch(:user, {}).permit(*allowed_attrs, extra_fields: defined_extra_fields_names)
end

def set_plans
Expand Down
6 changes: 5 additions & 1 deletion app/controllers/buyers/groups_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ class Buyers::GroupsController < Buyers::BaseController
def show; end

def update
flash[:success] = t('.success') if @account.update(params[:account])
flash[:success] = t('.success') if @account.update(groups_params)

redirect_to action: :show, id: @account.id
end
Expand All @@ -28,4 +28,8 @@ def authorize_groups
def find_account
@account = current_account.buyer_accounts.find(params[:account_id])
end

def groups_params
params.require(:account).permit(group_ids: [])
end
end
16 changes: 9 additions & 7 deletions app/controllers/buyers/users_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,7 @@ def show
def edit; end

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.assign_attributes(permitted_user_params)
user.role = user_params.fetch(:role, user.role) if can?(:update_role, user)

if user.save
Expand Down Expand Up @@ -86,10 +83,15 @@ def find_user
@user = @account.users.find(params[:id]).decorate
end

DEFAULT_PARAMS = %i[username email password password_confirmation role].freeze

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
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

extra_fields: extra_fields_names)
end

def redirect_back_or_show_detail(**opts)
Expand Down
29 changes: 20 additions & 9 deletions app/controllers/master/api/providers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,9 +48,7 @@ def create
# Tenant Update
# PUT /master/api/providers/{id}.xml
def update
provider_account.assign_attributes(update_params, without_protection: true)
provider_account.assign_unflattened_attributes(params.require(:account))
provider_account.save
provider_account.update(update_account_params)

respond_with signup_result_with_nil_token
end
Expand Down Expand Up @@ -117,14 +115,27 @@ def find_plan_for_upgrade
render_error "Plan with ID #{plan_id.presence} not found", status: :not_found
end

def update_params
permitted_params = provider_account.scheduled_for_deletion? ? %i[state_event] : UPDATE_PARAMS
params.require(:account).permit(permitted_params)
end

def create_params
defaults = { ApplicationPlan => { :name => 'API signup', :description => 'API signup', :create_origin => 'api' } }
Signup::SignupParams.new(plans: plans, user_attributes: flat_params.merge(signup_type: :created_by_provider), account_attributes: flat_params, defaults: defaults)
Signup::SignupParams.new(plans: plans, user_attributes: user_params.merge(signup_type: :created_by_provider), account_attributes: create_account_params, defaults: defaults)
end

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.

end

def permitted_account_attrs
current_account.defined_fields_names_for(Account) | UPDATE_PARAMS
end

def create_account_params
params.permit(*permitted_account_attrs)
end

def update_account_params
allowed_attrs = provider_account.scheduled_for_deletion? ? %i[state_event] : permitted_account_attrs
params.require(:account).permit(*allowed_attrs)
end

def plans
Expand Down
Loading