Commit 38fef977 authored by Alex Kalderimis's avatar Alex Kalderimis

Merge branch '347108-event-streaming-within-transaction' into 'master'

Move event streaming in to after commit queue

See merge request gitlab-org/gitlab!76765
parents f2d560de ab04d08e
# frozen_string_literal: true # frozen_string_literal: true
class AuditEvent < ApplicationRecord class AuditEvent < ApplicationRecord
include AfterCommitQueue
include CreatedAtFilterable include CreatedAtFilterable
include BulkInsertSafe include BulkInsertSafe
include EachBatch include EachBatch
......
...@@ -141,7 +141,7 @@ class AuditEventService ...@@ -141,7 +141,7 @@ class AuditEventService
event.save! if should_save_database?(@save_type) event.save! if should_save_database?(@save_type)
stream_event_to_external_destinations(event) if should_save_stream?(@save_type) stream_event_to_external_destinations(event) if should_save_stream?(@save_type)
rescue StandardError => e rescue StandardError => e
Gitlab::ErrorTracking.track_exception(e, audit_event_type: event.class.to_s) Gitlab::ErrorTracking.track_and_raise_for_dev_exception(e, audit_event_type: event.class.to_s)
end end
end end
......
...@@ -238,7 +238,9 @@ module EE ...@@ -238,7 +238,9 @@ module EE
def stream_event_to_external_destinations(event) def stream_event_to_external_destinations(event)
return if event.is_a?(AuthenticationEvent) return if event.is_a?(AuthenticationEvent)
event.stream_to_external_destinations if event.entity_is_group_or_project? if event.entity_is_group_or_project?
event.run_after_commit_or_now { event.stream_to_external_destinations }
end
end end
override :base_payload override :base_payload
......
...@@ -157,8 +157,7 @@ RSpec.describe AuditEventService, :request_store do ...@@ -157,8 +157,7 @@ RSpec.describe AuditEventService, :request_store do
allow(user).to receive_messages(current_sign_in_ip: 'invalid IP') allow(user).to receive_messages(current_sign_in_ip: 'invalid IP')
expect(Gitlab::ErrorTracking).to( expect(Gitlab::ErrorTracking).to(
receive(:track_exception) receive(:track_and_raise_for_dev_exception)
.with(ActiveRecord::RecordInvalid, audit_event_type: 'AuditEvent').and_call_original
) )
service.security_event service.security_event
...@@ -365,8 +364,7 @@ RSpec.describe AuditEventService, :request_store do ...@@ -365,8 +364,7 @@ RSpec.describe AuditEventService, :request_store do
end end
describe '#for_user' do describe '#for_user' do
let(:author_name) { 'Administrator' } let(:current_user) { create(:user) }
let(:current_user) { User.new(name: author_name) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:custom_message) { 'Some strange event has occurred' } let(:custom_message) { 'Some strange event has occurred' }
let(:options) { { action: action, custom_message: custom_message } } let(:options) { { action: action, custom_message: custom_message } }
...@@ -384,7 +382,7 @@ RSpec.describe AuditEventService, :request_store do ...@@ -384,7 +382,7 @@ RSpec.describe AuditEventService, :request_store do
it 'sets the details attribute' do it 'sets the details attribute' do
expect(service.instance_variable_get(:@details)).to eq( expect(service.instance_variable_get(:@details)).to eq(
remove: 'user', remove: 'user',
author_name: author_name, author_name: current_user.name,
target_id: user.id, target_id: user.id,
target_type: 'User', target_type: 'User',
target_details: user.username target_details: user.username
...@@ -402,7 +400,7 @@ RSpec.describe AuditEventService, :request_store do ...@@ -402,7 +400,7 @@ RSpec.describe AuditEventService, :request_store do
it 'sets the details attribute' do it 'sets the details attribute' do
expect(service.instance_variable_get(:@details)).to eq( expect(service.instance_variable_get(:@details)).to eq(
add: 'user', add: 'user',
author_name: author_name, author_name: current_user.name,
target_id: user.id, target_id: user.id,
target_type: 'User', target_type: 'User',
target_details: user.full_path target_details: user.full_path
...@@ -420,7 +418,7 @@ RSpec.describe AuditEventService, :request_store do ...@@ -420,7 +418,7 @@ RSpec.describe AuditEventService, :request_store do
it 'sets the details attribute' do it 'sets the details attribute' do
expect(service.instance_variable_get(:@details)).to eq( expect(service.instance_variable_get(:@details)).to eq(
custom_message: custom_message, custom_message: custom_message,
author_name: author_name, author_name: current_user.name,
target_id: user.id, target_id: user.id,
target_type: 'User', target_type: 'User',
target_details: user.full_path target_details: user.full_path
...@@ -653,6 +651,7 @@ RSpec.describe AuditEventService, :request_store do ...@@ -653,6 +651,7 @@ RSpec.describe AuditEventService, :request_store do
before do before do
stub_licensed_features(external_audit_events: true) stub_licensed_features(external_audit_events: true)
stub_feature_flags(ff_external_audit_events_namespace: group.root_ancestor)
end end
subject(:event) { described_class.new(user, project, details, save_type).for_project.security_event } subject(:event) { described_class.new(user, project, details, save_type).for_project.security_event }
...@@ -660,10 +659,27 @@ RSpec.describe AuditEventService, :request_store do ...@@ -660,10 +659,27 @@ RSpec.describe AuditEventService, :request_store do
context 'with save_type of :database_and_stream' do context 'with save_type of :database_and_stream' do
let(:save_type) { :database_and_stream } let(:save_type) { :database_and_stream }
it 'save to database and stream' do it 'saves to database' do
expect(AuditEvents::AuditEventStreamingWorker).to receive(:perform_async).once
expect { event }.to change(AuditEvent, :count).by(1) expect { event }.to change(AuditEvent, :count).by(1)
end end
it 'streams the event' do
expect_next_instance_of(described_class) do |service|
expect(service).to receive(:stream_event_to_external_destinations).once
end
event
end
context 'when the event is created within a transaction' do
it 'does not raise an error about a job being enqueued from within a transaction' do
RSpec::Expectations.configuration.on_potential_false_positives = :nothing
ApplicationRecord.transaction do
expect { event }.not_to raise_error(Sidekiq::Worker::EnqueueFromTransactionError)
end
end
end
end end
context 'with save_type of :database' do context 'with save_type of :database' do
...@@ -678,9 +694,16 @@ RSpec.describe AuditEventService, :request_store do ...@@ -678,9 +694,16 @@ RSpec.describe AuditEventService, :request_store do
context 'with save_type of :stream' do context 'with save_type of :stream' do
let(:save_type) { :stream } let(:save_type) { :stream }
it 'saves to database and stream' do it 'does not save to database' do
expect(AuditEvents::AuditEventStreamingWorker).to receive(:perform_async).once expect { event }.not_to change(AuditEvent, :count)
expect { event }.to change(AuditEvent, :count).by(0) end
it 'streams the event' do
expect_next_instance_of(described_class) do |service|
expect(service).to receive(:stream_event_to_external_destinations).once
end
event
end end
end end
end end
......
...@@ -60,17 +60,18 @@ RSpec.describe AuditEventService do ...@@ -60,17 +60,18 @@ RSpec.describe AuditEventService do
ip_address: user.current_sign_in_ip, ip_address: user.current_sign_in_ip,
result: AuthenticationEvent.results[:success], result: AuthenticationEvent.results[:success],
provider: 'standard' provider: 'standard'
) ).and_call_original
audit_service.for_authentication.security_event audit_service.for_authentication.security_event
end end
it 'tracks exceptions when the event cannot be created' do it 'tracks exceptions when the event cannot be created' do
allow(user).to receive_messages(current_sign_in_ip: 'invalid IP') allow_next_instance_of(AuditEvent) do |event|
allow(event).to receive(:valid?).and_return(false)
end
expect(Gitlab::ErrorTracking).to( expect(Gitlab::ErrorTracking).to(
receive(:track_exception) receive(:track_and_raise_for_dev_exception)
.with(ActiveRecord::RecordInvalid, audit_event_type: 'AuthenticationEvent').and_call_original
) )
audit_service.for_authentication.security_event audit_service.for_authentication.security_event
...@@ -93,7 +94,7 @@ RSpec.describe AuditEventService do ...@@ -93,7 +94,7 @@ RSpec.describe AuditEventService do
end end
specify do specify do
expect(AuthenticationEvent).to receive(:new).with(hash_including(ip_address: output)) expect(AuthenticationEvent).to receive(:new).with(hash_including(ip_address: output)).and_call_original
audit_service.for_authentication.security_event audit_service.for_authentication.security_event
end end
......
...@@ -7,13 +7,13 @@ RSpec.shared_examples 'user login operation with unique ip limit' do ...@@ -7,13 +7,13 @@ RSpec.shared_examples 'user login operation with unique ip limit' do
end end
it 'allows user authenticating from the same ip' do it 'allows user authenticating from the same ip' do
expect { operation_from_ip('ip') }.not_to raise_error expect { operation_from_ip('111.221.4.3') }.not_to raise_error
expect { operation_from_ip('ip') }.not_to raise_error expect { operation_from_ip('111.221.4.3') }.not_to raise_error
end end
it 'blocks user authenticating from two distinct ips' do it 'blocks user authenticating from two distinct ips' do
expect { operation_from_ip('ip') }.not_to raise_error expect { operation_from_ip('111.221.4.3') }.not_to raise_error
expect { operation_from_ip('ip2') }.to raise_error(Gitlab::Auth::TooManyIps) expect { operation_from_ip('1.2.2.3') }.to raise_error(Gitlab::Auth::TooManyIps)
end end
end end
end end
...@@ -25,13 +25,13 @@ RSpec.shared_examples 'user login request with unique ip limit' do |success_stat ...@@ -25,13 +25,13 @@ RSpec.shared_examples 'user login request with unique ip limit' do |success_stat
end end
it 'allows user authenticating from the same ip' do it 'allows user authenticating from the same ip' do
expect(request_from_ip('ip')).to have_gitlab_http_status(success_status) expect(request_from_ip('111.221.4.3')).to have_gitlab_http_status(success_status)
expect(request_from_ip('ip')).to have_gitlab_http_status(success_status) expect(request_from_ip('111.221.4.3')).to have_gitlab_http_status(success_status)
end end
it 'blocks user authenticating from two distinct ips' do it 'blocks user authenticating from two distinct ips' do
expect(request_from_ip('ip')).to have_gitlab_http_status(success_status) expect(request_from_ip('111.221.4.3')).to have_gitlab_http_status(success_status)
expect(request_from_ip('ip2')).to have_gitlab_http_status(:forbidden) expect(request_from_ip('1.2.2.3')).to have_gitlab_http_status(:forbidden)
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment