diff --git a/app/actions/organization_quota_apply.rb b/app/actions/organization_quota_apply.rb index 65af7672d6..d75f1fe3b3 100644 --- a/app/actions/organization_quota_apply.rb +++ b/app/actions/organization_quota_apply.rb @@ -1,8 +1,14 @@ +require 'repositories/organization_quota_event_repository' + module VCAP::CloudController class OrganizationQuotaApply class Error < ::StandardError end + def initialize(user_audit_info) + @user_audit_info = user_audit_info + end + def apply(org_quota, message) orgs = valid_orgs(message.organization_guids) @@ -18,7 +24,10 @@ def apply(org_quota, message) end QuotaDefinition.db.transaction do - orgs.each { |org| org_quota.add_organization(org) } + orgs.each do |org| + org_quota.add_organization(org) + Repositories::OrganizationQuotaEventRepository.new.record_organization_quota_apply(org_quota, org, @user_audit_info) + end end rescue Sequel::ValidationFailed => e error!(e.message) diff --git a/app/actions/organization_quota_delete.rb b/app/actions/organization_quota_delete.rb index ed1b48c98b..e0355c9c83 100644 --- a/app/actions/organization_quota_delete.rb +++ b/app/actions/organization_quota_delete.rb @@ -1,8 +1,15 @@ +require 'repositories/organization_quota_event_repository' + module VCAP::CloudController class OrganizationQuotaDeleteAction + def initialize(user_audit_info) + @user_audit_info = user_audit_info + end + def delete(organization_quotas) organization_quotas.each do |org_quota| QuotaDefinition.db.transaction do + Repositories::OrganizationQuotaEventRepository.new.record_organization_quota_delete(org_quota, @user_audit_info) org_quota.destroy end end diff --git a/app/actions/organization_quotas_create.rb b/app/actions/organization_quotas_create.rb index 29b3914ad8..95c47189bb 100644 --- a/app/actions/organization_quotas_create.rb +++ b/app/actions/organization_quotas_create.rb @@ -1,8 +1,14 @@ +require 'repositories/organization_quota_event_repository' + module VCAP::CloudController class OrganizationQuotasCreate class Error < ::StandardError end + def initialize(user_audit_info) + @user_audit_info = user_audit_info + end + # rubocop:todo Metrics/CyclomaticComplexity def create(message) org_quota = nil @@ -33,6 +39,8 @@ def create(message) orgs = valid_orgs(message.organization_guids) orgs.each { |org| org_quota.add_organization(org) } + + Repositories::OrganizationQuotaEventRepository.new.record_organization_quota_create(org_quota, @user_audit_info, message.audit_hash) end org_quota rescue Sequel::ValidationFailed => e diff --git a/app/actions/organization_quotas_update.rb b/app/actions/organization_quotas_update.rb index abbd9e2f77..1ead4b4720 100644 --- a/app/actions/organization_quotas_update.rb +++ b/app/actions/organization_quotas_update.rb @@ -1,3 +1,5 @@ +require 'repositories/organization_quota_event_repository' + module VCAP::CloudController class OrganizationQuotasUpdate class Error < ::StandardError @@ -6,7 +8,7 @@ class Error < ::StandardError MAX_ORGS_TO_LIST_ON_FAILURE = 2 # rubocop:disable Metrics/CyclomaticComplexity - def self.update(quota, message) + def self.update(quota, message, user_audit_info) if log_rate_limit(message) != QuotaDefinition::UNLIMITED orgs = orgs_with_unlimited_processes(quota) unlimited_processes_exist_error!(orgs) if orgs.any? @@ -33,6 +35,8 @@ def self.update(quota, message) quota.total_private_domains = total_private_domains(message) if message.domains_limits_message.requested? :total_domains quota.save + + Repositories::OrganizationQuotaEventRepository.new.record_organization_quota_update(quota, user_audit_info, message.audit_hash) end # rubocop:enable Metrics/CyclomaticComplexity diff --git a/app/actions/space_quota_apply.rb b/app/actions/space_quota_apply.rb index 01f00beba9..55ead4138a 100644 --- a/app/actions/space_quota_apply.rb +++ b/app/actions/space_quota_apply.rb @@ -1,8 +1,14 @@ +require 'repositories/space_quota_event_repository' + module VCAP::CloudController class SpaceQuotaApply class Error < ::StandardError end + def initialize(user_audit_info) + @user_audit_info = user_audit_info + end + def apply(space_quota, message, visible_space_guids: [], all_spaces_visible: false) spaces = valid_spaces(message.space_guids, visible_space_guids, all_spaces_visible, space_quota.organization_id) @@ -17,7 +23,10 @@ def apply(space_quota, message, visible_space_guids: [], all_spaces_visible: fal end SpaceQuotaDefinition.db.transaction do - spaces.each { |space| space_quota.add_space(space) } + spaces.each do |space| + space_quota.add_space(space) + Repositories::SpaceQuotaEventRepository.new.record_space_quota_apply(space_quota, space, @user_audit_info) + end end rescue Sequel::ValidationFailed => e error!(e.message) diff --git a/app/actions/space_quota_delete.rb b/app/actions/space_quota_delete.rb index 7d5d39767e..c26d8ff5c2 100644 --- a/app/actions/space_quota_delete.rb +++ b/app/actions/space_quota_delete.rb @@ -1,8 +1,15 @@ +require 'repositories/space_quota_event_repository' + module VCAP::CloudController class SpaceQuotaDeleteAction + def initialize(user_audit_info) + @user_audit_info = user_audit_info + end + def delete(space_quotas) space_quotas.each do |space_quota| SpaceQuotaDefinition.db.transaction do + Repositories::SpaceQuotaEventRepository.new.record_space_quota_delete(space_quota, @user_audit_info) space_quota.destroy end end diff --git a/app/actions/space_quota_unapply.rb b/app/actions/space_quota_unapply.rb index 9026b1ed9c..22178ac5e3 100644 --- a/app/actions/space_quota_unapply.rb +++ b/app/actions/space_quota_unapply.rb @@ -1,11 +1,18 @@ +require 'repositories/space_quota_event_repository' + module VCAP::CloudController class SpaceQuotaUnapply class Error < ::StandardError end - def self.unapply(space_quota, space) + def initialize(user_audit_info) + @user_audit_info = user_audit_info + end + + def unapply(space_quota, space) SpaceQuotaDefinition.db.transaction do space_quota.remove_space(space) + Repositories::SpaceQuotaEventRepository.new.record_space_quota_remove(space_quota, space, @user_audit_info) end rescue Sequel::ValidationFailed => e raise Error.new(e.message) diff --git a/app/actions/space_quota_update.rb b/app/actions/space_quota_update.rb index ebd0724cc8..c3eb0106d5 100644 --- a/app/actions/space_quota_update.rb +++ b/app/actions/space_quota_update.rb @@ -1,3 +1,5 @@ +require 'repositories/space_quota_event_repository' + module VCAP::CloudController class SpaceQuotaUpdate class Error < ::StandardError @@ -6,7 +8,7 @@ class Error < ::StandardError MAX_SPACES_TO_LIST_ON_FAILURE = 2 # rubocop:disable Metrics/CyclomaticComplexity - def self.update(quota, message) + def self.update(quota, message, user_audit_info) if log_rate_limit(message) != QuotaDefinition::UNLIMITED spaces = spaces_with_unlimited_processes(quota) unlimited_processes_exist_error!(spaces) if spaces.any? @@ -31,6 +33,8 @@ def self.update(quota, message) quota.total_routes = total_routes(message) if message.routes_limits_message.requested? :total_routes quota.save + + Repositories::SpaceQuotaEventRepository.new.record_space_quota_update(quota, user_audit_info, message.audit_hash) end quota diff --git a/app/actions/space_quotas_create.rb b/app/actions/space_quotas_create.rb index b07550bfcf..22b2458afe 100644 --- a/app/actions/space_quotas_create.rb +++ b/app/actions/space_quotas_create.rb @@ -1,8 +1,14 @@ +require 'repositories/space_quota_event_repository' + module VCAP::CloudController class SpaceQuotasCreate class Error < ::StandardError end + def initialize(user_audit_info) + @user_audit_info = user_audit_info + end + # rubocop:todo Metrics/CyclomaticComplexity def create(message, organization:) space_quota = nil @@ -31,6 +37,8 @@ def create(message, organization:) spaces = valid_spaces(message.space_guids, organization) spaces.each { |space| space_quota.add_space(space) } + + Repositories::SpaceQuotaEventRepository.new.record_space_quota_create(space_quota, @user_audit_info, message.audit_hash) end space_quota diff --git a/app/controllers/v3/organization_quotas_controller.rb b/app/controllers/v3/organization_quotas_controller.rb index 59484c3325..2a0e98d671 100644 --- a/app/controllers/v3/organization_quotas_controller.rb +++ b/app/controllers/v3/organization_quotas_controller.rb @@ -44,7 +44,7 @@ def create message = VCAP::CloudController::OrganizationQuotasCreateMessage.new(hashed_params[:body]) unprocessable!(message.errors.full_messages) unless message.valid? - organization_quota = OrganizationQuotasCreate.new.create(message) + organization_quota = OrganizationQuotasCreate.new(user_audit_info).create(message) render json: Presenters::V3::OrganizationQuotaPresenter.new(organization_quota, **presenter_args), status: :created rescue OrganizationQuotasCreate::Error => e @@ -60,7 +60,7 @@ def update organization_quota = QuotaDefinition.first(guid: hashed_params[:guid]) resource_not_found!(:organization_quota) unless organization_quota - organization_quota = OrganizationQuotasUpdate.update(organization_quota, message) + organization_quota = OrganizationQuotasUpdate.update(organization_quota, message, user_audit_info) render json: Presenters::V3::OrganizationQuotaPresenter.new(organization_quota, **presenter_args), status: :ok rescue OrganizationQuotasUpdate::Error => e @@ -77,7 +77,7 @@ def destroy unprocessable!('This quota is applied to one or more organizations. Apply different quotas to those organizations before deleting.') end - delete_action = OrganizationQuotaDeleteAction.new + delete_action = OrganizationQuotaDeleteAction.new(user_audit_info) deletion_job = VCAP::CloudController::Jobs::DeleteActionJob.new(QuotaDefinition, organization_quota.guid, delete_action, 'organization_quota') pollable_job = Jobs::Enqueuer.new(queue: Jobs::Queues.generic).enqueue_pollable(deletion_job) @@ -94,7 +94,7 @@ def apply_to_organizations organization_quota = QuotaDefinition.first(guid: hashed_params[:guid]) resource_not_found!(:organization_quota) unless organization_quota - OrganizationQuotaApply.new.apply(organization_quota, message) + OrganizationQuotaApply.new(user_audit_info).apply(organization_quota, message) render status: :ok, json: Presenters::V3::ToManyRelationshipPresenter.new( "organization_quotas/#{organization_quota.guid}", diff --git a/app/controllers/v3/space_quotas_controller.rb b/app/controllers/v3/space_quotas_controller.rb index cd14f7a846..fcd48c5d2b 100644 --- a/app/controllers/v3/space_quotas_controller.rb +++ b/app/controllers/v3/space_quotas_controller.rb @@ -46,7 +46,7 @@ def create unauthorized! unless permission_queryer.can_write_to_active_org?(org.id) suspended! unless permission_queryer.is_org_active?(org.id) - space_quota = SpaceQuotasCreate.new.create(message, organization: org) + space_quota = SpaceQuotasCreate.new(user_audit_info).create(message, organization: org) render status: :created, json: Presenters::V3::SpaceQuotaPresenter.new( space_quota, @@ -69,7 +69,7 @@ def update message = VCAP::CloudController::OrganizationQuotasUpdateMessage.new(hashed_params[:body]) unprocessable!(message.errors.full_messages) unless message.valid? - space_quota = SpaceQuotaUpdate.update(space_quota, message) + space_quota = SpaceQuotaUpdate.update(space_quota, message, user_audit_info) render status: :ok, json: Presenters::V3::SpaceQuotaPresenter.new( space_quota, @@ -92,7 +92,7 @@ def apply_to_spaces message = SpaceQuotaApplyMessage.new(hashed_params[:body]) unprocessable!(message.errors.full_messages) unless message.valid? - SpaceQuotaApply.new.apply(space_quota, message, **presenter_args) + SpaceQuotaApply.new(user_audit_info).apply(space_quota, message, **presenter_args) render status: :ok, json: Presenters::V3::ToManyRelationshipPresenter.new( "space_quotas/#{space_quota.guid}", @@ -121,7 +121,7 @@ def remove_from_space unprocessable!("Unable to remove quota from space with guid '#{space_guid}'. Ensure the space quota is applied to this space.") end - SpaceQuotaUnapply.unapply(space_quota, space) + SpaceQuotaUnapply.new(user_audit_info).unapply(space_quota, space) rescue SpaceQuotaUnapply::Error => e unprocessable!(e.message) end @@ -138,7 +138,7 @@ def destroy unprocessable!('This quota is applied to one or more spaces. Remove this quota from all spaces before deleting.') unless space_quota.spaces_dataset.empty? - delete_action = SpaceQuotaDeleteAction.new + delete_action = SpaceQuotaDeleteAction.new(user_audit_info) deletion_job = VCAP::CloudController::Jobs::DeleteActionJob.new(SpaceQuotaDefinition, space_quota.guid, delete_action, 'space_quota') pollable_job = Jobs::Enqueuer.new(queue: Jobs::Queues.generic).enqueue_pollable(deletion_job) diff --git a/app/repositories/event_types.rb b/app/repositories/event_types.rb index a71cbe878e..765b8a25c3 100644 --- a/app/repositories/event_types.rb +++ b/app/repositories/event_types.rb @@ -119,10 +119,21 @@ class EventTypesError < StandardError ORGANIZATION_UPDATE = 'audit.organization.update'.freeze, ORGANIZATION_DELETE_REQUEST = 'audit.organization.delete-request'.freeze, + ORGANIZATION_QUOTA_CREATE = 'audit.organization_quota.create'.freeze, + ORGANIZATION_QUOTA_UPDATE = 'audit.organization_quota.update'.freeze, + ORGANIZATION_QUOTA_DELETE = 'audit.organization_quota.delete'.freeze, + ORGANIZATION_QUOTA_APPLY = 'audit.organization_quota.apply'.freeze, + SPACE_CREATE = 'audit.space.create'.freeze, SPACE_UPDATE = 'audit.space.update'.freeze, SPACE_DELETE_REQUEST = 'audit.space.delete-request'.freeze, + SPACE_QUOTA_CREATE = 'audit.space_quota.create'.freeze, + SPACE_QUOTA_UPDATE = 'audit.space_quota.update'.freeze, + SPACE_QUOTA_DELETE = 'audit.space_quota.delete'.freeze, + SPACE_QUOTA_APPLY = 'audit.space_quota.apply'.freeze, + SPACE_QUOTA_REMOVE = 'audit.space_quota.remove'.freeze, + STACK_CREATE = 'audit.stack.create'.freeze, STACK_UPDATE = 'audit.stack.update'.freeze, STACK_DELETE = 'audit.stack.delete'.freeze, diff --git a/app/repositories/organization_quota_event_repository.rb b/app/repositories/organization_quota_event_repository.rb new file mode 100644 index 0000000000..f2145e4686 --- /dev/null +++ b/app/repositories/organization_quota_event_repository.rb @@ -0,0 +1,82 @@ +require 'repositories/event_types' + +module VCAP::CloudController + module Repositories + class OrganizationQuotaEventRepository + def record_organization_quota_create(quota, user_audit_info, request_attrs) + Event.create( + type: EventTypes::ORGANIZATION_QUOTA_CREATE, + actee: quota.guid, + actee_type: 'organization_quota', + actee_name: quota.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + timestamp: Sequel::CURRENT_TIMESTAMP, + space_guid: '', + organization_guid: '', + metadata: { + request: request_attrs + } + ) + end + + def record_organization_quota_update(quota, user_audit_info, request_attrs) + Event.create( + type: EventTypes::ORGANIZATION_QUOTA_UPDATE, + actee: quota.guid, + actee_type: 'organization_quota', + actee_name: quota.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + timestamp: Sequel::CURRENT_TIMESTAMP, + space_guid: '', + organization_guid: '', + metadata: { + request: request_attrs + } + ) + end + + def record_organization_quota_delete(quota, user_audit_info) + Event.create( + type: EventTypes::ORGANIZATION_QUOTA_DELETE, + actee: quota.guid, + actee_type: 'organization_quota', + actee_name: quota.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + timestamp: Sequel::CURRENT_TIMESTAMP, + space_guid: '', + organization_guid: '', + metadata: {} + ) + end + + def record_organization_quota_apply(quota, organization, user_audit_info) + Event.create( + type: EventTypes::ORGANIZATION_QUOTA_APPLY, + actee: quota.guid, + actee_type: 'organization_quota', + actee_name: quota.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + timestamp: Sequel::CURRENT_TIMESTAMP, + space_guid: '', + organization_guid: organization.guid, + metadata: { + organization_guid: organization.guid, + organization_name: organization.name + } + ) + end + end + end +end diff --git a/app/repositories/space_quota_event_repository.rb b/app/repositories/space_quota_event_repository.rb new file mode 100644 index 0000000000..8c27593468 --- /dev/null +++ b/app/repositories/space_quota_event_repository.rb @@ -0,0 +1,102 @@ +require 'repositories/event_types' + +module VCAP::CloudController + module Repositories + class SpaceQuotaEventRepository + def record_space_quota_create(quota, user_audit_info, request_attrs) + Event.create( + type: EventTypes::SPACE_QUOTA_CREATE, + actee: quota.guid, + actee_type: 'space_quota', + actee_name: quota.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + timestamp: Sequel::CURRENT_TIMESTAMP, + space_guid: '', + organization_guid: quota.organization.guid, + metadata: { + request: request_attrs + } + ) + end + + def record_space_quota_update(quota, user_audit_info, request_attrs) + Event.create( + type: EventTypes::SPACE_QUOTA_UPDATE, + actee: quota.guid, + actee_type: 'space_quota', + actee_name: quota.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + timestamp: Sequel::CURRENT_TIMESTAMP, + space_guid: '', + organization_guid: quota.organization.guid, + metadata: { + request: request_attrs + } + ) + end + + def record_space_quota_delete(quota, user_audit_info) + Event.create( + type: EventTypes::SPACE_QUOTA_DELETE, + actee: quota.guid, + actee_type: 'space_quota', + actee_name: quota.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + timestamp: Sequel::CURRENT_TIMESTAMP, + space_guid: '', + organization_guid: quota.organization.guid, + metadata: {} + ) + end + + def record_space_quota_apply(quota, space, user_audit_info) + Event.create( + type: EventTypes::SPACE_QUOTA_APPLY, + actee: quota.guid, + actee_type: 'space_quota', + actee_name: quota.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + timestamp: Sequel::CURRENT_TIMESTAMP, + space_guid: space.guid, + organization_guid: quota.organization.guid, + metadata: { + space_guid: space.guid, + space_name: space.name + } + ) + end + + def record_space_quota_remove(quota, space, user_audit_info) + Event.create( + type: EventTypes::SPACE_QUOTA_REMOVE, + actee: quota.guid, + actee_type: 'space_quota', + actee_name: quota.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + timestamp: Sequel::CURRENT_TIMESTAMP, + space_guid: space.guid, + organization_guid: quota.organization.guid, + metadata: { + space_guid: space.guid, + space_name: space.name + } + ) + end + end + end +end diff --git a/docs/v3/source/includes/resources/audit_events/_header.md.erb b/docs/v3/source/includes/resources/audit_events/_header.md.erb index c6671ec423..d9326fd31b 100644 --- a/docs/v3/source/includes/resources/audit_events/_header.md.erb +++ b/docs/v3/source/includes/resources/audit_events/_header.md.erb @@ -55,6 +55,12 @@ For more information, see the [Cloud Foundry docs](https://docs.cloudfoundry.org - `audit.organization.delete-request` - `audit.organization.update` +##### Organization_quota lifecycle +- `audit.organization_quota.apply` +- `audit.organization_quota.create` +- `audit.organization_quota.delete` +- `audit.organization_quota.update` + ##### Route lifecycle - `audit.route.create` - `audit.route.delete-request` @@ -129,6 +135,13 @@ For more information, see the [Cloud Foundry docs](https://docs.cloudfoundry.org - `audit.space.delete-request` - `audit.space.update` +##### Space_quota lifecycle +- `audit.space_quota.apply` +- `audit.space_quota.create` +- `audit.space_quota.delete` +- `audit.space_quota.remove` +- `audit.space_quota.update` + ##### Stack lifecycle - `audit.stack.create` - `audit.stack.delete` diff --git a/spec/unit/actions/organization_quota_apply_spec.rb b/spec/unit/actions/organization_quota_apply_spec.rb index 5d0a2196cc..f3f81c9e5c 100644 --- a/spec/unit/actions/organization_quota_apply_spec.rb +++ b/spec/unit/actions/organization_quota_apply_spec.rb @@ -5,7 +5,12 @@ module VCAP::CloudController RSpec.describe OrganizationQuotaApply do describe '#apply' do - subject { OrganizationQuotaApply.new } + let(:user) { User.make } + let(:user_email) { 'user@example.com' } + let(:user_name) { 'user-name' } + let(:user_audit_info) { UserAuditInfo.new(user_guid: user.guid, user_email: user_email, user_name: user_name) } + + subject { OrganizationQuotaApply.new(user_audit_info) } let(:org) { VCAP::CloudController::Organization.make } let(:org_quota) { VCAP::CloudController::QuotaDefinition.make } @@ -24,6 +29,31 @@ module VCAP::CloudController expect(org_quota.organizations.count).to eq(1) expect(org_quota.organizations[0].guid).to eq(org.guid) end + + it 'creates an audit event' do + subject.apply(org_quota, message) + + expect(VCAP::CloudController::Event.count).to eq(1) + event = VCAP::CloudController::Event.last + + expect(event.values).to include( + type: 'audit.organization_quota.apply', + actee: org_quota.guid, + actee_type: 'organization_quota', + actee_name: org_quota.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + space_guid: '', + organization_guid: org.guid + ) + expect(event.metadata).to eq({ + 'organization_guid' => org.guid, + 'organization_name' => org.name + }) + expect(event.timestamp).to be + end end context 'when a model validation fails' do @@ -54,6 +84,45 @@ module VCAP::CloudController end end + context 'when applying quota to multiple orgs' do + let(:org2) { VCAP::CloudController::Organization.make } + let(:message) do + VCAP::CloudController::OrganizationQuotaApplyMessage.new({ + data: [{ guid: org.guid }, { guid: org2.guid }] + }) + end + + it 'creates an audit event for each org' do + subject.apply(org_quota, message) + + expect(VCAP::CloudController::Event.count).to eq(2) + events = VCAP::CloudController::Event.all + + org_event = events.find { |e| e.organization_guid == org.guid } + org2_event = events.find { |e| e.organization_guid == org2.guid } + + expect(org_event.values).to include( + type: 'audit.organization_quota.apply', + actee: org_quota.guid, + organization_guid: org.guid + ) + expect(org_event.metadata).to eq({ + 'organization_guid' => org.guid, + 'organization_name' => org.name + }) + + expect(org2_event.values).to include( + type: 'audit.organization_quota.apply', + actee: org_quota.guid, + organization_guid: org2.guid + ) + expect(org2_event.metadata).to eq({ + 'organization_guid' => org2.guid, + 'organization_name' => org2.name + }) + end + end + context 'when trying to set a log rate limit and there are apps with unlimited log rates' do let(:space) { VCAP::CloudController::Space.make(guid: 'space-guid', organization: org) } let(:app_model) { VCAP::CloudController::AppModel.make(name: 'name1', space: space) } diff --git a/spec/unit/actions/organization_quota_delete_spec.rb b/spec/unit/actions/organization_quota_delete_spec.rb index cbc70a6da7..8eee896839 100644 --- a/spec/unit/actions/organization_quota_delete_spec.rb +++ b/spec/unit/actions/organization_quota_delete_spec.rb @@ -3,7 +3,12 @@ module VCAP::CloudController RSpec.describe OrganizationQuotaDeleteAction do - subject(:org_quota_delete) { OrganizationQuotaDeleteAction.new } + let(:user) { User.make } + let(:user_email) { 'user@example.com' } + let(:user_name) { 'user-name' } + let(:user_audit_info) { UserAuditInfo.new(user_guid: user.guid, user_email: user_email, user_name: user_name) } + + subject(:org_quota_delete) { OrganizationQuotaDeleteAction.new(user_audit_info) } describe '#delete' do let!(:quota) { QuotaDefinition.make } @@ -15,6 +20,31 @@ module VCAP::CloudController expect { quota.refresh }.to raise_error Sequel::Error, 'Record not found' end + + it 'creates an audit event' do + quota_guid = quota.guid + quota_name = quota.name + + org_quota_delete.delete([quota]) + + expect(VCAP::CloudController::Event.count).to eq(1) + event = VCAP::CloudController::Event.last + + expect(event.values).to include( + type: 'audit.organization_quota.delete', + actee: quota_guid, + actee_type: 'organization_quota', + actee_name: quota_name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + space_guid: '', + organization_guid: '' + ) + expect(event.metadata).to eq({}) + expect(event.timestamp).to be + end end end end diff --git a/spec/unit/actions/organization_quotas_create_spec.rb b/spec/unit/actions/organization_quotas_create_spec.rb index c6817e57c2..eab1fae1cd 100644 --- a/spec/unit/actions/organization_quotas_create_spec.rb +++ b/spec/unit/actions/organization_quotas_create_spec.rb @@ -5,7 +5,12 @@ module VCAP::CloudController RSpec.describe OrganizationQuotasCreate do describe 'create' do - subject(:org_quotas_create) { OrganizationQuotasCreate.new } + let(:user) { User.make } + let(:user_email) { 'user@example.com' } + let(:user_name) { 'user-name' } + let(:user_audit_info) { UserAuditInfo.new(user_guid: user.guid, user_email: user_email, user_name: user_name) } + + subject(:org_quotas_create) { OrganizationQuotasCreate.new(user_audit_info) } context 'when creating a organization quota' do let(:org) { VCAP::CloudController::Organization.make } @@ -72,6 +77,28 @@ module VCAP::CloudController expect(organization_quota.organizations.count).to eq(0) end + it 'creates an audit event' do + organization_quota = org_quotas_create.create(message) + + expect(VCAP::CloudController::Event.count).to eq(1) + event = VCAP::CloudController::Event.last + + expect(event.values).to include( + type: 'audit.organization_quota.create', + actee: organization_quota.guid, + actee_type: 'organization_quota', + actee_name: organization_quota.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + space_guid: '', + organization_guid: '' + ) + expect(event.metadata).to eq({ 'request' => message.audit_hash }) + expect(event.timestamp).to be + end + it 'provides defaults if the parameters are not provided' do organization_quota = org_quotas_create.create(minimum_message) diff --git a/spec/unit/actions/organization_quotas_update_spec.rb b/spec/unit/actions/organization_quotas_update_spec.rb index b03447de0f..8f54426ac2 100644 --- a/spec/unit/actions/organization_quotas_update_spec.rb +++ b/spec/unit/actions/organization_quotas_update_spec.rb @@ -5,6 +5,11 @@ module VCAP::CloudController RSpec.describe OrganizationQuotasUpdate do describe 'update' do + let(:user) { User.make } + let(:user_email) { 'user@example.com' } + let(:user_name) { 'user-name' } + let(:user_audit_info) { UserAuditInfo.new(user_guid: user.guid, user_email: user_email, user_name: user_name) } + context 'when updating an organization quota' do let!(:org_quota) { VCAP::CloudController::QuotaDefinition.make(name: 'org_quota_name', non_basic_services_allowed: true) } @@ -42,7 +47,7 @@ module VCAP::CloudController end it 'updates an organization quota with the given values' do - updated_organization_quota = OrganizationQuotasUpdate.update(org_quota, message) + updated_organization_quota = OrganizationQuotasUpdate.update(org_quota, message, user_audit_info) expect(updated_organization_quota.name).to eq('don-quixote') @@ -63,12 +68,34 @@ module VCAP::CloudController end it 'updates an organization quota with only the given values' do - updated_organization_quota = OrganizationQuotasUpdate.update(org_quota, minimum_message) + updated_organization_quota = OrganizationQuotasUpdate.update(org_quota, minimum_message, user_audit_info) expect(updated_organization_quota.name).to eq('org_quota_name') expect(updated_organization_quota.log_rate_limit).to eq(-1) end + it 'creates an audit event' do + OrganizationQuotasUpdate.update(org_quota, message, user_audit_info) + + expect(VCAP::CloudController::Event.count).to eq(1) + event = VCAP::CloudController::Event.last + + expect(event.values).to include( + type: 'audit.organization_quota.update', + actee: org_quota.guid, + actee_type: 'organization_quota', + actee_name: 'don-quixote', + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + space_guid: '', + organization_guid: '' + ) + expect(event.metadata).to eq({ 'request' => message.audit_hash }) + expect(event.timestamp).to be + end + context 'when a model validation fails' do it 'raises an error' do errors = Sequel::Model::Errors.new @@ -78,7 +105,7 @@ module VCAP::CloudController message = VCAP::CloudController::OrganizationQuotasCreateMessage.new(name: 'foobar') expect do - OrganizationQuotasUpdate.update(org_quota, message) + OrganizationQuotasUpdate.update(org_quota, message, user_audit_info) end.to raise_error(OrganizationQuotasUpdate::Error, 'blork is busted') end @@ -90,7 +117,7 @@ module VCAP::CloudController let(:create_message) { VCAP::CloudController::OrganizationQuotasCreateMessage.new(name:) } - let(:org_quotas_create) { OrganizationQuotasCreate.new } + let(:org_quotas_create) { OrganizationQuotasCreate.new(user_audit_info) } before do org_quotas_create.create(create_message) @@ -98,7 +125,7 @@ module VCAP::CloudController it 'raises a human-friendly error' do expect do - OrganizationQuotasUpdate.update(org_quota, update_message) + OrganizationQuotasUpdate.update(org_quota, update_message, user_audit_info) end.to raise_error(OrganizationQuotasUpdate::Error, "Organization Quota '#{name}' already exists.") end end @@ -121,7 +148,7 @@ def create_orgs_with_unlimited_log_rate_process(count) it 'errors with a message telling the user the affected org' do expect do - OrganizationQuotasUpdate.update(org_quota, message) + OrganizationQuotasUpdate.update(org_quota, message, user_audit_info) end.to raise_error(OrganizationQuotasUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to org ' \ "'org-name-1' which contains apps running with an unlimited log rate limit.") end @@ -134,7 +161,7 @@ def create_orgs_with_unlimited_log_rate_process(count) it 'errors with a message telling the user the affected orgs' do expect do - OrganizationQuotasUpdate.update(org_quota, message) + OrganizationQuotasUpdate.update(org_quota, message, user_audit_info) end.to raise_error(OrganizationQuotasUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to orgs ' \ "'org-name-1', 'org-name-2' which contain apps running with an unlimited log rate limit.") end @@ -147,7 +174,7 @@ def create_orgs_with_unlimited_log_rate_process(count) it 'errors with a message telling the user some of the affected orgs and a total count' do expect do - OrganizationQuotasUpdate.update(org_quota, message) + OrganizationQuotasUpdate.update(org_quota, message, user_audit_info) end.to raise_error(OrganizationQuotasUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to orgs ' \ "'org-name-1', 'org-name-2' and 3 other orgs which contain apps running with an unlimited log rate limit.") end @@ -162,7 +189,7 @@ def create_orgs_with_unlimited_log_rate_process(count) it 'only names the org once in the error message' do expect do - OrganizationQuotasUpdate.update(org_quota, message) + OrganizationQuotasUpdate.update(org_quota, message, user_audit_info) end.to raise_error(OrganizationQuotasUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to org ' \ "'org-name' which contains apps running with an unlimited log rate limit.") end diff --git a/spec/unit/actions/space_quota_apply_spec.rb b/spec/unit/actions/space_quota_apply_spec.rb index e76fba3402..83c795d034 100644 --- a/spec/unit/actions/space_quota_apply_spec.rb +++ b/spec/unit/actions/space_quota_apply_spec.rb @@ -8,7 +8,12 @@ module VCAP::CloudController let(:all_spaces_visible) { false } describe '#apply' do - subject { SpaceQuotaApply.new } + let(:user) { User.make } + let(:user_email) { 'user@example.com' } + let(:user_name) { 'user-name' } + let(:user_audit_info) { UserAuditInfo.new(user_guid: user.guid, user_email: user_email, user_name: user_name) } + + subject { SpaceQuotaApply.new(user_audit_info) } let(:org) { VCAP::CloudController::Organization.make } let(:space) { VCAP::CloudController::Space.make(organization: org) } @@ -31,6 +36,31 @@ module VCAP::CloudController expect(space_quota.spaces.count).to eq(1) expect(space_quota.spaces[0].guid).to eq(space.guid) end + + it 'creates an audit event' do + subject.apply(space_quota, message, visible_space_guids:, all_spaces_visible:) + + expect(VCAP::CloudController::Event.count).to eq(1) + event = VCAP::CloudController::Event.last + + expect(event.values).to include( + type: 'audit.space_quota.apply', + actee: space_quota.guid, + actee_type: 'space_quota', + actee_name: space_quota.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + space_guid: space.guid, + organization_guid: space_quota.organization.guid + ) + expect(event.metadata).to eq({ + 'space_guid' => space.guid, + 'space_name' => space.name + }) + expect(event.timestamp).to be + end end context 'when a model validation fails' do @@ -107,6 +137,46 @@ module VCAP::CloudController end end + context 'when applying quota to multiple spaces' do + let(:space2) { VCAP::CloudController::Space.make(organization: org) } + let(:visible_space_guids) { [space.guid, space2.guid] } + let(:message) do + VCAP::CloudController::SpaceQuotaApplyMessage.new({ + data: [{ guid: space.guid }, { guid: space2.guid }] + }) + end + + it 'creates an audit event for each space' do + subject.apply(space_quota, message, visible_space_guids:, all_spaces_visible:) + + expect(VCAP::CloudController::Event.count).to eq(2) + events = VCAP::CloudController::Event.all + + space_event = events.find { |e| e.space_guid == space.guid } + space2_event = events.find { |e| e.space_guid == space2.guid } + + expect(space_event.values).to include( + type: 'audit.space_quota.apply', + actee: space_quota.guid, + space_guid: space.guid + ) + expect(space_event.metadata).to eq({ + 'space_guid' => space.guid, + 'space_name' => space.name + }) + + expect(space2_event.values).to include( + type: 'audit.space_quota.apply', + actee: space_quota.guid, + space_guid: space2.guid + ) + expect(space2_event.metadata).to eq({ + 'space_guid' => space2.guid, + 'space_name' => space2.name + }) + end + end + context 'when user is admin' do let(:all_spaces_visible) { true } diff --git a/spec/unit/actions/space_quota_delete_spec.rb b/spec/unit/actions/space_quota_delete_spec.rb index 5199e45206..56784a35ac 100644 --- a/spec/unit/actions/space_quota_delete_spec.rb +++ b/spec/unit/actions/space_quota_delete_spec.rb @@ -3,10 +3,16 @@ module VCAP::CloudController RSpec.describe SpaceQuotaDeleteAction do - subject(:space_quota_delete) { SpaceQuotaDeleteAction.new } + let(:user) { User.make } + let(:user_email) { 'user@example.com' } + let(:user_name) { 'user-name' } + let(:user_audit_info) { UserAuditInfo.new(user_guid: user.guid, user_email: user_email, user_name: user_name) } + + subject(:space_quota_delete) { SpaceQuotaDeleteAction.new(user_audit_info) } describe '#delete' do - let!(:quota) { SpaceQuotaDefinition.make } + let(:org) { Organization.make } + let!(:quota) { SpaceQuotaDefinition.make(organization: org) } it 'deletes the space quota' do expect do @@ -15,6 +21,31 @@ module VCAP::CloudController expect { quota.refresh }.to raise_error Sequel::Error, 'Record not found' end + + it 'creates an audit event' do + quota_guid = quota.guid + quota_name = quota.name + + space_quota_delete.delete([quota]) + + expect(VCAP::CloudController::Event.count).to eq(1) + event = VCAP::CloudController::Event.last + + expect(event.values).to include( + type: 'audit.space_quota.delete', + actee: quota_guid, + actee_type: 'space_quota', + actee_name: quota_name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + space_guid: '', + organization_guid: org.guid + ) + expect(event.metadata).to eq({}) + expect(event.timestamp).to be + end end end end diff --git a/spec/unit/actions/space_quota_unapply_spec.rb b/spec/unit/actions/space_quota_unapply_spec.rb index 08cad3154d..92985aa8dd 100644 --- a/spec/unit/actions/space_quota_unapply_spec.rb +++ b/spec/unit/actions/space_quota_unapply_spec.rb @@ -4,7 +4,12 @@ module VCAP::CloudController RSpec.describe SpaceQuotaUnapply do describe '#unapply' do - subject { SpaceQuotaUnapply } + let(:user) { User.make } + let(:user_email) { 'user@example.com' } + let(:user_name) { 'user-name' } + let(:user_audit_info) { UserAuditInfo.new(user_guid: user.guid, user_email: user_email, user_name: user_name) } + + subject { SpaceQuotaUnapply.new(user_audit_info) } let(:org) { VCAP::CloudController::Organization.make } let(:space_quota) { VCAP::CloudController::SpaceQuotaDefinition.make(organization: org) } @@ -19,6 +24,31 @@ module VCAP::CloudController expect(space_quota.spaces.count).to eq(0) end + + it 'creates an audit event' do + subject.unapply(space_quota, space) + + expect(VCAP::CloudController::Event.count).to eq(1) + event = VCAP::CloudController::Event.last + + expect(event.values).to include( + type: 'audit.space_quota.remove', + actee: space_quota.guid, + actee_type: 'space_quota', + actee_name: space_quota.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + space_guid: space.guid, + organization_guid: space_quota.organization.guid + ) + expect(event.metadata).to eq({ + 'space_guid' => space.guid, + 'space_name' => space.name + }) + expect(event.timestamp).to be + end end context 'when a model validation fails' do diff --git a/spec/unit/actions/space_quota_update_spec.rb b/spec/unit/actions/space_quota_update_spec.rb index 71dd57fff1..7721b0fae1 100644 --- a/spec/unit/actions/space_quota_update_spec.rb +++ b/spec/unit/actions/space_quota_update_spec.rb @@ -5,6 +5,10 @@ module VCAP::CloudController RSpec.describe SpaceQuotaUpdate do let(:org) { VCAP::CloudController::Organization.make } + let(:user) { User.make } + let(:user_email) { 'user@example.com' } + let(:user_name) { 'user-name' } + let(:user_audit_info) { UserAuditInfo.new(user_guid: user.guid, user_email: user_email, user_name: user_name) } describe 'update' do context 'when updating a space quota' do @@ -43,7 +47,7 @@ module VCAP::CloudController end it 'updates a space quota with the given values' do - updated_space_quota = SpaceQuotaUpdate.update(space_quota, message) + updated_space_quota = SpaceQuotaUpdate.update(space_quota, message, user_audit_info) expect(updated_space_quota.name).to eq('don-quixote') @@ -62,12 +66,34 @@ module VCAP::CloudController end it 'updates a space quota with only the given values' do - updated_space_quota = SpaceQuotaUpdate.update(space_quota, minimum_message) + updated_space_quota = SpaceQuotaUpdate.update(space_quota, minimum_message, user_audit_info) expect(updated_space_quota.name).to eq('space_quota_name') expect(updated_space_quota.log_rate_limit).to eq(-1) end + it 'creates an audit event' do + SpaceQuotaUpdate.update(space_quota, message, user_audit_info) + + expect(VCAP::CloudController::Event.count).to eq(1) + event = VCAP::CloudController::Event.last + + expect(event.values).to include( + type: 'audit.space_quota.update', + actee: space_quota.guid, + actee_type: 'space_quota', + actee_name: 'don-quixote', + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + space_guid: '', + organization_guid: org.guid + ) + expect(event.metadata).to eq({ 'request' => message.audit_hash }) + expect(event.timestamp).to be + end + context 'when a model validation fails' do it 'raises an error' do errors = Sequel::Model::Errors.new @@ -76,7 +102,7 @@ module VCAP::CloudController message = VCAP::CloudController::SpaceQuotaUpdateMessage.new(name: 'foobar') expect do - SpaceQuotaUpdate.update(space_quota, message) + SpaceQuotaUpdate.update(space_quota, message, user_audit_info) end.to raise_error(SpaceQuotaUpdate::Error, 'blork is busted') end @@ -87,7 +113,7 @@ module VCAP::CloudController it 'raises a human-friendly error' do expect do - SpaceQuotaUpdate.update(space_quota, update_message) + SpaceQuotaUpdate.update(space_quota, update_message, user_audit_info) end.to raise_error(SpaceQuotaUpdate::Error, "Space Quota '#{name}' already exists.") end end @@ -109,7 +135,7 @@ def create_spaces_with_unlimited_log_rate_process(count) it 'errors with a message telling the user the affected space' do expect do - SpaceQuotaUpdate.update(space_quota, message) + SpaceQuotaUpdate.update(space_quota, message, user_audit_info) end.to raise_error(SpaceQuotaUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to space ' \ "'space-name-1' which contains apps running with an unlimited log rate limit.") end @@ -122,7 +148,7 @@ def create_spaces_with_unlimited_log_rate_process(count) it 'errors with a message telling the user the affected spaces' do expect do - SpaceQuotaUpdate.update(space_quota, message) + SpaceQuotaUpdate.update(space_quota, message, user_audit_info) end.to raise_error(SpaceQuotaUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to spaces ' \ "'space-name-1', 'space-name-2' which contain apps running with an unlimited log rate limit.") end @@ -135,7 +161,7 @@ def create_spaces_with_unlimited_log_rate_process(count) it 'errors with a message telling the user some of the affected spaces and a total count' do expect do - SpaceQuotaUpdate.update(space_quota, message) + SpaceQuotaUpdate.update(space_quota, message, user_audit_info) end.to raise_error(SpaceQuotaUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to spaces ' \ "'space-name-1', 'space-name-2' and 3 other spaces which contain apps running with an unlimited log rate limit.") end @@ -150,7 +176,7 @@ def create_spaces_with_unlimited_log_rate_process(count) it 'only names the space once in the error message' do expect do - SpaceQuotaUpdate.update(space_quota, message) + SpaceQuotaUpdate.update(space_quota, message, user_audit_info) end.to raise_error(SpaceQuotaUpdate::Error, 'Current usage exceeds new quota values. This quota is applied to space ' \ "'space-name' which contains apps running with an unlimited log rate limit.") end diff --git a/spec/unit/actions/space_quotas_create_spec.rb b/spec/unit/actions/space_quotas_create_spec.rb index 80b8fa1837..e3573fb9a0 100644 --- a/spec/unit/actions/space_quotas_create_spec.rb +++ b/spec/unit/actions/space_quotas_create_spec.rb @@ -5,7 +5,12 @@ module VCAP::CloudController RSpec.describe SpaceQuotasCreate do describe 'create' do - subject(:space_quotas_create) { SpaceQuotasCreate.new } + let(:user) { User.make } + let(:user_email) { 'user@example.com' } + let(:user_name) { 'user-name' } + let(:user_audit_info) { UserAuditInfo.new(user_guid: user.guid, user_email: user_email, user_name: user_name) } + + subject(:space_quotas_create) { SpaceQuotasCreate.new(user_audit_info) } let(:org) { VCAP::CloudController::Organization.make(guid: 'some-org') } let(:space) { VCAP::CloudController::Space.make(guid: 'some-space', organization: org) } @@ -105,6 +110,28 @@ module VCAP::CloudController expect(space_quota.spaces.count).to eq(1) end + + it 'creates an audit event' do + space_quota = space_quotas_create.create(message_with_params, organization: org) + + expect(VCAP::CloudController::Event.count).to eq(1) + event = VCAP::CloudController::Event.last + + expect(event.values).to include( + type: 'audit.space_quota.create', + actee: space_quota.guid, + actee_type: 'space_quota', + actee_name: space_quota.name, + actor: user_audit_info.user_guid, + actor_type: 'user', + actor_name: user_audit_info.user_email, + actor_username: user_audit_info.user_name, + space_guid: '', + organization_guid: org.guid + ) + expect(event.metadata).to eq({ 'request' => message_with_params.audit_hash }) + expect(event.timestamp).to be + end end end diff --git a/spec/unit/repositories/event_types_spec.rb b/spec/unit/repositories/event_types_spec.rb index 6c6b74bb5c..6682855c69 100644 --- a/spec/unit/repositories/event_types_spec.rb +++ b/spec/unit/repositories/event_types_spec.rb @@ -118,9 +118,18 @@ module Repositories 'audit.organization.create', 'audit.organization.update', 'audit.organization.delete-request', + 'audit.organization_quota.create', + 'audit.organization_quota.update', + 'audit.organization_quota.delete', + 'audit.organization_quota.apply', 'audit.space.create', 'audit.space.update', 'audit.space.delete-request', + 'audit.space_quota.create', + 'audit.space_quota.update', + 'audit.space_quota.delete', + 'audit.space_quota.apply', + 'audit.space_quota.remove', 'audit.stack.create', 'audit.stack.update', 'audit.stack.delete',