-
Notifications
You must be signed in to change notification settings - Fork 363
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add "create-service-key" to cloud controller, based on story #87057732
Add the rest API controller and DB model for "create service key", this patch includes the following part: 1. 1 new rest controller for "create-service-key" 2. DB migration script for adding a new table to record service keys 3. 1 new DB model for service key 4. Changes the v2 service broker client to support create service key This patch is submitted to implement story #87057732 in Service Key API More test cases will be added in other commits Signed-off-by: Tom Xing <xingzhou@cn.ibm.com>
- Loading branch information
Showing
12 changed files
with
795 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
module VCAP::CloudController | ||
class ServiceKeyAccess < BaseAccess | ||
def create?(service_key, params=nil) | ||
return true if admin_user? | ||
return false if service_key.in_suspended_org? | ||
service_key.service_instance.space.developers.include?(context.user) | ||
end | ||
|
||
def delete?(service_key) | ||
create?(service_key) | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
module VCAP::CloudController | ||
class ServiceKeyManager | ||
class ServiceInstanceNotFound < StandardError; end | ||
class ServiceInstanceNotBindable < StandardError; end | ||
|
||
def initialize(services_event_repository, access_validator, logger) | ||
@services_event_repository = services_event_repository | ||
@access_validator = access_validator | ||
@logger = logger | ||
end | ||
|
||
def create_service_key(request_attrs) | ||
service_instance = ServiceInstance.first(guid: request_attrs['service_instance_guid']) | ||
raise ServiceInstanceNotFound unless service_instance | ||
raise ServiceInstanceNotBindable unless service_instance.bindable? | ||
|
||
service_key = ServiceKey.new(request_attrs) | ||
@access_validator.validate_access(:create, service_key) | ||
raise Sequel::ValidationFailed.new(service_key) unless service_key.valid? | ||
|
||
lock_service_instance_by_blocking(service_instance) do | ||
attributes_to_update = service_key.client.bind(service_key) | ||
begin | ||
service_key.set_all(attributes_to_update) | ||
service_key.save | ||
rescue | ||
safe_unbind_instance(service_key) | ||
raise | ||
end | ||
end | ||
|
||
service_key | ||
end | ||
|
||
private | ||
|
||
def safe_unbind_instance(service_key) | ||
service_key.client.unbind(service_key) | ||
rescue => e | ||
@logger.error "Unable to unbind #{service_key}: #{e}" | ||
end | ||
|
||
def lock_service_instance_by_blocking(service_instance, &block) | ||
return block.call unless service_instance.managed_instance? | ||
|
||
original_attributes = service_instance.last_operation.try(:to_hash) | ||
begin | ||
service_instance.lock_by_failing_other_operations('update') do | ||
block.call | ||
end | ||
ensure | ||
if original_attributes | ||
service_instance.last_operation.set_all(original_attributes) | ||
service_instance.last_operation.save | ||
else | ||
service_instance.service_instance_operation.destroy | ||
service_instance.save | ||
end | ||
end | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
require 'services/api' | ||
|
||
module VCAP::CloudController | ||
class ServiceKeysController < RestController::ModelController | ||
define_attributes do | ||
to_one :service_instance | ||
attribute :name, String | ||
end | ||
|
||
post path, :create | ||
def create | ||
@request_attrs = self.class::CreateMessage.decode(body).extract(stringify_keys: true) | ||
logger.debug 'cc.create', model: self.class.model_class_name, attributes: request_attrs | ||
raise InvalidRequest unless request_attrs | ||
service_key_manager = ServiceKeyManager.new(@services_event_repository, self, logger) | ||
service_key = service_key_manager.create_service_key(@request_attrs) | ||
[HTTP::CREATED, | ||
{ 'Location' => "#{self.class.path}/#{service_key.guid}" }, | ||
object_renderer.render_json(self.class, service_key, @opts) | ||
] | ||
rescue ServiceKeyManager::ServiceInstanceNotFound | ||
raise VCAP::Errors::ApiError.new_from_details('ServiceInstanceNotFound', @request_attrs['service_instance_guid']) | ||
rescue ServiceKeyManager::ServiceInstanceNotBindable | ||
raise VCAP::Errors::ApiError.new_from_details('UnbindableService') | ||
end | ||
|
||
private | ||
|
||
def self.translate_validation_exception(e, attributes) | ||
unique_errors = e.errors.on([:name, :service_instance_id]) | ||
if unique_errors && unique_errors.include?(:unique) | ||
Errors::ApiError.new_from_details('ServiceKeyTaken', "#{attributes['name']} #{attributes['service_instance_guid']}") | ||
elsif e.errors.on(:service_instance) && e.errors.on(:service_instance).include?(:presence) | ||
Errors::ApiError.new_from_details('ServiceInstanceNotFound', attributes['service_instance_guid']) | ||
else | ||
Errors::ApiError.new_from_details('ServiceKeyInvalid', e.errors.full_messages) | ||
end | ||
end | ||
|
||
define_messages | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
module VCAP::CloudController | ||
class ServiceKey < Sequel::Model | ||
class InvalidAppAndServiceRelation < StandardError; end | ||
|
||
many_to_one :service_instance | ||
|
||
export_attributes :name, :service_instance_guid, :credentials, :syslog_drain_url | ||
|
||
import_attributes :name, :service_instance_guid, :credentials, :syslog_drain_url | ||
|
||
delegate :client, :service, :service_plan, to: :service_instance | ||
|
||
plugin :after_initialize | ||
|
||
encrypt :credentials, salt: :salt | ||
|
||
def to_hash(opts={}) | ||
if !VCAP::CloudController::SecurityContext.admin? && !service_instance.space.developers.include?(VCAP::CloudController::SecurityContext.current_user) | ||
opts.merge!({ redact: ['credentials'] }) | ||
end | ||
super(opts) | ||
end | ||
|
||
def in_suspended_org? | ||
space.in_suspended_org? | ||
end | ||
|
||
def space | ||
service_instance.space | ||
end | ||
|
||
def validate | ||
validates_presence :name | ||
validates_presence :service_instance | ||
validates_unique [:name, :service_instance_id] | ||
validate_logging_service_binding if service_instance.respond_to?(:service_plan) | ||
end | ||
|
||
def validate_logging_service_binding | ||
return if syslog_drain_url.blank? | ||
service_advertised_as_logging_service = service_instance.service_plan.service.requires.include?('syslog_drain') | ||
raise VCAP::Errors::ApiError.new_from_details('InvalidLoggingServiceBinding') unless service_advertised_as_logging_service | ||
end | ||
|
||
def credentials_with_serialization=(val) | ||
self.credentials_without_serialization = MultiJson.dump(val) | ||
end | ||
alias_method_chain :credentials=, 'serialization' | ||
|
||
def credentials_with_serialization | ||
string = credentials_without_serialization | ||
return if string.blank? | ||
MultiJson.load string | ||
end | ||
alias_method_chain :credentials, 'serialization' | ||
|
||
def create! | ||
client.bind(self) | ||
begin | ||
save | ||
rescue => e | ||
safe_unbind | ||
raise e | ||
end | ||
end | ||
|
||
def self.user_visibility_filter(user) | ||
{ service_instance: ServiceInstance.user_visible(user) } | ||
end | ||
|
||
def after_initialize | ||
super | ||
self.guid ||= SecureRandom.uuid | ||
end | ||
|
||
def before_destroy | ||
client.unbind(self) | ||
super | ||
end | ||
|
||
def logger | ||
@logger ||= Steno.logger('cc.models.service_key') | ||
end | ||
|
||
private | ||
|
||
def safe_unbind | ||
client.unbind(self) | ||
rescue => unbind_e | ||
logger.error "Unable to unbind #{self}: #{unbind_e}" | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
Sequel.migration do | ||
change do | ||
create_table(:service_keys) do | ||
VCAP::Migration.common(self, :sk) | ||
String :name, null: false | ||
String :salt | ||
String :syslog_drain_url | ||
String :credentials, null: false, size: 2048 | ||
Integer :service_instance_id, null: false | ||
foreign_key [:service_instance_id], :service_instances, name: :fk_svc_key_svc_instance_id | ||
index [:name, :service_instance_id], unique: true, name: :svc_key_name_instance_id_index | ||
end | ||
end | ||
end |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,120 @@ | ||
require 'spec_helper' | ||
|
||
module VCAP::CloudController | ||
describe ServiceKeyAccess, type: :access do | ||
subject(:access) { ServiceKeyAccess.new(Security::AccessContext.new) } | ||
let(:token) { { 'scope' => ['cloud_controller.read', 'cloud_controller.write'] } } | ||
|
||
let(:user) { VCAP::CloudController::User.make } | ||
let(:service) { VCAP::CloudController::Service.make } | ||
let(:org) { VCAP::CloudController::Organization.make } | ||
let(:space) { VCAP::CloudController::Space.make(organization: org) } | ||
let(:service_instance) { VCAP::CloudController::ManagedServiceInstance.make(space: space) } | ||
|
||
let(:object) { VCAP::CloudController::ServiceKey.make(name: 'fake-key', service_instance: service_instance) } | ||
|
||
before do | ||
SecurityContext.set(user, token) | ||
end | ||
|
||
after do | ||
SecurityContext.clear | ||
end | ||
|
||
it_should_behave_like :admin_full_access | ||
|
||
context 'for a logged in user (defensive)' do | ||
it_behaves_like :no_access | ||
end | ||
|
||
context 'a user that isnt logged in (defensive)' do | ||
let(:user) { nil } | ||
it_behaves_like :no_access | ||
end | ||
|
||
context 'organization manager (defensive)' do | ||
before { org.add_manager(user) } | ||
it_behaves_like :no_access | ||
end | ||
|
||
context 'organization billing manager (defensive)' do | ||
before { org.add_billing_manager(user) } | ||
it_behaves_like :no_access | ||
end | ||
|
||
context 'organization auditor (defensive)' do | ||
before { org.add_auditor(user) } | ||
it_behaves_like :no_access | ||
end | ||
|
||
context 'organization user (defensive)' do | ||
before { org.add_user(user) } | ||
it_behaves_like :no_access | ||
end | ||
|
||
context 'space auditor' do | ||
before do | ||
org.add_user(user) | ||
space.add_auditor(user) | ||
end | ||
|
||
it_behaves_like :read_only | ||
end | ||
|
||
context 'space manager (defensive)' do | ||
before do | ||
org.add_user(user) | ||
space.add_manager(user) | ||
end | ||
|
||
it_behaves_like :no_access | ||
end | ||
|
||
context 'space developer' do | ||
before do | ||
org.add_user(user) | ||
space.add_developer(user) | ||
end | ||
|
||
it { is_expected.to allow_op_on_object :create, object } | ||
it { is_expected.to allow_op_on_object :read, object } | ||
it { is_expected.not_to allow_op_on_object :read_for_update, object } | ||
it { is_expected.to allow_op_on_object :delete, object } | ||
|
||
context 'when the organization is suspended' do | ||
before { allow(object).to receive(:in_suspended_org?).and_return(true) } | ||
it_behaves_like :read_only | ||
end | ||
end | ||
|
||
context 'any user using client without cloud_controller.write' do | ||
let(:token) { { 'scope' => ['cloud_controller.read'] } } | ||
before do | ||
org.add_user(user) | ||
org.add_manager(user) | ||
org.add_billing_manager(user) | ||
org.add_auditor(user) | ||
space.add_manager(user) | ||
space.add_developer(user) | ||
space.add_auditor(user) | ||
end | ||
|
||
it_behaves_like :read_only | ||
end | ||
|
||
context 'any user using client without cloud_controller.read' do | ||
let(:token) { { 'scope' => [] } } | ||
before do | ||
org.add_user(user) | ||
org.add_manager(user) | ||
org.add_billing_manager(user) | ||
org.add_auditor(user) | ||
space.add_manager(user) | ||
space.add_developer(user) | ||
space.add_auditor(user) | ||
end | ||
|
||
it_behaves_like :no_access | ||
end | ||
end | ||
end |
Oops, something went wrong.