2015-12-01 18:45:36 -05:00
|
|
|
# UrlValidator
|
|
|
|
#
|
|
|
|
# Custom validator for URLs.
|
|
|
|
#
|
|
|
|
# By default, only URLs for the HTTP(S) protocols will be considered valid.
|
|
|
|
# Provide a `:protocols` option to configure accepted protocols.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
#
|
|
|
|
# class User < ActiveRecord::Base
|
|
|
|
# validates :personal_url, url: true
|
|
|
|
#
|
|
|
|
# validates :ftp_url, url: { protocols: %w(ftp) }
|
|
|
|
#
|
|
|
|
# validates :git_url, url: { protocols: %w(http https ssh git) }
|
|
|
|
# end
|
|
|
|
#
|
2018-06-01 07:43:53 -04:00
|
|
|
# This validator can also block urls pointing to localhost or the local network to
|
|
|
|
# protect against Server-side Request Forgery (SSRF), or check for the right port.
|
|
|
|
#
|
|
|
|
# Example:
|
|
|
|
# class User < ActiveRecord::Base
|
|
|
|
# validates :personal_url, url: { allow_localhost: false, allow_local_network: false}
|
|
|
|
#
|
|
|
|
# validates :web_url, url: { ports: [80, 443] }
|
|
|
|
# end
|
2015-12-01 18:45:36 -05:00
|
|
|
class UrlValidator < ActiveModel::EachValidator
|
2018-06-01 07:43:53 -04:00
|
|
|
DEFAULT_PROTOCOLS = %w(http https).freeze
|
|
|
|
|
|
|
|
attr_reader :record
|
|
|
|
|
2015-12-01 18:45:36 -05:00
|
|
|
def validate_each(record, attribute, value)
|
2018-06-01 07:43:53 -04:00
|
|
|
@record = record
|
|
|
|
|
|
|
|
if value.present?
|
|
|
|
value.strip!
|
|
|
|
else
|
2015-12-01 18:45:36 -05:00
|
|
|
record.errors.add(attribute, "must be a valid URL")
|
|
|
|
end
|
2018-06-01 07:43:53 -04:00
|
|
|
|
|
|
|
Gitlab::UrlBlocker.validate!(value, blocker_args)
|
|
|
|
rescue Gitlab::UrlBlocker::BlockedUrlError => e
|
|
|
|
record.errors.add(attribute, "is blocked: #{e.message}")
|
2015-12-01 18:45:36 -05:00
|
|
|
end
|
|
|
|
|
|
|
|
private
|
|
|
|
|
|
|
|
def default_options
|
2018-06-01 07:43:53 -04:00
|
|
|
# By default the validator doesn't block any url based on the ip address
|
|
|
|
{
|
|
|
|
protocols: DEFAULT_PROTOCOLS,
|
|
|
|
ports: [],
|
|
|
|
allow_localhost: true,
|
|
|
|
allow_local_network: true
|
|
|
|
}
|
2015-12-01 18:45:36 -05:00
|
|
|
end
|
|
|
|
|
2018-06-01 07:43:53 -04:00
|
|
|
def current_options
|
|
|
|
options = self.options.map do |option, value|
|
|
|
|
[option, value.is_a?(Proc) ? value.call(record) : value]
|
|
|
|
end.to_h
|
|
|
|
|
|
|
|
default_options.merge(options)
|
|
|
|
end
|
2016-02-22 07:55:36 -05:00
|
|
|
|
2018-06-01 07:43:53 -04:00
|
|
|
def blocker_args
|
|
|
|
current_options.slice(:allow_localhost, :allow_local_network, :protocols, :ports).tap do |args|
|
|
|
|
if allow_setting_local_requests?
|
|
|
|
args[:allow_localhost] = args[:allow_local_network] = true
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|
2015-12-01 18:45:36 -05:00
|
|
|
|
2018-06-01 07:43:53 -04:00
|
|
|
def allow_setting_local_requests?
|
|
|
|
ApplicationSetting.current&.allow_local_requests_from_hooks_and_services?
|
2015-12-01 18:45:36 -05:00
|
|
|
end
|
|
|
|
end
|