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

Commit 8ee41d4

Browse files
author
Brian Butz
authored
v3(services): Return credential binding info for both key and app bindings (cloudfoundry#1750)
[#173148626](https://www.pivotaltracker.com/story/show/173148626)
1 parent 55a8b5f commit 8ee41d4

6 files changed

Lines changed: 272 additions & 44 deletions

File tree

app/controllers/v3/service_credential_bindings_controller.rb

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,48 @@
1+
require 'fetchers/service_credential_binding_fetcher'
2+
13
class ServiceCredentialBindingsController < ApplicationController
2-
before_action :ensure_service_key_exists!
4+
before_action :ensure_service_credential_binding_exists!
35
before_action :ensure_user_has_access!
46

57
def show
6-
render status: :ok, json: hashed_params.slice(:guid)
8+
render status: :ok, json: serialized
79
end
810

911
private
1012

11-
def ensure_service_key_exists!
12-
service_key_not_found! unless service_key_exists?
13+
def serialized
14+
{
15+
guid: service_credential_binding.guid,
16+
type: service_credential_binding.type
17+
}
18+
end
19+
20+
def ensure_service_credential_binding_exists!
21+
not_found! unless service_credential_binding_exists?
1322
end
1423

1524
def ensure_user_has_access!
16-
service_key_not_found! unless allowed_to_access_space?
25+
not_found! unless allowed_to_access_space?
1726
end
1827

19-
def service_key_not_found!
28+
def not_found!
2029
resource_not_found!(:service_credential_binding)
2130
end
2231

23-
def service_key
24-
@service_key ||= ServiceKey.first(guid: hashed_params[:guid])
32+
def service_credential_binding
33+
@service_credential_binding ||= fetcher.fetch(hashed_params[:guid])
34+
end
35+
36+
def fetcher
37+
@fetcher ||= VCAP::CloudController::ServiceCredentialBindingFetcher.new
2538
end
2639

27-
def service_key_exists?
28-
!!service_key
40+
def service_credential_binding_exists?
41+
!!service_credential_binding
2942
end
3043

3144
def allowed_to_access_space?
32-
space = service_key.service_instance.space
45+
space = service_credential_binding.space
3346

3447
permission_queryer.can_read_from_space?(space.guid, space.organization_guid)
3548
end
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
module VCAP
2+
module CloudController
3+
class ServiceCredentialBindingFetcher
4+
ServiceInstanceCredential = Struct.new(:guid, :type, :space).freeze
5+
6+
def fetch(guid)
7+
ServiceCredentialBinding::View.first(guid: guid).try do |db_binding|
8+
ServiceInstanceCredential.new(
9+
db_binding.guid,
10+
db_binding.type,
11+
db_binding.space
12+
)
13+
end
14+
end
15+
end
16+
end
17+
end

app/models.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,7 @@
130130
require 'models/services/service_usage_event'
131131
require 'models/services/service_key'
132132
require 'models/services/route_binding'
133+
require 'models/services/service_credential_binding_view'
133134

134135
require 'models/request_count'
135136
require 'models/orphaned_blob'
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
module VCAP
2+
module CloudController
3+
module ServiceCredentialBinding
4+
module Types
5+
SERVICE_KEY = 'key'.freeze
6+
SERVICE_BINDING = 'app'.freeze
7+
end
8+
9+
SERVICE_KEY_VIEW = VCAP::CloudController::ServiceKey.select(
10+
:guid,
11+
Sequel.as(Types::SERVICE_KEY, :type),
12+
:service_instance_id,
13+
Sequel.as(nil, :app_guid)
14+
).freeze
15+
16+
SERVICE_BINDING_VIEW = VCAP::CloudController::ServiceBinding.select(
17+
:guid,
18+
Sequel.as(Types::SERVICE_BINDING, :type),
19+
Sequel.as(nil, :service_instance_id),
20+
:app_guid
21+
).freeze
22+
23+
VIEW = [
24+
SERVICE_KEY_VIEW,
25+
SERVICE_BINDING_VIEW
26+
].inject do |statement, sub_select|
27+
statement.union(sub_select, all: true, from_self: false)
28+
end.freeze
29+
30+
class View < Sequel::Model(VIEW)
31+
many_to_one :service_instance, class: 'VCAP::CloudController::ServiceInstance'
32+
many_to_one :app, class: 'VCAP::CloudController::AppModel', key: :app_guid, primary_key: :guid, without_guid_generation: true
33+
34+
def space
35+
relation =
36+
case type
37+
when Types::SERVICE_BINDING
38+
app
39+
else
40+
service_instance
41+
end
42+
43+
relation.space
44+
end
45+
end
46+
end
47+
end
48+
end

spec/request/service_credential_bindings_spec.rb

Lines changed: 77 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -7,59 +7,103 @@
77
let(:space) { VCAP::CloudController::Space.make(organization: org) }
88
let(:other_space) { VCAP::CloudController::Space.make }
99

10+
context 'GET /v3/service_credential_bindings/:missing_key' do
11+
let(:api_call) { ->(user_headers) { get '/v3/service_credential_bindings/no-binding', nil, user_headers } }
12+
13+
let(:expected_codes_and_responses) do
14+
Hash.new(code: 404)
15+
end
16+
17+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
18+
end
19+
1020
describe 'GET /v3/service_credential_bindings/:key_guid' do
11-
context 'key exists' do
12-
let(:key) { VCAP::CloudController::ServiceKey.make(service_instance: instance) }
13-
let(:instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space) }
14-
let(:api_call) { ->(user_headers) { get "/v3/service_credential_bindings/#{key.guid}", nil, user_headers } }
21+
let(:key) { VCAP::CloudController::ServiceKey.make(service_instance: instance) }
22+
let(:instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space) }
23+
let(:api_call) { ->(user_headers) { get "/v3/service_credential_bindings/#{key.guid}", nil, user_headers } }
24+
let(:expected_object) { { guid: key.guid, type: 'key' } }
1525

16-
context 'global roles' do
26+
context 'global roles' do
27+
let(:expected_codes_and_responses) do
28+
Hash.new({ code: 200, response_object: expected_object })
29+
end
30+
31+
it_behaves_like 'permissions for single object endpoint', GLOBAL_SCOPES
32+
end
33+
34+
context 'local roles' do
35+
context 'user is in the original space of the service instance' do
1736
let(:expected_codes_and_responses) do
18-
Hash.new({ code: 200, response_object: { guid: key.guid } })
37+
Hash.new({ code: 200, response_object: expected_object }).tap do |h|
38+
h['org_auditor'] = { code: 404 }
39+
h['org_billing_manager'] = { code: 404 }
40+
h['no_role'] = { code: 404 }
41+
end
1942
end
2043

21-
it_behaves_like 'permissions for single object endpoint', GLOBAL_SCOPES
44+
it_behaves_like 'permissions for single object endpoint', LOCAL_ROLES
2245
end
2346

24-
context 'local roles' do
25-
context 'user is in the original space of the service instance' do
26-
let(:expected_codes_and_responses) do
27-
Hash.new({ code: 200, response_object: { guid: key.guid } }).tap do |h|
28-
h['org_auditor'] = { code: 404 }
29-
h['org_billing_manager'] = { code: 404 }
30-
h['no_role'] = { code: 404 }
31-
end
32-
end
47+
context 'user is in a space that the service instance is shared to' do
48+
let(:instance) { VCAP::CloudController::ManagedServiceInstance.make(space: other_space) }
3349

34-
it_behaves_like 'permissions for single object endpoint', LOCAL_ROLES
50+
before do
51+
instance.add_shared_space(space)
3552
end
3653

37-
context 'user is in a space that the service instance is shared to' do
38-
let(:instance) { VCAP::CloudController::ManagedServiceInstance.make(space: other_space) }
54+
let(:api_call) { ->(user_headers) { get "/v3/service_credential_bindings/#{key.guid}", nil, user_headers } }
3955

40-
before do
41-
instance.add_shared_space(space)
42-
end
56+
let(:expected_codes_and_responses) do
57+
Hash.new(code: 404)
58+
end
4359

44-
let(:api_call) { ->(user_headers) { get "/v3/service_credential_bindings/#{key.guid}", nil, user_headers } }
60+
it_behaves_like 'permissions for single object endpoint', LOCAL_ROLES
61+
end
62+
end
63+
end
4564

46-
let(:expected_codes_and_responses) do
47-
Hash.new(code: 404)
48-
end
65+
describe 'GET /v3/service_credential_bindings/:app_guid' do
66+
let(:app_binding) { VCAP::CloudController::ServiceBinding.make(service_instance: instance) }
67+
let(:instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space) }
68+
let(:api_call) { ->(user_headers) { get "/v3/service_credential_bindings/#{app_binding.guid}", nil, user_headers } }
69+
let(:expected_object) { { guid: app_binding.guid, type: 'app' } }
4970

50-
it_behaves_like 'permissions for single object endpoint', LOCAL_ROLES
51-
end
71+
context 'global roles' do
72+
let(:expected_codes_and_responses) do
73+
Hash.new({ code: 200, response_object: expected_object })
5274
end
75+
76+
it_behaves_like 'permissions for single object endpoint', GLOBAL_SCOPES
5377
end
5478

55-
context 'no such binding exists' do
56-
let(:api_call) { ->(user_headers) { get '/v3/service_credential_bindings/no-binding', nil, user_headers } }
79+
context 'local roles' do
80+
context 'user is in the original space of the service instance' do
81+
let(:expected_codes_and_responses) do
82+
Hash.new({ code: 200, response_object: expected_object }).tap do |h|
83+
h['org_auditor'] = { code: 404 }
84+
h['org_billing_manager'] = { code: 404 }
85+
h['no_role'] = { code: 404 }
86+
end
87+
end
5788

58-
let(:expected_codes_and_responses) do
59-
Hash.new(code: 404)
89+
it_behaves_like 'permissions for single object endpoint', LOCAL_ROLES
6090
end
6191

62-
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS
92+
context 'user is in a space that the service instance is shared to' do
93+
let(:instance) { VCAP::CloudController::ManagedServiceInstance.make(space: other_space) }
94+
95+
before do
96+
instance.add_shared_space(space)
97+
end
98+
99+
let(:api_call) { ->(user_headers) { get "/v3/service_credential_bindings/#{app_binding.guid}", nil, user_headers } }
100+
101+
let(:expected_codes_and_responses) do
102+
Hash.new(code: 404)
103+
end
104+
105+
it_behaves_like 'permissions for single object endpoint', LOCAL_ROLES
106+
end
63107
end
64108
end
65109
end
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
require 'spec_helper'
2+
require 'fetchers/service_credential_binding_fetcher'
3+
4+
module VCAP
5+
module CloudController
6+
RSpec.describe ServiceCredentialBindingFetcher do
7+
let(:fetcher) { ServiceCredentialBindingFetcher.new }
8+
9+
describe 'not a real guid' do
10+
it 'should return nothing' do
11+
credential_binding = fetcher.fetch('does-not-exist')
12+
expect(credential_binding).to be_nil
13+
end
14+
end
15+
16+
describe 'service keys' do
17+
let(:type) { 'key' }
18+
let(:space) { Space.make }
19+
let(:service_instance) { ManagedServiceInstance.make(space: space) }
20+
let!(:service_key) { ServiceKey.make(service_instance: service_instance) }
21+
22+
it 'can be found' do
23+
credential_binding = fetcher.fetch(service_key.guid)
24+
25+
expect(credential_binding).not_to be_nil
26+
expect(credential_binding.type).to eql(type)
27+
end
28+
29+
it 'can access the space' do
30+
credential_binding = fetcher.fetch(service_key.guid)
31+
32+
expect(credential_binding.space).to eql(space)
33+
end
34+
end
35+
36+
describe 'app bindings' do
37+
let(:type) { 'app' }
38+
39+
describe 'managed services' do
40+
let(:space) { Space.make }
41+
let(:service_instance) { ManagedServiceInstance.make(space: space) }
42+
let!(:app_binding) { ServiceBinding.make(service_instance: service_instance) }
43+
44+
it 'can be found' do
45+
credential_binding = fetcher.fetch(app_binding.guid)
46+
47+
expect(credential_binding).not_to be_nil
48+
expect(credential_binding.type).to eql(type)
49+
end
50+
51+
it 'can access the space' do
52+
credential_binding = fetcher.fetch(app_binding.guid)
53+
54+
expect(credential_binding.space).to eql(space)
55+
end
56+
end
57+
58+
describe 'user provided services' do
59+
let(:space) { Space.make }
60+
let(:service_instance) { UserProvidedServiceInstance.make(space: space) }
61+
let!(:app_binding) { ServiceBinding.make(service_instance: service_instance) }
62+
63+
it 'can be found' do
64+
credential_binding = fetcher.fetch(app_binding.guid)
65+
66+
expect(credential_binding).not_to be_nil
67+
expect(credential_binding.type).to eql(type)
68+
end
69+
70+
it 'can access the space' do
71+
credential_binding = fetcher.fetch(app_binding.guid)
72+
73+
expect(credential_binding.space).to eql(space)
74+
end
75+
end
76+
77+
describe 'shared services' do
78+
let(:space) { Space.make }
79+
let(:other_space) { Space.make }
80+
let(:service_instance) do
81+
ManagedServiceInstance.make(space: other_space).tap do |si|
82+
si.shared_spaces << space
83+
end
84+
end
85+
86+
let(:app) { AppModel.make(space: space) }
87+
let!(:app_binding) { ServiceBinding.make(service_instance: service_instance, app: app) }
88+
89+
it 'can be found' do
90+
credential_binding = fetcher.fetch(app_binding.guid)
91+
92+
expect(credential_binding).not_to be_nil
93+
expect(credential_binding.type).to eql(type)
94+
end
95+
96+
it "can access the app's space" do
97+
credential_binding = fetcher.fetch(app_binding.guid)
98+
99+
expect(credential_binding.space).to eql(space)
100+
end
101+
end
102+
end
103+
end
104+
end
105+
end

0 commit comments

Comments
 (0)