summaryrefslogtreecommitdiff
path: root/app/validators/url_validator.rb
diff options
context:
space:
mode:
Diffstat (limited to 'app/validators/url_validator.rb')
-rw-r--r--app/validators/url_validator.rb52
1 files changed, 45 insertions, 7 deletions
diff --git a/app/validators/url_validator.rb b/app/validators/url_validator.rb
index a77beb2683d..8648c4c75e3 100644
--- a/app/validators/url_validator.rb
+++ b/app/validators/url_validator.rb
@@ -15,25 +15,63 @@
# validates :git_url, url: { protocols: %w(http https ssh git) }
# end
#
+# 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
class UrlValidator < ActiveModel::EachValidator
+ DEFAULT_PROTOCOLS = %w(http https).freeze
+
+ attr_reader :record
+
def validate_each(record, attribute, value)
- unless valid_url?(value)
+ @record = record
+
+ if value.present?
+ value.strip!
+ else
record.errors.add(attribute, "must be a valid URL")
end
+
+ Gitlab::UrlBlocker.validate!(value, blocker_args)
+ rescue Gitlab::UrlBlocker::BlockedUrlError => e
+ record.errors.add(attribute, "is blocked: #{e.message}")
end
private
def default_options
- @default_options ||= { protocols: %w(http https) }
+ # 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
+ }
end
- def valid_url?(value)
- return false if value.nil?
+ 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
- options = default_options.merge(self.options)
+ 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
- value.strip!
- value =~ /\A#{URI.regexp(options[:protocols])}\z/
+ def allow_setting_local_requests?
+ ApplicationSetting.current&.allow_local_requests_from_hooks_and_services?
end
end