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

Commit 5c48fa4

Browse files
author
Derik Evangelista
authored
v3(bindings): Get details of a credential binding (cloudfoundry#1798)
[#173529480](https://www.pivotaltracker.com/story/show/173529480) [#173510836](https://www.pivotaltracker.com/story/show/173510836)
1 parent 7bcfee6 commit 5c48fa4

11 files changed

Lines changed: 301 additions & 8 deletions

File tree

app/controllers/v3/service_credential_bindings_controller.rb

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
require 'fetchers/service_credential_binding_fetcher'
22
require 'fetchers/service_credential_binding_list_fetcher'
33
require 'presenters/v3/service_credential_binding_presenter'
4+
require 'presenters/v3/service_credential_binding_details_presenter'
45
require 'messages/service_credential_bindings_list_message'
56
require 'messages/service_credential_bindings_show_message'
67
require 'decorators/include_binding_app_decorator'
@@ -33,6 +34,13 @@ def show
3334
render status: :ok, json: serialized(message)
3435
end
3536

37+
def details
38+
ensure_service_credential_binding_is_accessible!
39+
not_found! unless can_read_secrets_in_the_binding_space?
40+
41+
render status: :ok, json: credential_binding_details
42+
end
43+
3644
private
3745

3846
AVAILABLE_DECORATORS = [
@@ -65,6 +73,10 @@ def serialized(message)
6573
Presenters::V3::ServiceCredentialBindingPresenter.new(service_credential_binding, decorators: decorators(message)).to_hash
6674
end
6775

76+
def credential_binding_details
77+
Presenters::V3::ServiceCredentialBindingDetailsPresenter.new(service_credential_binding).to_hash
78+
end
79+
6880
def ensure_service_credential_binding_is_accessible!
6981
not_found! unless service_credential_binding_exists?
7082
end
@@ -77,6 +89,18 @@ def service_credential_binding_exists?
7789
!!service_credential_binding
7890
end
7991

92+
def can_read_secrets_in_the_binding_space?
93+
permission_queryer.can_read_secrets_in_space?(binding_space.guid, binding_org.guid)
94+
end
95+
96+
def binding_space
97+
service_credential_binding.space
98+
end
99+
100+
def binding_org
101+
service_credential_binding.space.organization
102+
end
103+
80104
def list_fetcher
81105
@list_fetcher ||= VCAP::CloudController::ServiceCredentialBindingListFetcher.new
82106
end

app/models/services/service_credential_binding_view.rb

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,18 @@ module Types
1414
Sequel.as(:service_keys__created_at, :created_at),
1515
Sequel.as(:service_keys__updated_at, :updated_at),
1616
Sequel.as(:service_keys__name, :name),
17+
Sequel.as(:service_keys__credentials, :credentials),
18+
Sequel.as(:service_keys__salt, :salt),
19+
Sequel.as(:service_keys__encryption_key_label, :encryption_key_label),
20+
Sequel.as(:service_keys__encryption_iterations, :encryption_iterations),
1721
Sequel.as(:service_instances__guid, :service_instance_guid),
1822
Sequel.as(:service_instances__name, :service_instance_name),
1923
Sequel.as(nil, :app_guid),
2024
Sequel.as(nil, :app_name),
21-
Sequel.as(:service_keys__service_instance_id, :service_instance_id)
25+
Sequel.as(:service_keys__service_instance_id, :service_instance_id),
26+
Sequel.as(nil, :syslog_drain_url),
27+
Sequel.as(nil, :volume_mounts),
28+
Sequel.as(nil, :volume_mounts_salt)
2229
).join(
2330
:service_instances, id: Sequel[:service_keys][:service_instance_id]
2431
).join(
@@ -33,11 +40,18 @@ module Types
3340
Sequel.as(:service_bindings__created_at, :created_at),
3441
Sequel.as(:service_bindings__updated_at, :updated_at),
3542
Sequel.as(:service_bindings__name, :name),
43+
Sequel.as(:service_bindings__credentials, :credentials),
44+
Sequel.as(:service_bindings__salt, :salt),
45+
Sequel.as(:service_bindings__encryption_key_label, :encryption_key_label),
46+
Sequel.as(:service_bindings__encryption_iterations, :encryption_iterations),
3647
Sequel.as(:service_bindings__service_instance_guid, :service_instance_guid),
3748
Sequel.as(:service_instances__name, :service_instance_name),
3849
Sequel.as(:service_bindings__app_guid, :app_guid),
3950
Sequel.as(:apps__name, :app_name),
40-
Sequel.as(nil, :service_instance_id)
51+
Sequel.as(nil, :service_instance_id),
52+
Sequel.as(:service_bindings__syslog_drain_url, :syslog_drain_url),
53+
Sequel.as(:service_bindings__volume_mounts, :volume_mounts),
54+
Sequel.as(:service_bindings__volume_mounts_salt, :volume_mounts_salt)
4155
).join(
4256
:apps, guid: Sequel[:service_bindings][:app_guid]
4357
).join(
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
require_relative 'base_presenter'
2+
3+
module VCAP
4+
module CloudController
5+
module Presenters
6+
module V3
7+
class ServiceCredentialBindingDetailsPresenter < BasePresenter
8+
def to_hash
9+
{
10+
credentials: credentials,
11+
syslog_drain_url: @resource.syslog_drain_url,
12+
volume_mounts: @resource.volume_mounts || nil
13+
}.compact
14+
end
15+
16+
private
17+
18+
def credentials
19+
JSON.parse(@resource.credentials)
20+
rescue JSON::ParserError
21+
nil
22+
end
23+
end
24+
end
25+
end
26+
end
27+
end

config/routes.rb

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -187,7 +187,11 @@
187187
# service_credential_bindings
188188
resources :service_credential_bindings,
189189
param: :guid,
190-
only: [:show, :index]
190+
only: [:show, :index] do
191+
member do
192+
get :details
193+
end
194+
end
191195

192196
# service_route_bindings
193197
resources :service_route_bindings,

docs/v3/source/includes/api_resources/_service_credential_bindings.erb

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,16 @@
6363
}
6464
<% end %>
6565

66+
<% content_for :single_service_credential_binding_details do %>
67+
{
68+
"credentials": {
69+
"connection": "mydb://user@password:example.com"
70+
},
71+
"syslog_drain_url": "http://syslog.example.com/drain",
72+
"volume_mounts": ["/vcap/data", "store"]
73+
}
74+
<% end %>
75+
6676
<% content_for :paginated_list_of_service_credential_bindings do |base_url| %>
6777
{
6878
"pagination": {
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
### Get a service credential binding details
2+
3+
```
4+
Example Request
5+
```
6+
7+
```shell
8+
curl "https://api.example.org/v3/service_credential_bindings/[guid]/details" \
9+
-X GET \
10+
-H "Authorization: bearer [token]"
11+
```
12+
13+
```
14+
Example Credential Binding Details Response
15+
```
16+
17+
```http
18+
HTTP/1.1 200 OK
19+
Content-Type: application/json
20+
21+
<%= yield_content :single_service_credential_binding_details %>
22+
```
23+
24+
This endpoint retrieves the service credential binding details.
25+
26+
#### Definition
27+
`GET /v3/service_service_credential_bindings/:guid/details`
28+
29+
30+
#### Permitted roles
31+
|
32+
--- | ---
33+
Admin |
34+
Admin Read-Only |
35+
Space Developer |

docs/v3/source/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -359,6 +359,7 @@ includes:
359359
- experimental_resources/service_credential_bindings/object
360360
- experimental_resources/service_credential_bindings/get
361361
- experimental_resources/service_credential_bindings/list
362+
- experimental_resources/service_credential_bindings/details
362363
- experimental_resources/service_instances/header
363364
- experimental_resources/service_instances/create
364365
- experimental_resources/service_instances/get

spec/request/service_credential_bindings_spec.rb

Lines changed: 94 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -344,7 +344,7 @@ def check_filtered_bindings(*bindings)
344344
end
345345
end
346346

347-
describe 'GET /v3/service_credential_bindings/:app_guid' do
347+
describe 'GET /v3/service_credential_bindings/:app_binding_guid' do
348348
let(:app_to_bind_to) { VCAP::CloudController::AppModel.make(space: space) }
349349
let(:app_binding) do
350350
VCAP::CloudController::ServiceBinding.make(service_instance: instance, app: app_to_bind_to).tap do |binding|
@@ -450,6 +450,99 @@ def check_filtered_bindings(*bindings)
450450
end
451451
end
452452

453+
describe 'GET /v3/service_credential_bindings/:binding_guid/details' do
454+
let(:details) {
455+
{
456+
service_instance: instance,
457+
volume_mounts: ['foo', 'bar'],
458+
syslog_drain_url: 'some-drain-url',
459+
credentials: '{"cred_key": "creds-val-64", "magic": true}'
460+
}
461+
}
462+
let(:app_binding) { VCAP::CloudController::ServiceBinding.make(**details) }
463+
let(:instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space) }
464+
let(:api_call) { ->(user_headers) { get "/v3/service_credential_bindings/#{app_binding.guid}/details", nil, user_headers } }
465+
let(:binding_credentials) {
466+
{
467+
credentials: {
468+
cred_key: 'creds-val-64',
469+
magic: true
470+
},
471+
syslog_drain_url: 'some-drain-url',
472+
volume_mounts: ['foo', 'bar']
473+
}
474+
}
475+
476+
context 'permissions' do
477+
it_behaves_like 'permissions for single object endpoint', ALL_PERMISSIONS do
478+
let(:expected_codes_and_responses) do
479+
Hash.new(code: 404).tap do |h|
480+
h['space_developer'] = { code: 200, response_object: binding_credentials }
481+
h['admin'] = { code: 200, response_object: binding_credentials }
482+
h['admin_read_only'] = { code: 200, response_object: binding_credentials }
483+
end
484+
end
485+
end
486+
487+
describe 'when the service instance is shared' do
488+
let(:originating_space) { VCAP::CloudController::Space.make }
489+
let(:shared_space) { space }
490+
let(:user_in_shared_space) {
491+
u = VCAP::CloudController::User.make
492+
shared_space.organization.add_user(u)
493+
shared_space.add_developer(u)
494+
u
495+
}
496+
let(:user_in_originating_space) do
497+
u = VCAP::CloudController::User.make
498+
originating_space.organization.add_user(u)
499+
originating_space.add_developer(u)
500+
u
501+
end
502+
let(:instance) { VCAP::CloudController::ManagedServiceInstance.make(space: originating_space) }
503+
let(:details) {
504+
{
505+
service_instance: instance,
506+
credentials: '{"password": "terces"}',
507+
app: source_app
508+
}
509+
}
510+
511+
before do
512+
instance.add_shared_space(shared_space)
513+
end
514+
515+
context 'bindings in the originating space' do
516+
let(:source_app) { VCAP::CloudController::AppModel.make(space: originating_space) }
517+
518+
it 'should return the credentials for users in the originating space' do
519+
api_call.call(headers_for(user_in_originating_space))
520+
expect(last_response).to have_status_code(200)
521+
end
522+
523+
it 'should return 404 for users in the shared space' do
524+
api_call.call(headers_for(user_in_shared_space))
525+
expect(last_response).to have_status_code(404)
526+
end
527+
end
528+
529+
context 'bindings in the shared space' do
530+
let(:source_app) { VCAP::CloudController::AppModel.make(space: shared_space) }
531+
532+
it 'should return 404 for users in the originating space' do
533+
api_call.call(headers_for(user_in_originating_space))
534+
expect(last_response).to have_status_code(404)
535+
end
536+
537+
it 'should return the credentials for users in the shared space space' do
538+
api_call.call(headers_for(user_in_shared_space))
539+
expect(last_response).to have_status_code(200)
540+
end
541+
end
542+
end
543+
end
544+
end
545+
453546
def expected_json(binding)
454547
{
455548
guid: binding.guid,

spec/support/fakes/blueprints.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,7 @@ module VCAP::CloudController
448448
syslog_drain_url { nil }
449449
type { 'app' }
450450
name { nil }
451+
guid { Sham.guid }
451452
end
452453

453454
ServiceKey.blueprint do

spec/unit/fetchers/service_credential_binding_list_fetcher_spec.rb

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,37 @@ module CloudController
1818
describe 'app and key bindings' do
1919
let(:space) { VCAP::CloudController::Space.make }
2020
let(:instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space) }
21-
let!(:key_binding) { VCAP::CloudController::ServiceKey.make(service_instance: instance) }
22-
let!(:app_binding) { VCAP::CloudController::ServiceBinding.make(service_instance: instance, name: Sham.name) }
21+
22+
let(:key_details) {
23+
{
24+
credentials: '{"some":"key"}'
25+
}
26+
}
27+
28+
let(:app_binding_details) {
29+
{
30+
credentials: '{"some":"app secret"}',
31+
syslog_drain_url: 'http://example.com/drain-app',
32+
volume_mounts: ['ccc', 'ddd']
33+
}
34+
}
35+
let!(:key_binding) { VCAP::CloudController::ServiceKey.make(service_instance: instance, **key_details) }
36+
let!(:app_binding) { VCAP::CloudController::ServiceBinding.make(service_instance: instance, name: Sham.name, **app_binding_details) }
2337

2438
context 'when getting everything' do
2539
it 'returns both key and app bindings' do
2640
bindings = fetcher.fetch(space_guids: :all, message: message).all
27-
binding_guids = bindings.map(&:guid)
41+
to_hash = ->(b) {
42+
{
43+
guid: b.guid,
44+
credentials: b.credentials,
45+
syslog_drain_url: b.try(:syslog_drain_url) || nil,
46+
volume_mounts: b.try(:volume_mount) || []
47+
}
48+
}
2849

29-
expect(binding_guids).to contain_exactly(key_binding.guid, app_binding.guid)
50+
actual_bindings = bindings.map { |b| to_hash.call(b) }
51+
expect(actual_bindings).to contain_exactly(to_hash.call(key_binding), to_hash.call(app_binding))
3052
end
3153
end
3254

0 commit comments

Comments
 (0)