-
Notifications
You must be signed in to change notification settings - Fork 74
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
THREESCALE-6412: Fix admin member user permissions for services (also THREESCALE-11202) #3929
base: master
Are you sure you want to change the base?
Changes from all commits
c969ffb
5dfa172
1875182
032f35c
c28331e
7a4fdb5
06db7c6
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 |
---|---|---|
|
@@ -62,10 +62,15 @@ def self.issued_by(issuer, *ids) | |
where.has { plan_id.in( scope ) } | ||
end | ||
|
||
# FIXME: this probably shouldn't return `all`, it's dangerous if it is accidentally | ||
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 don't really want to fix it in this PR as it is already too long. And it was already this way before... 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. Maybe use scope |
||
# used as a separate scope, and not merged with other scopes that filters by account previously | ||
scope :permitted_for, ->(user) { | ||
next all unless user.forbidden_some_services? | ||
permitted_services_status = user.permitted_services_status | ||
next none if permitted_services_status == :none | ||
|
||
where(service_id: user.member_permission_service_ids) | ||
next all if permitted_services_status == :all | ||
|
||
merge(permitted_services_status == :selected ? where(service_id: user.member_permission_service_ids) : {}) | ||
Comment on lines
-66
to
+73
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. Could you explain what is the actual difference in behavior compared to before. I don't really understand it and I can't properly review if I don't understand this. 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, I'll try... From the user point of view, the issue was that a freshly created member that has NO permissions defined at all had access to some pages - specifically mapping rules, policies, metrics etc. They were not visible from the menus, but using a direct link was possible. The main reason was in this scope, together with the cancan definition:
So, if the user had no Currently, it is considered that the user has access to all services ONLY if in addition to not having So, a newly created member, that does not have any permissions at all will not be considered as "being able to see all services". 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.
Do you have a reference for this? 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, it's not that straightforward, you kind of need to follow the whole chain. So, this is what the scope use to say:
So, if
which basically means 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. To clarify, the above is for the But when testing I realised the |
||
} | ||
|
||
# Return contracts bought by given account. | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,7 @@ | |
module User::Permissions | ||
extend ActiveSupport::Concern | ||
|
||
ATTRIBUTES = %I[ role member_permission_ids member_permission_service_ids ] | ||
ATTRIBUTES = %I[role member_permission_ids member_permission_service_ids].freeze | ||
|
||
included do | ||
has_many :member_permissions, dependent: :destroy, autosave: true | ||
|
@@ -16,15 +16,12 @@ module User::Permissions | |
alias_attribute :allowed_service_ids, :member_permission_service_ids | ||
end | ||
|
||
#TODO: this is repeated from bcms_hacks plugins because of some loading problem | ||
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 bcms ("browser CMS") thing was removed long time ago, so this comment is not relevant anymore. |
||
def has_permission?(permission) | ||
return true if account.provider? && admin? | ||
return true if account.provider? && admin? | ||
return false if account.buyer? | ||
|
||
# check = Permission.count(:include => {:groups => :users}, :conditions => ["users.id = ? and permissions.name=?", id, permission]) > 0 | ||
|
||
admin_sections.include?(permission.to_sym).tap do |check| | ||
logger.info "~> #{username} has_permission?(#{permission}) => #{check}" | ||
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 don't think it's nice to pollute logs with these unnecessary logs 😬 |
||
logger.debug "~> #{username} has_permission?(#{permission}) => #{check}" | ||
end | ||
end | ||
|
||
|
@@ -60,11 +57,12 @@ def member_permission_ids=(roles) | |
end | ||
|
||
def member_permission_service_ids=(service_ids) | ||
if service_ids.present? | ||
service_ids = Array(service_ids).compact.map(&:to_i) | ||
if service_ids.is_a? Array | ||
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. API and UI send these values in a "funny way", like I changed the way the So While the API/UI behavior seems to be still OK. |
||
# remove all non-integer values | ||
service_ids = service_ids.map { Integer(_1, exception: false) }.compact_blank | ||
member_permission = services_member_permission || member_permissions.build(admin_section: :services) | ||
member_permission.service_ids = service_ids & existing_service_ids | ||
else | ||
elsif service_ids.blank? | ||
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 allows us to set |
||
self.member_permissions = member_permissions - [services_member_permission].compact | ||
end | ||
ensure | ||
|
@@ -80,6 +78,7 @@ def existing_service_ids | |
# returns [] if no services are enabled, and nil if all (current and future) services are enabled | ||
def member_permission_service_ids | ||
return nil if admin? || !services_member_permission | ||
|
||
permitted_service_ids = services_member_permission.try(:service_ids) || [] | ||
permitted_service_ids & existing_service_ids | ||
end | ||
|
@@ -89,22 +88,34 @@ def services_member_permission | |
end | ||
|
||
def has_access_to_service?(service) | ||
services_permission = services_member_permission | ||
services_permission && services_permission.has_service?(service) || has_access_to_all_services? | ||
services_member_permission&.has_service?(service) || has_access_to_all_services? | ||
end | ||
|
||
# Returns: | ||
# :none - if no services are allowed | ||
# :all - if all services are allowed for the selected service-related permissions | ||
# :selected - if a subset of services is allowed for the selected service-related permissions | ||
def permitted_services_status | ||
if admin? || (service_permissions_selected? && member_permission_service_ids.nil?) | ||
:all | ||
elsif service_permissions_selected? && member_permission_service_ids&.any? | ||
:selected | ||
else | ||
:none | ||
end | ||
end | ||
|
||
# Lack of the services section means it is the old permission system where everyone had access | ||
# to every service. So to limit the scope only for new users, we start adding this permission. | ||
def has_access_to_all_services? | ||
!admin_sections.include?(:services) || admin? | ||
permitted_services_status == :all | ||
end | ||
|
||
def forbidden_some_services? | ||
!has_access_to_all_services? && account.provider_can_use?(:service_permissions) | ||
# Returns whether the user has access to any service-related permissions (partners, plans or monitoring) | ||
def service_permissions_selected? | ||
(member_permission_ids & AdminSection::SERVICE_PERMISSIONS).any? | ||
end | ||
|
||
def access_to_service_admin_sections? | ||
(member_permission_ids & AdminSection::SERVICE_PERMISSIONS).any? && accessible_services? | ||
service_permissions_selected? && accessible_services? | ||
end | ||
|
||
def reload(*) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -21,7 +21,8 @@ | |
cannot %i[destroy update_role], user | ||
|
||
# Services | ||
can %i[read show edit update], Service, user.accessible_services.where_values_hash | ||
user_accessible_services = user.accessible_services | ||
can %i[read show edit update], Service, user_accessible_services.where_values_hash unless user_accessible_services.is_a? ActiveRecord::NullRelation | ||
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.
Otherwise, in case "no services are allowed", while Adding this |
||
|
||
# | ||
# Events | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2812,7 +2812,7 @@ | |
}, | ||
"allowed_service_ids[]": { | ||
"type": "array", | ||
"description": "IDs of the services that need to be enabled, URL-encoded array. To disable all services, set the value to '[]'. To enable all services, add parameter 'allowed_service_ids[]' with no value to the 'curl' command (can't be done in ActiveDocs)", | ||
"description": "IDs of the services that need to be enabled, URL-encoded array. To disable all services, send `allowed_service_ids[]` with an empty value, e.g. `allowed_service_ids%5B%5D=`. `<allowed_service_ids/>` or `\"allowed_service_ids\":[]` in the response indicate that no services are allowed. To enable all services, send 'allowed_service_ids' with no value to the 'curl' command, e.g. `allowed_service_ids=` (can't be done in ActiveDocs). Missing `<allowed_service_ids>` or `\"allowed_service_ids\":null` indicate that all services are allowed.", | ||
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. As we talked on slack, the API Docs are very counter intuitive for this endpoint, for instance, I expect "Send empty value" to set I'm aware on how cumbersome is making any small change in SwaggerUI, so if this can't be properly fixed, at least I think we should mention in the description how to set the 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 agree. I actually updated the description of this parameter, trying to make it more clear, but it is true that unfortunately "send It used to say "set it to |
||
"items": { | ||
"type": "integer" | ||
} | ||
|
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -55,4 +55,15 @@ def setup | |||||
get edit_admin_service_policies_path(@service) | ||||||
assert_response :service_unavailable | ||||||
end | ||||||
|
||||||
test 'policies edit for members with no permissions' do | ||||||
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. @akostadinov I applied your suggestion for memoization and added tests for the 2 pages mentioned in the JIRA description. They fail on 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. About this quote in the PR description:
That's actually not true, right? if I try to access So if it's fixed, why not removing this?: porta/config/abilities/provider_any.rb Lines 14 to 15 in f291877
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, indeed, as As mentioned in the PR description, |
||||||
Policies::PoliciesListService.unstub(:call!) | ||||||
Policies::PoliciesListService.expects(:call!).never | ||||||
member = FactoryBot.create(:member, account: @provider, state: 'active') | ||||||
logout! && login!(@provider, user: member) | ||||||
|
||||||
get edit_admin_service_policies_path(@service) | ||||||
|
||||||
assert_response :not_found | ||||||
end | ||||||
end |
Original file line number | Diff line number | Diff line change | ||
---|---|---|---|---|
|
@@ -56,4 +56,14 @@ def setup | |||
assert_response :success | ||||
assert_equal '{"foo":"bar"}', response.body | ||||
end | ||||
|
||||
test 'proxy configs index for members with no permissions' do | ||||
member = FactoryBot.create(:member, account: @provider, state: 'active') | ||||
logout! && login!(@provider, user: member) | ||||
|
||||
get admin_service_proxy_configs_path(service_id: service, environment: 'sandbox') | ||||
|
||||
# TODO: maybe this should be be a :forbidden | ||||
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 agree, but, why does it return a 404? how did your changes affect routes? 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, right, the thing is that the first thing that the controller does is trying to find a service:
And as That's also the reason why all of these controllers return 404 and not 403 when trying to access - they all load I am not sure if it's worth fixing to be honest. And actually it also makes sense - the user doesn't need to know whether the service exists or not, if they don't have permission for it - it's as if it doesn't exist for them 😬 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 think returning https://www.rfc-editor.org/rfc/rfc7231#section-6.5.3
|
||||
assert_response :not_found | ||||
end | ||||
end |
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.
After fixing the permission issue for services, the controller started responding with 404 when 403 was expected (i.e. the service is not allowed AND the required permission is missing).
So, swapping these actions results in the expected behavior, and is more in line with some other controllers.