Skip to content

Commit 7dd0212

Browse files
committed
Tighten the semantics of POST /v3/service_plans/:guid/visibility
- Allow POST only when type = organization. Otherwise fail with 422 Unprocessable Entity. - Ensure the target plan already has visibility type organization. If the plan’s visibility type is not organization, the request fails with 422 Unprocessable Entity.
1 parent 2b5ac3a commit 7dd0212

File tree

7 files changed

+74
-40
lines changed

7 files changed

+74
-40
lines changed

app/actions/v3/service_plan_visibility_update.rb

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,12 @@ def update(service_plan, message, append_organizations: false)
1111
type = message.type
1212
requested_org_guids = message.organizations&.pluck(:guid) || []
1313

14-
unprocessable!("cannot update plans with visibility type 'space'") if space?(service_plan)
14+
unprocessable!("cannot update plans with visibility type 'space'") if visibility_type_space?(service_plan)
15+
16+
if append_organizations # i.e. POST request
17+
unprocessable!("type must be 'organization'") unless org?(type)
18+
unprocessable!("can only append organizations to plans with visibility type 'organization'") unless visibility_type_org?(service_plan)
19+
end
1520

1621
service_plan.db.transaction do
1722
service_plan.lock!
@@ -69,10 +74,14 @@ def unprocessable!(message)
6974
raise UnprocessableRequest.new(message)
7075
end
7176

72-
def space?(service_plan)
77+
def visibility_type_space?(service_plan)
7378
service_plan.visibility_type == VCAP::CloudController::ServicePlanVisibilityTypes::SPACE
7479
end
7580

81+
def visibility_type_org?(service_plan)
82+
service_plan.visibility_type == VCAP::CloudController::ServicePlanVisibilityTypes::ORGANIZATION
83+
end
84+
7685
def org?(type)
7786
type == VCAP::CloudController::ServicePlanVisibilityTypes::ORGANIZATION
7887
end

app/controllers/v3/service_plan_visibility_controller.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -59,15 +59,15 @@ def event_repository
5959
VCAP::CloudController::Repositories::ServiceEventRepository::WithUserActor.new(user_audit_info)
6060
end
6161

62-
def update_visibility(opts={})
62+
def update_visibility(append_organizations: false)
6363
service_plan = ServicePlanFetcher.fetch(hashed_params[:guid])
6464
service_plan_not_found! unless service_plan.present? && visible_to_current_user?(plan: service_plan)
6565
unauthorized! unless current_user_can_write?(service_plan)
6666

6767
message = ServicePlanVisibilityUpdateMessage.new(hashed_params[:body])
6868
bad_request!(message.errors.full_messages) unless message.valid?
6969

70-
updated_service_plan = V3::ServicePlanVisibilityUpdate.new.update(service_plan, message, **opts)
70+
updated_service_plan = V3::ServicePlanVisibilityUpdate.new.update(service_plan, message, append_organizations:)
7171
event_repository.record_service_plan_update_visibility_event(service_plan, message.audit_hash)
7272
updated_service_plan
7373
rescue V3::ServicePlanVisibilityUpdate::UnprocessableRequest => e

docs/v3/source/includes/resources/service_plan_visibility/_apply.md.erb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Content-Type: application/json
2828
<%= yield_content :service_plan_visibility_response %>
2929
```
3030

31-
This endpoint applies a service plan visibility. It behaves similar to the [PATCH service plan visibility endpoint](#update-a-service-plan-visibility) but this endpoint will append to the existing list of organizations when the service plan is `organization` visible.
31+
This endpoint appends to the existing list of organizations. See [PATCH service plan visibility endpoint](#update-a-service-plan-visibility) to replace the existing list or change the visibility `type`.
3232

3333
#### Definition
3434
`POST /v3/service_plans/:guid/visibility`
@@ -37,8 +37,8 @@ This endpoint applies a service plan visibility. It behaves similar to the [PATC
3737

3838
Name | Type | Description
3939
---- | ---- | -----------
40-
**type** | _string_ | Denotes the visibility of the plan; can be `public`, `admin`, `organization`, see [_list of visibility types_](#list-of-visibility-types)
41-
**organizations** | _array of objects_ | Desired list of organizations GUIDs where the plan will be accessible; required if `type` is `organization`
40+
**type** | _string_ | Denotes the visibility of the plan; must be `organization`, see [_list of visibility types_](#list-of-visibility-types)
41+
**organizations** | _array of objects_ | Desired list of organizations GUIDs where the plan will be accessible; required.
4242

4343
#### Permitted roles
4444
|

docs/v3/source/includes/resources/service_plan_visibility/_update.md.erb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Content-Type: application/json
2828
<%= yield_content :new_org_service_plan_visibility %>
2929
```
3030

31-
This endpoint updates a service plan visibility. It behaves similar to the [POST service plan visibility endpoint](#apply-a-service-plan-visibility) but this endpoint will replace the existing list of organizations when the service plan is `organization` visible.
31+
This endpoint updates a service plan visibility. When `type` is `organization`, it will **replace** the existing list of organizations. See [POST service plan visibility endpoint](#apply-a-service-plan-visibility) to append to the existing list.
3232

3333
#### Definition
3434
`PATCH /v3/service_plans/:guid/visibility`

spec/request/service_plan_visibility_spec.rb

Lines changed: 6 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -454,21 +454,11 @@
454454
let(:service_plan) { VCAP::CloudController::ServicePlan.make(public: true) }
455455
let(:body) { { type: 'organization', organizations: [{ guid: org.guid }] } }
456456

457-
it 'updates the visibility type AND add the orgs' do
457+
it 'returns a 422 bad request' do
458458
post api_url, body.to_json, admin_headers
459459

460-
expect(parsed_response['type']).to eq 'organization'
461-
expect(parsed_response).not_to have_key('organizations')
462-
end
463-
464-
it 'creates an audit event' do
465-
post api_url, body.to_json, admin_headers
466-
event = VCAP::CloudController::Event.find(type: 'audit.service_plan_visibility.update')
467-
expect(event).to be
468-
expect(event.actee).to eq(service_plan.guid)
469-
expect(event.data).to include({
470-
'request' => body.with_indifferent_access
471-
})
460+
expect(last_response).to have_status_code(422)
461+
expect(parsed_response['errors'].first['detail']).to include("can only append organizations to plans with visibility type 'organization'")
472462
end
473463
end
474464

@@ -519,24 +509,11 @@
519509
context 'when request type is not "organization"' do
520510
let(:body) { { type: 'public' } }
521511

522-
it 'behaves like a PATCH' do
512+
it 'returns a 422 bad request' do
523513
post api_url, body.to_json, admin_headers
524-
expect(last_response).to have_status_code(200)
525514

526-
get api_url, {}, admin_headers
527-
expect(parsed_response).to eq({ 'type' => 'public' })
528-
visibilities = VCAP::CloudController::ServicePlanVisibility.where(service_plan:).all
529-
expect(visibilities).to be_empty
530-
end
531-
532-
it 'creates an audit event' do
533-
post api_url, body.to_json, admin_headers
534-
event = VCAP::CloudController::Event.find(type: 'audit.service_plan_visibility.update')
535-
expect(event).to be
536-
expect(event.actee).to eq(service_plan.guid)
537-
expect(event.data).to include({
538-
'request' => body.with_indifferent_access
539-
})
515+
expect(last_response).to have_status_code(422)
516+
expect(parsed_response['errors'].first['detail']).to include("type must be 'organization'")
540517
end
541518
end
542519

spec/support/lifecycle_tests_helpers.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def catalog
8888
end
8989

9090
def make_plan_visible(plan_guid)
91-
post "v3/service_plans/#{plan_guid}/visibility", { type: 'public' }.to_json, admin_headers
91+
patch "v3/service_plans/#{plan_guid}/visibility", { type: 'public' }.to_json, admin_headers
9292
expect(last_response).to have_status_code(200)
9393

9494
get "v3/service_plans/#{plan_guid}/visibility", nil, admin_headers

spec/unit/actions/v3/service_plan_visibility_update_spec.rb

Lines changed: 50 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,17 @@ module V3
1919
updated_plan = subject.update(service_plan, message)
2020
expect(updated_plan.reload.visibility_type).to eq 'public'
2121
end
22+
23+
context "when 'append_orgs' is set to true" do
24+
it 'raises an error' do
25+
expect do
26+
subject.update(service_plan, message, append_organizations: true)
27+
end.to raise_error(ServicePlanVisibilityUpdate::UnprocessableRequest, "type must be 'organization'")
28+
end
29+
end
2230
end
2331

24-
context 'and its being updated do "organization"' do
32+
context 'and its being updated to "organization"' do
2533
let(:org_guid) { Organization.make.guid }
2634
let(:other_org_guid) { Organization.make.guid }
2735
let(:params) do
@@ -38,6 +46,14 @@ module V3
3846

3947
expect(visible_org_guids).to contain_exactly(org_guid, other_org_guid)
4048
end
49+
50+
context "when 'append_orgs' is set to true" do
51+
it 'raises an error' do
52+
expect do
53+
subject.update(service_plan, message, append_organizations: true)
54+
end.to raise_error(ServicePlanVisibilityUpdate::UnprocessableRequest, "can only append organizations to plans with visibility type 'organization'")
55+
end
56+
end
4157
end
4258
end
4359

@@ -51,9 +67,17 @@ module V3
5167
updated_plan = subject.update(service_plan, message)
5268
expect(updated_plan.reload.visibility_type).to eq 'admin'
5369
end
70+
71+
context "when 'append_orgs' is set to true" do
72+
it 'raises an error' do
73+
expect do
74+
subject.update(service_plan, message, append_organizations: true)
75+
end.to raise_error(ServicePlanVisibilityUpdate::UnprocessableRequest, "type must be 'organization'")
76+
end
77+
end
5478
end
5579

56-
context 'and its being updated do "organization"' do
80+
context 'and its being updated to "organization"' do
5781
let(:org_guid) { Organization.make.guid }
5882
let(:other_org_guid) { Organization.make.guid }
5983
let(:params) do
@@ -70,6 +94,14 @@ module V3
7094

7195
expect(visible_org_guids).to contain_exactly(org_guid, other_org_guid)
7296
end
97+
98+
context "when 'append_orgs' is set to true" do
99+
it 'raises an error' do
100+
expect do
101+
subject.update(service_plan, message, append_organizations: true)
102+
end.to raise_error(ServicePlanVisibilityUpdate::UnprocessableRequest, "can only append organizations to plans with visibility type 'organization'")
103+
end
104+
end
73105
end
74106
end
75107

@@ -136,6 +168,14 @@ module V3
136168
expect(updated_plan.service_plan_visibilities).to be_empty
137169
expect(ServicePlanVisibility.where(service_plan:).all).to be_empty
138170
end
171+
172+
context "when 'append_orgs' is set to true" do
173+
it 'raises an error' do
174+
expect do
175+
subject.update(service_plan, message, append_organizations: true)
176+
end.to raise_error(ServicePlanVisibilityUpdate::UnprocessableRequest, "type must be 'organization'")
177+
end
178+
end
139179
end
140180

141181
context 'and its being updated to "public"' do
@@ -147,6 +187,14 @@ module V3
147187
expect(updated_plan.service_plan_visibilities).to be_empty
148188
expect(ServicePlanVisibility.where(service_plan:).all).to be_empty
149189
end
190+
191+
context "when 'append_orgs' is set to true" do
192+
it 'raises an error' do
193+
expect do
194+
subject.update(service_plan, message, append_organizations: true)
195+
end.to raise_error(ServicePlanVisibilityUpdate::UnprocessableRequest, "type must be 'organization'")
196+
end
197+
end
150198
end
151199
end
152200

0 commit comments

Comments
 (0)