Class: AddressableUrlValidator

Inherits:
ActiveModel::EachValidator
  • Object
show all
Defined in:
app/validators/addressable_url_validator.rb

Overview

AddressableUrlValidator

Custom validator for URLs. This is a stricter version of UrlValidator - it also checks for using the right protocol, but it actually parses the URL checking for any syntax errors. The regex is also different from ‘URI` as we use `Addressable::URI` here.

By default, only URLs for the HTTP(S) schemes will be considered valid. Provide a ‘:schemes` option to configure accepted schemes.

Example:

class User < ActiveRecord::Base
  validates :personal_url, addressable_url: true

  validates :ftp_url, addressable_url: { schemes: %w(ftp) }

  validates :git_url, addressable_url: { schemes: %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.

Configuration options:

  • message - A custom error message, used when the URL is blank. (default is: “must be a valid URL”).

  • blocked_message - A custom error message, used when the URL is blocked. Default: ‘is blocked: %{exception_message}’.

  • schemes - Array of URI schemes. Default: [‘http’, ‘https’]

  • allow_localhost - Allow urls pointing to localhost. Default: true

  • allow_local_network - Allow urls pointing to private network addresses. Default: true

  • allow_blank - Allow urls to be blank. Default: false

  • allow_nil - Allow urls to be nil. Default: false

  • ports - Allowed ports. Default: all.

  • deny_all_requests_except_allowed - Deny all requests. Default: Respects the instance app setting.

    Note: Regardless of whether enforced during validation, an HTTP request that uses the URI may still be blocked.
    
  • enforce_user - Validate user format. Default: false

  • enforce_sanitization - Validate that there are no html/css/js tags. Default: false

Example:

class User < ActiveRecord::Base
  validates :personal_url, addressable_url: { allow_localhost: false, allow_local_network: false}

  validates :web_url, addressable_url: { ports: [80, 443] }
end

Direct Known Subclasses

PublicUrlValidator

Constant Summary collapse

BLOCKER_VALIDATE_OPTIONS =

By default, we avoid checking the dns rebinding protection when saving/updating a record. Sometimes, the url is not resolvable at that point, and some automated tasks that uses that url won’t work. See gitlab.com/gitlab-org/gitlab-foss/issues/66723

{
  schemes: %w[http https],
  ports: [],
  allow_localhost: true,
  allow_local_network: true,
  ascii_only: false,
  deny_all_requests_except_allowed: Gitlab::UrlBlocker::DENY_ALL_REQUESTS_EXCEPT_ALLOWED_DEFAULT,
  enforce_user: false,
  enforce_sanitization: false,
  dns_rebind_protection: false
}.freeze
DEFAULT_OPTIONS =
BLOCKER_VALIDATE_OPTIONS.merge({
  message: 'must be a valid URL',
  blocked_message: 'is blocked: %{exception_message}'
}).freeze

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(options) ⇒ AddressableUrlValidator

Returns a new instance of AddressableUrlValidator.



70
71
72
73
74
# File 'app/validators/addressable_url_validator.rb', line 70

def initialize(options)
  options.reverse_merge!(DEFAULT_OPTIONS)

  super(options)
end

Instance Attribute Details

#recordObject (readonly)

Returns the value of attribute record.



46
47
48
# File 'app/validators/addressable_url_validator.rb', line 46

def record
  @record
end

Instance Method Details

#validate_each(record, attribute, value) ⇒ Object



76
77
78
79
80
81
82
83
84
85
86
87
88
89
# File 'app/validators/addressable_url_validator.rb', line 76

def validate_each(record, attribute, value)
  @record = record

  unless value.present?
    record.errors.add(attribute, options.fetch(:message))
    return
  end

  value = strip_value!(record, attribute, value)

  Gitlab::UrlBlocker.validate!(value, **blocker_args)
rescue Gitlab::UrlBlocker::BlockedUrlError => e
  record.errors.add(attribute, options.fetch(:blocked_message) % { exception_message: e.message })
end