mirror of
https://github.com/redmine/redmine.git
synced 2025-10-26 07:46:17 +01:00
Patch by Jens Krämer (user:jkraemer). git-svn-id: https://svn.redmine.org/redmine/trunk@24035 e93f8b46-1217-0410-a6f0-8f06a7374b81
200 lines
6.1 KiB
Ruby
200 lines
6.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'test_helper'
|
|
require 'pp'
|
|
|
|
class WebhookTest < ActiveSupport::TestCase
|
|
include ActiveJob::TestHelper
|
|
|
|
fixtures :projects, :users, :email_addresses, :user_preferences, :members, :member_roles, :roles,
|
|
:groups_users,
|
|
:trackers, :projects_trackers,
|
|
:enabled_modules,
|
|
:versions,
|
|
:issue_statuses, :issue_categories, :issue_relations, :workflows,
|
|
:enumerations,
|
|
:issues, :journals, :journal_details
|
|
|
|
setup do
|
|
# Set ActiveJob to use the test adapter
|
|
@original_adapter = ActiveJob::Base.queue_adapter
|
|
ActiveJob::Base.queue_adapter = :test
|
|
|
|
@project = Project.find 'ecookbook'
|
|
@dlopper = User.find_by_login 'dlopper'
|
|
@role = Role.find_by_name 'Developer'
|
|
@role.permissions << :use_webhooks; @role.save!
|
|
@issue = @project.issues.first
|
|
WebhookEndpointValidator.class_eval do
|
|
@blocked_hosts = nil
|
|
end
|
|
end
|
|
|
|
teardown do
|
|
# Restore the original adapter
|
|
ActiveJob::Base.queue_adapter = @original_adapter
|
|
end
|
|
|
|
test "should validate url" do
|
|
Redmine::Configuration.with('webhook_blocklist' => ['*.example.org', '10.0.0.0/8', '192.168.0.0/16']) do
|
|
%w[
|
|
mailto:user@example.com
|
|
https://x.example.org/
|
|
https://example.org/
|
|
https://x.example.org/foo/bar?a=b
|
|
foobar
|
|
example.com
|
|
https://10.1.0.12/
|
|
].each do |url|
|
|
hook = Webhook.new(url: url)
|
|
assert_not hook.valid?, "URL '#{url}' should be invalid"
|
|
assert hook.errors[:url].any?
|
|
end
|
|
end
|
|
end
|
|
|
|
test "should validate secret length" do
|
|
hook = Webhook.new secret: 'abdc' * 100
|
|
assert_not hook.valid?
|
|
assert hook.errors[:secret].any?
|
|
end
|
|
|
|
test "should validate events" do
|
|
Webhook.new.setable_event_names.each do |event|
|
|
h = create_hook events: [event]
|
|
assert h.persisted?
|
|
end
|
|
hook = Webhook.new(events: ['issue.created', 'invalid.event'])
|
|
assert_not hook.valid?
|
|
assert hook.errors[:events].any?
|
|
assert_raise(ActiveRecord::SerializationTypeMismatch){ Webhook.new(events: 'issue.created') }
|
|
end
|
|
|
|
test "should clean up project list based on permissions on save" do
|
|
h = create_hook
|
|
assert_equal [@project], h.projects
|
|
@role.permissions.delete :use_webhooks
|
|
@role.save!
|
|
|
|
h.reload
|
|
h.save
|
|
h.reload
|
|
assert_equal [], h.projects
|
|
end
|
|
|
|
test "should clean up project list based on project visibility on save" do
|
|
h = create_hook
|
|
assert_equal [@project], h.projects
|
|
@project.memberships.destroy_all
|
|
@project.update is_public: false
|
|
|
|
h.reload
|
|
h.save
|
|
h.reload
|
|
assert_equal [], h.projects
|
|
end
|
|
|
|
test "should filter setable projects" do
|
|
assert_equal [@project], Webhook.new(user: @dlopper).setable_projects
|
|
|
|
@role.permissions.delete :use_webhooks
|
|
@role.save!
|
|
@dlopper.reload
|
|
assert_equal [], Webhook.new(user: @dlopper).setable_projects
|
|
end
|
|
|
|
test "should check ip address at run time" do
|
|
Redmine::Configuration.with('webhook_blocklist' => ['*.example.org', '10.0.0.0/8', '192.168.0.0/16']) do
|
|
%w[
|
|
127.0.0.0
|
|
127.0.0.1
|
|
10.0.0.0
|
|
10.0.1.0
|
|
169.254.1.9
|
|
192.168.2.1
|
|
224.0.0.1
|
|
::1/128
|
|
fe80::/10
|
|
].each do |ip|
|
|
h = Webhook.new url: "http://#{ip}"
|
|
assert_not h.valid?, "IP #{ip} should be invalid"
|
|
assert h.errors[:url].any?
|
|
end
|
|
end
|
|
end
|
|
|
|
test "should find hooks for issue" do
|
|
hook = create_hook
|
|
assert @issue.visible?(hook.user)
|
|
assert_equal [hook], Webhook.hooks_for('issue.created', @issue)
|
|
assert_equal [], Webhook.hooks_for('issue.deleted', @issue)
|
|
@issue.update_column :project_id, 99
|
|
assert_equal [], Webhook.hooks_for('issue.created', @issue)
|
|
end
|
|
|
|
test "should check permission when looking for hooks" do
|
|
hook = create_hook
|
|
assert @issue.visible?(hook.user)
|
|
assert_equal [hook], Webhook.hooks_for('issue.created', @issue)
|
|
@role.permissions.delete :use_webhooks
|
|
@role.save!
|
|
assert_equal [], Webhook.hooks_for('issue.created', @issue)
|
|
end
|
|
|
|
test "should not find inactive hook" do
|
|
hook = create_hook active: false
|
|
assert @issue.visible?(hook.user)
|
|
assert_equal [], Webhook.hooks_for('issue.created', @issue)
|
|
end
|
|
|
|
test "should not find hook of inactive user" do
|
|
admin = User.find_by_login 'admin'
|
|
hook = create_hook user: admin
|
|
assert_equal [hook], Webhook.hooks_for('issue.created', @issue)
|
|
admin.update_column :status, 3
|
|
assert_equal [], Webhook.hooks_for('issue.created', @issue)
|
|
end
|
|
|
|
test "should find hook for deleted issue" do
|
|
hook = create_hook events: ['issue.deleted']
|
|
@issue.destroy
|
|
assert_equal [hook], Webhook.hooks_for('issue.deleted', @issue)
|
|
end
|
|
|
|
test "schedule should enqueue jobs for hooks" do
|
|
hook = create_hook
|
|
assert_enqueued_jobs 1 do
|
|
assert_enqueued_with(job: WebhookJob) do
|
|
Webhook.trigger('issue.created', @issue)
|
|
end
|
|
end
|
|
end
|
|
|
|
test "should not enqueue job for inactive hook" do
|
|
hook = create_hook active: false
|
|
assert_no_enqueued_jobs do
|
|
Webhook.trigger('issue.created', @issue)
|
|
end
|
|
end
|
|
|
|
test "should compute payload" do
|
|
hook = create_hook
|
|
payload = hook.payload('issue.created', @issue)
|
|
assert_equal 'issue.created', payload[:type]
|
|
assert_equal @issue.id, payload.dig(:data, :issue, :id)
|
|
end
|
|
|
|
test "should compute correct signature" do
|
|
# we're implementing the same signature mechanism as GitHub, so might as well re-use their
|
|
# example. https://docs.github.com/en/webhooks/using-webhooks/validating-webhook-deliveries
|
|
e = Webhook::Executor.new('https://example.com', 'Hello, World!', "It's a Secret to Everybody")
|
|
assert_equal "sha256=757107ea0eb2509fc211221cce984b8a37570b6d7586c22c46f4379c8b043e17", e.compute_signature
|
|
end
|
|
|
|
private
|
|
|
|
def create_hook(url: 'https://example.com/some/hook', user: User.find_by_login('dlopper'), projects: [Project.find('ecookbook')], events: ['issue.created'], active: true)
|
|
Webhook.create!(url: url, user: user, projects: projects, events: events, active: active)
|
|
end
|
|
end
|