mirror of
https://github.com/redmine/redmine.git
synced 2025-10-26 00:36:14 +02:00
* users can set up hooks for issue creation, update and deletion events, for any number of projects * hooks run in the context of the creating user, and only if the object in question is visible to that user * the actual HTTP call is done in ActiveJob * webhook calls are optionally signed the same way GitHub does Patch by Jens Krämer (user:jkraemer). git-svn-id: https://svn.redmine.org/redmine/trunk@24034 e93f8b46-1217-0410-a6f0-8f06a7374b81
173 lines
4.2 KiB
Ruby
173 lines
4.2 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'uri'
|
|
|
|
class WebhookEndpointValidator < ActiveModel::EachValidator
|
|
def validate_each(record, attribute, value)
|
|
return if value.blank?
|
|
|
|
unless self.class.safe_webhook_uri?(value)
|
|
record.errors.add attribute, :invalid
|
|
end
|
|
end
|
|
|
|
def self.safe_webhook_uri?(value)
|
|
uri = value.is_a?(URI) ? value : URI.parse(value)
|
|
return false if uri.nil?
|
|
|
|
return false unless valid_scheme?(uri.scheme)
|
|
return false unless valid_host?(uri.host)
|
|
return false unless valid_port?(uri.port)
|
|
|
|
true
|
|
rescue
|
|
Rails.logger.warn { "URI failed webhook safety checks: #{uri}" }
|
|
false
|
|
end
|
|
|
|
def self.valid_port?(port)
|
|
!BAD_PORTS.include?(port)
|
|
end
|
|
|
|
def self.valid_scheme?(scheme)
|
|
%w[http https].include?(scheme)
|
|
end
|
|
|
|
def self.blocked_hosts
|
|
@blocked_hosts ||= begin
|
|
ips = []
|
|
wildcards = []
|
|
hosts = []
|
|
|
|
Array(Redmine::Configuration['webhook_blocklist']).map(&:to_s).each do |block|
|
|
# We try to parse the block as an IP address first...
|
|
ips << IPAddr.new(block)
|
|
rescue IPAddr::Error
|
|
# If that failed, we assume it is a (wildcard) hostname
|
|
if block.start_with?('*.')
|
|
wildcards << Regexp.escape(block[2..])
|
|
else
|
|
hosts << Regexp.escape(block)
|
|
end
|
|
end
|
|
|
|
regex_parts = []
|
|
regex_parts << "(?:#{hosts.join('|')})" if hosts.any?
|
|
regex_parts << "(?:.*\\.)?(?:#{wildcards.join('|')})" if wildcards.any?
|
|
|
|
{
|
|
ips: ips.freeze,
|
|
host: regex_parts.any? ? /\A(?:#{regex_parts.join('|')})\z/i : nil
|
|
}.freeze
|
|
end
|
|
end
|
|
|
|
def self.valid_host?(host)
|
|
return false if host.blank?
|
|
|
|
return false if blocked_hosts[:host]&.match?(host)
|
|
|
|
Resolv.each_address(host) do |ip|
|
|
ipaddr = IPAddr.new(ip)
|
|
return false if ipaddr.link_local? || ipaddr.loopback?
|
|
return false if IPAddr.new('224.0.0.0/24').include?(ipaddr) # multicast
|
|
return false if blocked_hosts[:ips].any? { |net| net.include?(ipaddr) }
|
|
end
|
|
|
|
true
|
|
end
|
|
|
|
# A general port blacklist. Connections to these ports will not be allowed
|
|
# unless the protocol overrides.
|
|
#
|
|
# This list is to be kept in sync with "bad ports" as defined in the
|
|
# WHATWG Fetch standard at https://fetch.spec.whatwg.org/#port-blocking
|
|
#
|
|
# see also: https://github.com/mozilla/gecko-dev/blob/d55e89d48a8053ce45a74b0ec92c0ff6a9dcc43d/netwerk/base/nsIOService.cpp#L109-L199
|
|
#
|
|
BAD_PORTS = Set[
|
|
1, # tcpmux
|
|
7, # echo
|
|
9, # discard
|
|
11, # systat
|
|
13, # daytime
|
|
15, # netstat
|
|
17, # qotd
|
|
19, # chargen
|
|
20, # ftp-data
|
|
21, # ftp
|
|
22, # ssh
|
|
23, # telnet
|
|
25, # smtp
|
|
37, # time
|
|
42, # name
|
|
43, # nicname
|
|
53, # domain
|
|
69, # tftp
|
|
77, # priv-rjs
|
|
79, # finger
|
|
87, # ttylink
|
|
95, # supdup
|
|
101, # hostriame
|
|
102, # iso-tsap
|
|
103, # gppitnp
|
|
104, # acr-nema
|
|
109, # pop2
|
|
110, # pop3
|
|
111, # sunrpc
|
|
113, # auth
|
|
115, # sftp
|
|
117, # uucp-path
|
|
119, # nntp
|
|
123, # ntp
|
|
135, # loc-srv / epmap
|
|
137, # netbios
|
|
139, # netbios
|
|
143, # imap2
|
|
161, # snmp
|
|
179, # bgp
|
|
389, # ldap
|
|
427, # afp (alternate)
|
|
465, # smtp (alternate)
|
|
512, # print / exec
|
|
513, # login
|
|
514, # shell
|
|
515, # printer
|
|
526, # tempo
|
|
530, # courier
|
|
531, # chat
|
|
532, # netnews
|
|
540, # uucp
|
|
548, # afp
|
|
554, # rtsp
|
|
556, # remotefs
|
|
563, # nntp+ssl
|
|
587, # smtp (outgoing)
|
|
601, # syslog-conn
|
|
636, # ldap+ssl
|
|
989, # ftps-data
|
|
990, # ftps
|
|
993, # imap+ssl
|
|
995, # pop3+ssl
|
|
1719, # h323gatestat
|
|
1720, # h323hostcall
|
|
1723, # pptp
|
|
2049, # nfs
|
|
3659, # apple-sasl
|
|
4045, # lockd
|
|
4190, # sieve
|
|
5060, # sip
|
|
5061, # sips
|
|
6000, # x11
|
|
6566, # sane-port
|
|
6665, # irc (alternate)
|
|
6666, # irc (alternate)
|
|
6667, # irc (default)
|
|
6668, # irc (alternate)
|
|
6669, # irc (alternate)
|
|
6679, # osaut
|
|
6697, # irc+tls
|
|
10080 # amanda
|
|
].freeze
|
|
end
|