# frozen_string_literal: true require 'spec_helper' RSpec.describe Gitlab::Metrics::Subscribers::RackAttack, :request_store do let(:subscriber) { described_class.new } describe '.payload' do context 'when the request store is empty' do it 'returns empty data' do expect(described_class.payload).to eql( rack_attack_redis_count: 0, rack_attack_redis_duration_s: 0.0 ) end end context 'when the request store already has data' do before do Gitlab::SafeRequestStore[:rack_attack_instrumentation] = { rack_attack_redis_count: 10, rack_attack_redis_duration_s: 9.0 } end it 'returns the accumulated data' do expect(described_class.payload).to eql( rack_attack_redis_count: 10, rack_attack_redis_duration_s: 9.0 ) end end end describe '#redis' do it 'accumulates per-request RackAttack cache usage' do freeze_time do subscriber.redis( ActiveSupport::Notifications::Event.new( 'redis.rack_attack', Time.current, Time.current + 1.second, '1', { operation: 'fetch' } ) ) subscriber.redis( ActiveSupport::Notifications::Event.new( 'redis.rack_attack', Time.current, Time.current + 2.seconds, '1', { operation: 'write' } ) ) subscriber.redis( ActiveSupport::Notifications::Event.new( 'redis.rack_attack', Time.current, Time.current + 3.seconds, '1', { operation: 'read' } ) ) end expect(Gitlab::SafeRequestStore[:rack_attack_instrumentation]).to eql( rack_attack_redis_count: 3, rack_attack_redis_duration_s: 6.0 ) end end shared_examples 'log into auth logger' do context 'when matched throttle does not require user information' do let(:event) do ActiveSupport::Notifications::Event.new( event_name, Time.current, Time.current + 2.seconds, '1', request: double( :request, ip: '1.2.3.4', request_method: 'GET', fullpath: '/api/v4/internal/authorized_keys', env: { 'rack.attack.match_type' => match_type, 'rack.attack.matched' => 'throttle_unauthenticated' } ) ) end it 'logs request information' do expect(Gitlab::AuthLogger).to receive(:error).with( include( message: 'Rack_Attack', env: match_type, remote_ip: '1.2.3.4', request_method: 'GET', path: '/api/v4/internal/authorized_keys', matched: 'throttle_unauthenticated' ) ) subscriber.send(match_type, event) end end context 'when matched throttle requires user information' do context 'when user not found' do let(:event) do ActiveSupport::Notifications::Event.new( event_name, Time.current, Time.current + 2.seconds, '1', request: double( :request, ip: '1.2.3.4', request_method: 'GET', fullpath: '/api/v4/internal/authorized_keys', env: { 'rack.attack.match_type' => match_type, 'rack.attack.matched' => 'throttle_authenticated_api', 'rack.attack.match_discriminator' => 'not_exist_user_id' } ) ) end it 'logs request information and user id' do expect(Gitlab::AuthLogger).to receive(:error).with( include( message: 'Rack_Attack', env: match_type, remote_ip: '1.2.3.4', request_method: 'GET', path: '/api/v4/internal/authorized_keys', matched: 'throttle_authenticated_api', user_id: 'not_exist_user_id' ) ) subscriber.send(match_type, event) end end context 'when user found' do let(:user) { create(:user) } let(:event) do ActiveSupport::Notifications::Event.new( event_name, Time.current, Time.current + 2.seconds, '1', request: double( :request, ip: '1.2.3.4', request_method: 'GET', fullpath: '/api/v4/internal/authorized_keys', env: { 'rack.attack.match_type' => match_type, 'rack.attack.matched' => 'throttle_authenticated_api', 'rack.attack.match_discriminator' => user.id } ) ) end it 'logs request information and user meta' do expect(Gitlab::AuthLogger).to receive(:error).with( include( message: 'Rack_Attack', env: match_type, remote_ip: '1.2.3.4', request_method: 'GET', path: '/api/v4/internal/authorized_keys', matched: 'throttle_authenticated_api', user_id: user.id, 'meta.user' => user.username ) ) subscriber.send(match_type, event) end end end end describe '#throttle' do let(:match_type) { :throttle } let(:event_name) { 'throttle.rack_attack' } it_behaves_like 'log into auth logger' end describe '#blocklist' do let(:match_type) { :blocklist } let(:event_name) { 'blocklist.rack_attack' } it_behaves_like 'log into auth logger' end describe '#track' do let(:match_type) { :track } let(:event_name) { 'track.rack_attack' } it_behaves_like 'log into auth logger' end describe '#safelist' do let(:event) do ActiveSupport::Notifications::Event.new( 'safelist.rack_attack', Time.current, Time.current + 2.seconds, '1', request: double( :request, env: { 'rack.attack.matched' => 'throttle_unauthenticated' } ) ) end it 'adds the matched name to safe request store' do subscriber.safelist(event) expect(Gitlab::SafeRequestStore[:instrumentation_throttle_safelist]).to eql('throttle_unauthenticated') end end end