From d9bd5f2e546d4fe6e6c6830d4944f6dd22dda125 Mon Sep 17 00:00:00 2001 From: Jen Spinney Date: Thu, 14 Dec 2017 10:43:05 +0000 Subject: [PATCH 1/4] Give better error message when share command fails for multiple reasons * If a user tries to share a service instance to multiple spaces at the same time and there are different problems with different potential target spaces, we now return a more detailed error message explainig which target spaces failed for which reason. * If a user tries to share a service instance into a target space for which they have write access, we now return a 422 instaed of a 403 for simplicity and API consistency. [#153629300] Signed-off-by: Alex Blease --- .../v3/service_instances_controller.rb | 41 +++++---- .../v3/service_instances_controller_spec.rb | 85 ++++++++++++++++--- 2 files changed, 99 insertions(+), 27 deletions(-) diff --git a/app/controllers/v3/service_instances_controller.rb b/app/controllers/v3/service_instances_controller.rb index ca347792f8b..d15fa051ede 100644 --- a/app/controllers/v3/service_instances_controller.rb +++ b/app/controllers/v3/service_instances_controller.rb @@ -37,8 +37,7 @@ def share_service_instance unprocessable!(message.errors.full_messages) unless message.valid? spaces = Space.where(guid: message.guids) - check_spaces_exist_and_are_readable!(message.guids, spaces) - check_spaces_are_writeable!(spaces) + check_spaces_exist_and_are_writeable!(service_instance, message.guids, spaces) share = ServiceInstanceShare.new share.create(service_instance, spaces, user_audit_info) @@ -76,26 +75,38 @@ def relationships_shared_spaces private - def check_spaces_are_writeable!(spaces) - unwriteable_spaces = spaces.reject do |space| + def check_spaces_exist_and_are_writeable!(service_instance, request_guids, found_spaces) + unreadable_space_guids = request_guids - found_spaces.map(&:guid) + + unreadable_spaces = found_spaces.reject do |space| + can_read_space?(space) + end + + unreadable_space_guids += unreadable_spaces.map(&:guid) + + unwriteable_spaces = found_spaces.reject do |space| can_write?(space.guid) end - unauthorized! unless unwriteable_spaces.empty? - end + unwriteable_space_guids = unwriteable_spaces.map(&:guid) - def check_spaces_exist_and_are_readable!(request_guids, found_spaces) - missing_guids = request_guids - found_spaces.map(&:guid) + unless unreadable_space_guids.empty? && unwriteable_space_guids.empty? + unreadable_guid_list = unreadable_space_guids.map { |g| "'#{g}'" }.join(', ') + unwriteable_guid_list = unwriteable_space_guids.map { |s| "'#{s}'" }.join(', ') - unreadable_spaces = found_spaces.reject do |space| - can_read_space?(space) - end + error_msg = '' + + unless unreadable_guid_list.empty? + error_msg += "Unable to share service instance #{service_instance.name} with spaces [#{unreadable_guid_list}]. Ensure the spaces exist and that you have access to them." + end - missing_guids += unreadable_spaces.map(&:guid) + unless unwriteable_guid_list.empty? + error_msg += "\n" unless unreadable_guid_list.empty? + error_msg += "Unable to share service instance #{service_instance.name} with spaces [#{unwriteable_guid_list}]. " + error_msg += 'Write permission is required in order to share a service instance with a space.' + end - unless missing_guids.empty? - guid_list = missing_guids.map { |g| "'#{g}'" }.join(', ') - unprocessable!("Unable to share to spaces [#{guid_list}] for the service instance. Ensure the spaces exist and you have access to them.") + unprocessable!(error_msg) end end diff --git a/spec/unit/controllers/v3/service_instances_controller_spec.rb b/spec/unit/controllers/v3/service_instances_controller_spec.rb index 8cc57246540..d9cb1468e38 100644 --- a/spec/unit/controllers/v3/service_instances_controller_spec.rb +++ b/spec/unit/controllers/v3/service_instances_controller_spec.rb @@ -334,11 +334,11 @@ it 'returns a 422' do post :share_service_instance, service_instance_guid: service_instance.guid, body: req_body expect(response.status).to eq 422 - expect(response.body).to include('Unable to share to spaces') - expect(response.body).to include('nonexistant-space-guid') + expect(response.body).to include("Unable to share service instance #{service_instance.name} with spaces ['nonexistant-space-guid']. ") + expect(response.body).to include('Ensure the spaces exist and that you have access to them.') + expect(response.body).not_to include('Write permission is required in order to share a service instance with a space.') end end - context 'when multiple target spaces do not exist' do before do req_body[:data] = [ @@ -351,10 +351,71 @@ it 'does not share to any of the valid target spaces and returns a 422' do post :share_service_instance, service_instance_guid: service_instance.guid, body: req_body expect(response.status).to eq 422 - expect(response.body).to include('Unable to share to spaces') - expect(response.body).to include('nonexistant-space-guid') - expect(response.body).to include('nonexistant-space-guid2') - expect(service_instance.shared_spaces).to_not include(target_space) + expect(response.body).to include("Unable to share service instance #{service_instance.name} with spaces ['nonexistant-space-guid', 'nonexistant-space-guid2']. ") + expect(response.body).to include('Ensure the spaces exist and that you have access to them.') + expect(response.body).not_to include('Write permission is required in order to share a service instance with a space.') + end + end + + context 'when the user is a SpaceAuditor in the target space' do + before do + set_current_user_as_role(role: 'space_developer', org: source_space.organization, space: source_space, user: user) + set_current_user_as_role(role: 'space_auditor', org: target_space.organization, space: target_space, user: user) + end + + it 'returns a 422' do + post :share_service_instance, service_instance_guid: service_instance.guid, body: req_body + expect(response.status).to eq 422 + expect(response.body).to include("Unable to share service instance #{service_instance.name} with spaces ['#{target_space.guid}']. ") + expect(response.body).to include('Write permission is required in order to share a service instance with a space.') + expect(response.body).not_to include('Ensure the spaces exist and that you have access to them.') + end + end + + context 'when some target spaces are unreadable and some are unwriteable' do + before do + set_current_user_as_role(role: 'space_developer', org: source_space.organization, space: source_space, user: user) + set_current_user_as_role(role: 'space_auditor', org: target_space.organization, space: target_space, user: user) + + req_body[:data] = [ + { guid: 'nonexistant-space-guid' }, + { guid: target_space.guid } + ] + end + + it 'returns a 422' do + post :share_service_instance, service_instance_guid: service_instance.guid, body: req_body + expect(response.status).to eq 422 + expect(response.body).to include( + "Unable to share service instance #{service_instance.name} with spaces ['nonexistant-space-guid']. Ensure the spaces exist and that you have access to them.\\n" \ + "Unable to share service instance #{service_instance.name} with spaces ['#{target_space.guid}']. "\ + 'Write permission is required in order to share a service instance with a space.' + ) + end + end + + context 'when the user is a SpaceAuditor in mulitple target spaces' do + let(:req_body) do + { + data: [ + { guid: target_space.guid }, + { guid: target_space2.guid } + ] + } + end + + before do + set_current_user_as_role(role: 'space_developer', org: source_space.organization, space: source_space, user: user) + set_current_user_as_role(role: 'space_auditor', org: target_space.organization, space: target_space, user: user) + set_current_user_as_role(role: 'space_auditor', org: target_space2.organization, space: target_space2, user: user) + end + + it 'returns a 422' do + post :share_service_instance, service_instance_guid: service_instance.guid, body: req_body + expect(response.status).to eq 422 + expect(response.body).to include( + "Unable to share service instance #{service_instance.name} with spaces ['#{target_space.guid}', '#{target_space2.guid}']. "\ + 'Write permission is required in order to share a service instance with a space.') end end @@ -399,11 +460,11 @@ role_to_expected_http_response = { 'admin' => 200, 'space_developer' => 200, - 'admin_read_only' => 403, - 'global_auditor' => 403, - 'space_manager' => 403, - 'space_auditor' => 403, - 'org_manager' => 403, + 'admin_read_only' => 422, + 'global_auditor' => 422, + 'space_manager' => 422, + 'space_auditor' => 422, + 'org_manager' => 422, 'org_auditor' => 422, 'org_billing_manager' => 422, }.freeze From 067a1ff3ff43e8c3650423f424f052555f6daaaf Mon Sep 17 00:00:00 2001 From: Alex Blease Date: Thu, 14 Dec 2017 15:55:05 +0000 Subject: [PATCH 2/4] Don't include unreadable_space_guids in unwriteable_space_guids list [#153629300] Signed-off-by: Jen Spinney --- .../v3/service_instances_controller.rb | 8 +++----- .../v3/service_instances_controller_spec.rb | 15 +++++++++++++++ 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/controllers/v3/service_instances_controller.rb b/app/controllers/v3/service_instances_controller.rb index d15fa051ede..90c1342090d 100644 --- a/app/controllers/v3/service_instances_controller.rb +++ b/app/controllers/v3/service_instances_controller.rb @@ -76,18 +76,16 @@ def relationships_shared_spaces private def check_spaces_exist_and_are_writeable!(service_instance, request_guids, found_spaces) - unreadable_space_guids = request_guids - found_spaces.map(&:guid) - unreadable_spaces = found_spaces.reject do |space| can_read_space?(space) end - unreadable_space_guids += unreadable_spaces.map(&:guid) - unwriteable_spaces = found_spaces.reject do |space| - can_write?(space.guid) + can_write_space?(space) || unreadable_spaces.include?(space) end + unreadable_space_guids = request_guids - found_spaces.map(&:guid) + unreadable_space_guids += unreadable_spaces.map(&:guid) unwriteable_space_guids = unwriteable_spaces.map(&:guid) unless unreadable_space_guids.empty? && unwriteable_space_guids.empty? diff --git a/spec/unit/controllers/v3/service_instances_controller_spec.rb b/spec/unit/controllers/v3/service_instances_controller_spec.rb index d9cb1468e38..049fc15b7ca 100644 --- a/spec/unit/controllers/v3/service_instances_controller_spec.rb +++ b/spec/unit/controllers/v3/service_instances_controller_spec.rb @@ -339,6 +339,21 @@ expect(response.body).not_to include('Write permission is required in order to share a service instance with a space.') end end + + context 'when the user does not have read access to the target space' do + before do + set_current_user_as_role(role: 'space_developer', org: source_space.organization, space: source_space, user: user) + end + + it 'returns a 422' do + post :share_service_instance, service_instance_guid: service_instance.guid, body: req_body + expect(response.status).to eq 422 + expect(response.body).to include("Unable to share service instance #{service_instance.name} with spaces ['#{target_space.guid}']. ") + expect(response.body).to include('Ensure the spaces exist and that you have access to them.') + expect(response.body).not_to include('Write permission is required in order to share a service instance with a space.') + end + end + context 'when multiple target spaces do not exist' do before do req_body[:data] = [ From a39eb2d42a1db63737afa74d76619d114c719cc6 Mon Sep 17 00:00:00 2001 From: Jen Spinney Date: Thu, 14 Dec 2017 17:00:43 +0000 Subject: [PATCH 3/4] Space guids in error message can be in any order [#153629300] Signed-off-by: Alex Blease --- .../controllers/v3/service_instances_controller_spec.rb | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/spec/unit/controllers/v3/service_instances_controller_spec.rb b/spec/unit/controllers/v3/service_instances_controller_spec.rb index 049fc15b7ca..94e10d0e742 100644 --- a/spec/unit/controllers/v3/service_instances_controller_spec.rb +++ b/spec/unit/controllers/v3/service_instances_controller_spec.rb @@ -428,9 +428,10 @@ it 'returns a 422' do post :share_service_instance, service_instance_guid: service_instance.guid, body: req_body expect(response.status).to eq 422 - expect(response.body).to include( - "Unable to share service instance #{service_instance.name} with spaces ['#{target_space.guid}', '#{target_space2.guid}']. "\ - 'Write permission is required in order to share a service instance with a space.') + expect(response.body).to include(target_space.guid) + expect(response.body).to include(target_space2.guid) + expect(response.body).to include("Unable to share service instance #{service_instance.name} with spaces ") + expect(response.body).to include('Write permission is required in order to share a service instance with a space.') end end From 22ae5694572e6a2d002f0230c9d1bfb8d24b9763 Mon Sep 17 00:00:00 2001 From: Jen Spinney Date: Wed, 27 Dec 2017 12:23:12 +0100 Subject: [PATCH 4/4] Refactor validation method to make error msg logic clearer [#153629300] --- .../v3/service_instances_controller.rb | 46 ++++++++++--------- 1 file changed, 24 insertions(+), 22 deletions(-) diff --git a/app/controllers/v3/service_instances_controller.rb b/app/controllers/v3/service_instances_controller.rb index 90c1342090d..a1a4211f649 100644 --- a/app/controllers/v3/service_instances_controller.rb +++ b/app/controllers/v3/service_instances_controller.rb @@ -76,35 +76,37 @@ def relationships_shared_spaces private def check_spaces_exist_and_are_writeable!(service_instance, request_guids, found_spaces) - unreadable_spaces = found_spaces.reject do |space| - can_read_space?(space) - end - - unwriteable_spaces = found_spaces.reject do |space| - can_write_space?(space) || unreadable_spaces.include?(space) - end + unreadable_spaces = found_spaces.reject { |s| can_read_space?(s) } + unwriteable_spaces = found_spaces.reject { |s| can_write_space?(s) || unreadable_spaces.include?(s) } - unreadable_space_guids = request_guids - found_spaces.map(&:guid) - unreadable_space_guids += unreadable_spaces.map(&:guid) + not_found_space_guids = request_guids - found_spaces.map(&:guid) + unreadable_space_guids = not_found_space_guids + unreadable_spaces.map(&:guid) unwriteable_space_guids = unwriteable_spaces.map(&:guid) - unless unreadable_space_guids.empty? && unwriteable_space_guids.empty? - unreadable_guid_list = unreadable_space_guids.map { |g| "'#{g}'" }.join(', ') - unwriteable_guid_list = unwriteable_space_guids.map { |s| "'#{s}'" }.join(', ') + if unreadable_space_guids.any? || unwriteable_space_guids.any? + unreadable_error = unreadable_error_message(service_instance.name, unreadable_space_guids) + unwriteable_error = unwriteable_error_message(service_instance.name, unwriteable_space_guids) - error_msg = '' + error_msg = [unreadable_error, unwriteable_error].map(&:presence).compact.join("\n") - unless unreadable_guid_list.empty? - error_msg += "Unable to share service instance #{service_instance.name} with spaces [#{unreadable_guid_list}]. Ensure the spaces exist and that you have access to them." - end + unprocessable!(error_msg) + end + end - unless unwriteable_guid_list.empty? - error_msg += "\n" unless unreadable_guid_list.empty? - error_msg += "Unable to share service instance #{service_instance.name} with spaces [#{unwriteable_guid_list}]. " - error_msg += 'Write permission is required in order to share a service instance with a space.' - end + def unreadable_error_message(service_instance_name, unreadable_space_guids) + if unreadable_space_guids.any? + unreadable_guid_list = unreadable_space_guids.map { |g| "'#{g}'" }.join(', ') - unprocessable!(error_msg) + "Unable to share service instance #{service_instance_name} with spaces [#{unreadable_guid_list}]. Ensure the spaces exist and that you have access to them." + end + end + + def unwriteable_error_message(service_instance_name, unwriteable_space_guids) + if unwriteable_space_guids.any? + unwriteable_guid_list = unwriteable_space_guids.map { |s| "'#{s}'" }.join(', ') + + "Unable to share service instance #{service_instance_name} with spaces [#{unwriteable_guid_list}]. "\ + 'Write permission is required in order to share a service instance with a space.' end end