Skip to content
This repository was archived by the owner on Jun 2, 2021. It is now read-only.

Commit d29537c

Browse files
FelisiaMpivotal-marcela-campo
authored andcommitted
v3(services) Service key creation validation of the related SI
[#174145595](https://www.pivotaltracker.com/story/show/174145595)
1 parent 576213f commit d29537c

3 files changed

Lines changed: 202 additions & 15 deletions

File tree

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
module VCAP::CloudController
2+
module V3
3+
class ServiceCredentialBindingKeyCreate
4+
class UnprocessableCreate < StandardError
5+
end
6+
7+
def precursor(service_instance, volume_mount_services_enabled: false)
8+
if service_instance.managed_instance?
9+
service_not_bindable! unless service_instance.service_plan.bindable?
10+
service_not_available! unless service_instance.service_plan.active?
11+
volume_mount_not_enabled! if service_instance.volume_service? && !volume_mount_services_enabled
12+
operation_in_progress! if service_instance.operation_in_progress?
13+
else
14+
key_not_supported_for_user_provided_service!
15+
end
16+
end
17+
18+
private
19+
20+
def key_not_supported_for_user_provided_service!
21+
raise UnprocessableCreate.new("Service credential bindings of type 'key' are not supported for user-provided service instances.")
22+
end
23+
24+
def operation_in_progress!
25+
raise UnprocessableCreate.new('There is an operation in progress for the service instance.')
26+
end
27+
28+
def service_not_bindable!
29+
raise UnprocessableCreate.new('Service plan does not allow bindings.')
30+
end
31+
32+
def service_not_available!
33+
raise UnprocessableCreate.new('Service plan is not available.')
34+
end
35+
36+
def volume_mount_not_enabled!
37+
raise UnprocessableCreate.new('Support for volume mount services is disabled.')
38+
end
39+
end
40+
end
41+
end

app/controllers/v3/service_credential_bindings_controller.rb

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
require 'actions/service_credential_binding_create'
2+
require 'actions/service_credential_binding_key_create'
23
require 'actions/service_credential_binding_delete'
34
require 'fetchers/service_credential_binding_fetcher'
45
require 'fetchers/service_credential_binding_list_fetcher'
@@ -44,12 +45,14 @@ def show
4445
def create
4546
message = build_create_message(hashed_params[:body])
4647

47-
if message.type == 'app'
48-
service_instance = VCAP::CloudController::ServiceInstance.first(guid: message.service_instance_guid)
49-
resource_not_accessible!('service instance', message.service_instance_guid) unless can_read_service_instance?(service_instance)
48+
service_instance = VCAP::CloudController::ServiceInstance.first(guid: message.service_instance_guid)
49+
resource_not_accessible!('service instance', message.service_instance_guid) unless can_read_service_instance?(service_instance)
5050

51+
case message.type
52+
when 'app'
5153
app = VCAP::CloudController::AppModel.first(guid: message.app_guid)
5254
resource_not_accessible!('app', message.app_guid) unless can_access_resource?(app)
55+
5356
unauthorized! unless can_write_to_space?(app.space)
5457

5558
action = V3::ServiceCredentialBindingCreate.new(user_audit_info, message.audit_hash)
@@ -63,11 +66,19 @@ def create
6366
action.bind(binding)
6467
render status: :created, json: Presenters::V3::ServiceCredentialBindingPresenter.new(binding).to_hash
6568
end
66-
else
69+
when 'key'
70+
unauthorized! unless can_write_to_space?(service_instance.space)
71+
72+
V3::ServiceCredentialBindingKeyCreate.new.precursor(
73+
service_instance,
74+
volume_mount_services_enabled: volume_services_enabled?
75+
)
76+
6777
head :not_implemented
6878
return
6979
end
70-
rescue V3::ServiceCredentialBindingCreate::UnprocessableCreate => e
80+
rescue V3::ServiceCredentialBindingCreate::UnprocessableCreate,
81+
V3::ServiceCredentialBindingKeyCreate::UnprocessableCreate => e
7182
unprocessable!(e.message)
7283
end
7384

@@ -284,6 +295,7 @@ def operation_in_progress!
284295
unprocessable!('There is an operation in progress for the service instance.')
285296
end
286297

298+
287299
def not_found!
288300
resource_not_found!(:service_credential_binding)
289301
end

spec/request/service_credential_bindings_spec.rb

Lines changed: 144 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -836,7 +836,6 @@ def check_filtered_bindings(*bindings)
836836
credentials: { password: 'foo' }
837837
}
838838
}
839-
let(:service_instance) { VCAP::CloudController::UserProvidedServiceInstance.make(space: space, **service_instance_details) }
840839
let(:service_instance_guid) { service_instance.guid }
841840

842841
context 'creating a credential binding to an app' do
@@ -983,6 +982,8 @@ def check_filtered_bindings(*bindings)
983982
end
984983

985984
context 'user-provided service' do
985+
let(:service_instance) { VCAP::CloudController::UserProvidedServiceInstance.make(space: space, **service_instance_details) }
986+
986987
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS do
987988
let(:expected_codes_and_responses) do
988989
Hash.new(code: 403).tap do |h|
@@ -1472,6 +1473,7 @@ def check_filtered_bindings(*bindings)
14721473
end
14731474

14741475
context 'creating a credential binding as a key' do
1476+
let(:service_instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space, **service_instance_details) }
14751477
let(:create_body) {
14761478
{
14771479
type: 'key',
@@ -1482,16 +1484,148 @@ def check_filtered_bindings(*bindings)
14821484
}.merge(request_extra)
14831485
}
14841486

1485-
it 'returns 422 when type is missing' do
1486-
create_body.delete(:type)
1487+
context 'permissions' do
1488+
context 'users in the originating service instance space' do
1489+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS do
1490+
let(:expected_codes_and_responses) do
1491+
Hash.new(code: 403).tap do |h|
1492+
h['space_developer'] = { code: 501 }
1493+
h['admin'] = { code: 501 }
1494+
h['org_billing_manager'] = { code: 422 }
1495+
h['org_auditor'] = { code: 422 }
1496+
h['no_role'] = { code: 422 }
1497+
end
1498+
end
1499+
end
1500+
end
14871501

1488-
api_call.call admin_headers
1489-
expect(last_response).to have_status_code(422)
1490-
expect(parsed_response['errors']).to include(include({
1491-
'detail' => include("Type must be 'app' or 'key'"),
1492-
'title' => 'CF-UnprocessableEntity',
1493-
'code' => 10008,
1494-
}))
1502+
context 'users in the space where the SI has been shared to' do
1503+
let(:orginal_org) { VCAP::CloudController::Organization.make }
1504+
let(:original_space) { VCAP::CloudController::Space.make(organization: orginal_org) }
1505+
let(:service_instance) { VCAP::CloudController::ManagedServiceInstance.make(space: original_space) }
1506+
1507+
before do
1508+
service_instance.add_shared_space(space)
1509+
end
1510+
1511+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS do
1512+
let(:expected_codes_and_responses) do
1513+
Hash.new(code: 403).tap do |h|
1514+
h['admin'] = { code: 501 }
1515+
h['org_billing_manager'] = { code: 422 }
1516+
h['org_auditor'] = { code: 422 }
1517+
h['no_role'] = { code: 422 }
1518+
end
1519+
end
1520+
end
1521+
end
1522+
end
1523+
1524+
context 'request validation' do
1525+
it 'returns 422 when type is missing' do
1526+
create_body.delete(:type)
1527+
1528+
api_call.call admin_headers
1529+
expect(last_response).to have_status_code(422)
1530+
expect(parsed_response['errors']).to include(include({
1531+
'detail' => include("Type must be 'app' or 'key'"),
1532+
'title' => 'CF-UnprocessableEntity',
1533+
'code' => 10008,
1534+
}))
1535+
end
1536+
1537+
it 'returns 422 when service instance relationship is not included' do
1538+
create_body[:relationships].delete(:service_instance)
1539+
api_call.call admin_headers
1540+
expect(last_response).to have_status_code(422)
1541+
expect(parsed_response['errors']).to include(include({
1542+
'detail' => include("Relationships 'relationships' must include one or more valid relationships"),
1543+
'title' => 'CF-UnprocessableEntity',
1544+
'code' => 10008,
1545+
}))
1546+
end
1547+
1548+
context 'when the service instance does not exist' do
1549+
let(:service_instance_guid) { 'fake-instance' }
1550+
1551+
it 'returns a 422 when the service instance does not exist' do
1552+
api_call.call admin_headers
1553+
1554+
expect(last_response).to have_status_code(422)
1555+
expect(parsed_response['errors']).to include(include({
1556+
'detail' => include("The service instance could not be found: 'fake-instance'"),
1557+
'title' => 'CF-UnprocessableEntity',
1558+
'code' => 10008,
1559+
}))
1560+
end
1561+
end
1562+
1563+
context 'when the user has no access to the service instance' do
1564+
let(:space_user) do
1565+
u = VCAP::CloudController::User.make
1566+
other_space.organization.add_user(u)
1567+
other_space.add_developer(u)
1568+
u
1569+
end
1570+
let(:space_dev_headers) { headers_for(space_user) }
1571+
1572+
it 'returns a 422' do
1573+
api_call.call space_dev_headers
1574+
expect(last_response).to have_status_code(422)
1575+
expect(parsed_response['errors']).to include(include({
1576+
'detail' => include("The service instance could not be found: '#{service_instance_guid}'"),
1577+
'title' => 'CF-UnprocessableEntity',
1578+
'code' => 10008,
1579+
}))
1580+
end
1581+
end
1582+
1583+
context 'when the service instance is user-provided' do
1584+
let(:service_instance) { VCAP::CloudController::UserProvidedServiceInstance.make(space: space, **service_instance_details) }
1585+
1586+
it 'returns a 422' do
1587+
api_call.call admin_headers
1588+
1589+
expect(last_response).to have_status_code(422)
1590+
expect(parsed_response['errors']).to include(include({
1591+
'detail' => include("Service credential bindings of type 'key' are not supported for user-provided service instances."),
1592+
'title' => 'CF-UnprocessableEntity',
1593+
'code' => 10008,
1594+
}))
1595+
end
1596+
end
1597+
1598+
context 'when the service instance is not bindable' do
1599+
let(:plan) { VCAP::CloudController::ServicePlan.make(bindable: false) }
1600+
let(:service_instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space, service_plan: plan) }
1601+
1602+
it 'returns a 422' do
1603+
api_call.call admin_headers
1604+
1605+
expect(last_response).to have_status_code(422)
1606+
expect(parsed_response['errors']).to include(include({
1607+
'detail' => include("Service plan does not allow bindings."),
1608+
'title' => 'CF-UnprocessableEntity',
1609+
'code' => 10008,
1610+
}))
1611+
end
1612+
end
1613+
1614+
context 'when the service instance is from unavailable plan' do
1615+
let(:plan) { VCAP::CloudController::ServicePlan.make(active: false) }
1616+
let(:service_instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space, service_plan: plan) }
1617+
1618+
it 'returns a 422' do
1619+
api_call.call admin_headers
1620+
1621+
expect(last_response).to have_status_code(422)
1622+
expect(parsed_response['errors']).to include(include({
1623+
'detail' => include("Service plan is not available."),
1624+
'title' => 'CF-UnprocessableEntity',
1625+
'code' => 10008,
1626+
}))
1627+
end
1628+
end
14951629
end
14961630

14971631
it 'should return 501' do

0 commit comments

Comments
 (0)