Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/services/web_hook_service_spec.rb')
-rw-r--r--spec/services/web_hook_service_spec.rb139
1 files changed, 130 insertions, 9 deletions
diff --git a/spec/services/web_hook_service_spec.rb b/spec/services/web_hook_service_spec.rb
index 2aebd2adab9..7d933ea9c5c 100644
--- a/spec/services/web_hook_service_spec.rb
+++ b/spec/services/web_hook_service_spec.rb
@@ -2,20 +2,12 @@
require 'spec_helper'
-RSpec.describe WebHookService do
+RSpec.describe WebHookService, :request_store, :clean_gitlab_redis_shared_state do
include StubRequests
let_it_be(:project) { create(:project) }
let_it_be_with_reload(:project_hook) { create(:project_hook, project: project) }
- let(:headers) do
- {
- 'Content-Type' => 'application/json',
- 'User-Agent' => "GitLab/#{Gitlab::VERSION}",
- 'X-Gitlab-Event' => 'Push Hook'
- }
- end
-
let(:data) do
{ before: 'oldrev', after: 'newrev', ref: 'ref' }
end
@@ -61,6 +53,21 @@ RSpec.describe WebHookService do
end
describe '#execute' do
+ let!(:uuid) { SecureRandom.uuid }
+ let(:headers) do
+ {
+ 'Content-Type' => 'application/json',
+ 'User-Agent' => "GitLab/#{Gitlab::VERSION}",
+ 'X-Gitlab-Event' => 'Push Hook',
+ 'X-Gitlab-Event-UUID' => uuid
+ }
+ end
+
+ before do
+ # Set a stable value for the `X-Gitlab-Event-UUID` header.
+ Gitlab::WebHooks::RecursionDetection.set_request_uuid(uuid)
+ end
+
context 'when token is defined' do
let_it_be(:project_hook) { create(:project_hook, :token) }
@@ -127,11 +134,74 @@ RSpec.describe WebHookService do
expect(service_instance.execute).to eq({ status: :error, message: 'Hook disabled' })
end
+ it 'executes and registers the hook with the recursion detection', :aggregate_failures do
+ stub_full_request(project_hook.url, method: :post)
+ cache_key = Gitlab::WebHooks::RecursionDetection.send(:cache_key_for_hook, project_hook)
+
+ ::Gitlab::Redis::SharedState.with do |redis|
+ expect { service_instance.execute }.to change {
+ redis.sismember(cache_key, project_hook.id)
+ }.to(true)
+ end
+
+ expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url))
+ .with(headers: headers)
+ .once
+ end
+
+ it 'executes and logs if a recursive web hook is detected', :aggregate_failures do
+ stub_full_request(project_hook.url, method: :post)
+ Gitlab::WebHooks::RecursionDetection.register!(project_hook)
+
+ expect(Gitlab::AuthLogger).to receive(:error).with(
+ include(
+ message: 'Webhook recursion detected and will be blocked in future',
+ hook_id: project_hook.id,
+ hook_type: 'ProjectHook',
+ hook_name: 'push_hooks',
+ recursion_detection: Gitlab::WebHooks::RecursionDetection.to_log(project_hook),
+ 'correlation_id' => kind_of(String)
+ )
+ )
+
+ service_instance.execute
+
+ expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url))
+ .with(headers: headers)
+ .once
+ end
+
+ it 'executes and logs if the recursion count limit would be exceeded', :aggregate_failures do
+ stub_full_request(project_hook.url, method: :post)
+ stub_const("#{Gitlab::WebHooks::RecursionDetection.name}::COUNT_LIMIT", 3)
+ previous_hooks = create_list(:project_hook, 3)
+ previous_hooks.each { Gitlab::WebHooks::RecursionDetection.register!(_1) }
+
+ expect(Gitlab::AuthLogger).to receive(:error).with(
+ include(
+ message: 'Webhook recursion detected and will be blocked in future',
+ hook_id: project_hook.id,
+ hook_type: 'ProjectHook',
+ hook_name: 'push_hooks',
+ recursion_detection: Gitlab::WebHooks::RecursionDetection.to_log(project_hook),
+ 'correlation_id' => kind_of(String)
+ )
+ )
+
+ service_instance.execute
+
+ expect(WebMock).to have_requested(:post, stubbed_hostname(project_hook.url))
+ .with(headers: headers)
+ .once
+ end
+
it 'handles exceptions' do
exceptions = Gitlab::HTTP::HTTP_ERRORS + [
Gitlab::Json::LimitedEncoder::LimitExceeded, URI::InvalidURIError
]
+ allow(Gitlab::WebHooks::RecursionDetection).to receive(:block?).and_return(false)
+
exceptions.each do |exception_class|
exception = exception_class.new('Exception message')
project_hook.enable!
@@ -420,6 +490,57 @@ RSpec.describe WebHookService do
end
end
+ context 'recursion detection' do
+ before do
+ # Set a request UUID so `RecursionDetection.block?` will query redis.
+ Gitlab::WebHooks::RecursionDetection.set_request_uuid(SecureRandom.uuid)
+ end
+
+ it 'queues a worker and logs an error if the call chain limit would be exceeded' do
+ stub_const("#{Gitlab::WebHooks::RecursionDetection.name}::COUNT_LIMIT", 3)
+ previous_hooks = create_list(:project_hook, 3)
+ previous_hooks.each { Gitlab::WebHooks::RecursionDetection.register!(_1) }
+
+ expect(WebHookWorker).to receive(:perform_async)
+ expect(Gitlab::AuthLogger).to receive(:error).with(
+ include(
+ message: 'Webhook recursion detected and will be blocked in future',
+ hook_id: project_hook.id,
+ hook_type: 'ProjectHook',
+ hook_name: 'push_hooks',
+ recursion_detection: Gitlab::WebHooks::RecursionDetection.to_log(project_hook),
+ 'correlation_id' => kind_of(String),
+ 'meta.project' => project.full_path,
+ 'meta.related_class' => 'ProjectHook',
+ 'meta.root_namespace' => project.root_namespace.full_path
+ )
+ )
+
+ service_instance.async_execute
+ end
+
+ it 'queues a worker and logs an error if a recursive call chain is detected' do
+ Gitlab::WebHooks::RecursionDetection.register!(project_hook)
+
+ expect(WebHookWorker).to receive(:perform_async)
+ expect(Gitlab::AuthLogger).to receive(:error).with(
+ include(
+ message: 'Webhook recursion detected and will be blocked in future',
+ hook_id: project_hook.id,
+ hook_type: 'ProjectHook',
+ hook_name: 'push_hooks',
+ recursion_detection: Gitlab::WebHooks::RecursionDetection.to_log(project_hook),
+ 'correlation_id' => kind_of(String),
+ 'meta.project' => project.full_path,
+ 'meta.related_class' => 'ProjectHook',
+ 'meta.root_namespace' => project.root_namespace.full_path
+ )
+ )
+
+ service_instance.async_execute
+ end
+ end
+
context 'when hook has custom context attributes' do
it 'includes the attributes in the worker context' do
expect(WebHookWorker).to receive(:perform_async) do