Commit 6accf077 authored by James Lopez's avatar James Lopez

Merge branch 'fix/url_validator_' into 'master'

EE: Align UrlValidator to validate_url gem implementation.

See merge request gitlab-org/gitlab-ee!10693
parents 6fbf9088 c7b96984
...@@ -48,17 +48,17 @@ class ApplicationSetting < ApplicationRecord ...@@ -48,17 +48,17 @@ class ApplicationSetting < ApplicationRecord
validates :home_page_url, validates :home_page_url,
allow_blank: true, allow_blank: true,
url: true, addressable_url: true,
if: :home_page_url_column_exists? if: :home_page_url_column_exists?
validates :help_page_support_url, validates :help_page_support_url,
allow_blank: true, allow_blank: true,
url: true, addressable_url: true,
if: :help_page_support_url_column_exists? if: :help_page_support_url_column_exists?
validates :after_sign_out_path, validates :after_sign_out_path,
allow_blank: true, allow_blank: true,
url: true addressable_url: true
validates :admin_notification_email, validates :admin_notification_email,
devise_email: true, devise_email: true,
...@@ -218,7 +218,7 @@ class ApplicationSetting < ApplicationRecord ...@@ -218,7 +218,7 @@ class ApplicationSetting < ApplicationRecord
if: :external_authorization_service_enabled if: :external_authorization_service_enabled
validates :external_authorization_service_url, validates :external_authorization_service_url,
url: true, allow_blank: true, addressable_url: true, allow_blank: true,
if: :external_authorization_service_enabled if: :external_authorization_service_enabled
validates :external_authorization_service_timeout, validates :external_authorization_service_timeout,
......
...@@ -22,7 +22,7 @@ class Badge < ApplicationRecord ...@@ -22,7 +22,7 @@ class Badge < ApplicationRecord
scope :order_created_at_asc, -> { reorder(created_at: :asc) } scope :order_created_at_asc, -> { reorder(created_at: :asc) }
validates :link_url, :image_url, url: { protocols: %w(http https) } validates :link_url, :image_url, addressable_url: true
validates :type, presence: true validates :type, presence: true
def rendered_link_url(project = nil) def rendered_link_url(project = nil)
......
...@@ -13,7 +13,7 @@ module Ci ...@@ -13,7 +13,7 @@ module Ci
belongs_to :build, class_name: 'Ci::Build', inverse_of: :runner_session belongs_to :build, class_name: 'Ci::Build', inverse_of: :runner_session
validates :build, presence: true validates :build, presence: true
validates :url, url: { protocols: %w(https) } validates :url, addressable_url: { schemes: %w(https) }
def terminal_specification def terminal_specification
wss_url = Gitlab::UrlHelpers.as_wss(self.url) wss_url = Gitlab::UrlHelpers.as_wss(self.url)
......
...@@ -35,7 +35,7 @@ class Environment < ApplicationRecord ...@@ -35,7 +35,7 @@ class Environment < ApplicationRecord
validates :external_url, validates :external_url,
length: { maximum: 255 }, length: { maximum: 255 },
allow_nil: true, allow_nil: true,
url: true addressable_url: true
delegate :stop_action, :manual_actions, to: :last_deployment, allow_nil: true delegate :stop_action, :manual_actions, to: :last_deployment, allow_nil: true
......
...@@ -22,7 +22,7 @@ module ErrorTracking ...@@ -22,7 +22,7 @@ module ErrorTracking
belongs_to :project belongs_to :project
validates :api_url, length: { maximum: 255 }, public_url: true, url: { enforce_sanitization: true, ascii_only: true }, allow_nil: true validates :api_url, length: { maximum: 255 }, public_url: { enforce_sanitization: true, ascii_only: true }, allow_nil: true
validates :api_url, presence: { message: 'is a required field' }, if: :enabled validates :api_url, presence: { message: 'is a required field' }, if: :enabled
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
class GenericCommitStatus < CommitStatus class GenericCommitStatus < CommitStatus
before_validation :set_default_values before_validation :set_default_values
validates :target_url, url: true, validates :target_url, addressable_url: true,
length: { maximum: 255 }, length: { maximum: 255 },
allow_nil: true allow_nil: true
......
...@@ -327,7 +327,7 @@ class Project < ApplicationRecord ...@@ -327,7 +327,7 @@ class Project < ApplicationRecord
validates :namespace, presence: true validates :namespace, presence: true
validates :name, uniqueness: { scope: :namespace_id } validates :name, uniqueness: { scope: :namespace_id }
validates :import_url, public_url: { protocols: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS }, validates :import_url, public_url: { schemes: ->(project) { project.persisted? ? VALID_MIRROR_PROTOCOLS : VALID_IMPORT_PROTOCOLS },
ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS }, ports: ->(project) { project.persisted? ? VALID_MIRROR_PORTS : VALID_IMPORT_PORTS },
enforce_user: true }, if: [:external_import?, :import_url_changed?] enforce_user: true }, if: [:external_import?, :import_url_changed?]
validates :star_count, numericality: { greater_than_or_equal_to: 0 } validates :star_count, numericality: { greater_than_or_equal_to: 0 }
......
...@@ -6,7 +6,7 @@ module Releases ...@@ -6,7 +6,7 @@ module Releases
belongs_to :release belongs_to :release
validates :url, presence: true, url: { protocols: %w(http https ftp) }, uniqueness: { scope: :release } validates :url, presence: true, addressable_url: { schemes: %w(http https ftp) }, uniqueness: { scope: :release }
validates :name, presence: true, uniqueness: { scope: :release } validates :name, presence: true, uniqueness: { scope: :release }
scope :sorted, -> { order(created_at: :desc) } scope :sorted, -> { order(created_at: :desc) }
......
...@@ -17,7 +17,7 @@ class RemoteMirror < ApplicationRecord ...@@ -17,7 +17,7 @@ class RemoteMirror < ApplicationRecord
belongs_to :project, inverse_of: :remote_mirrors belongs_to :project, inverse_of: :remote_mirrors
validates :url, presence: true, public_url: { protocols: %w(ssh git http https), allow_blank: true, enforce_user: true } validates :url, presence: true, public_url: { schemes: %w(ssh git http https), allow_blank: true, enforce_user: true }
before_save :set_new_remote_name, if: :mirror_url_changed? before_save :set_new_remote_name, if: :mirror_url_changed?
......
# frozen_string_literal: true # frozen_string_literal: true
# UrlValidator # AddressableUrlValidator
# #
# Custom validator for URLs. # 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) protocols will be considered valid. # By default, only URLs for the HTTP(S) schemes will be considered valid.
# Provide a `:protocols` option to configure accepted protocols. # Provide a `:schemes` option to configure accepted schemes.
# #
# Example: # Example:
# #
# class User < ActiveRecord::Base # class User < ActiveRecord::Base
# validates :personal_url, url: true # validates :personal_url, addressable_url: true
# #
# validates :ftp_url, url: { protocols: %w(ftp) } # validates :ftp_url, addressable_url: { schemes: %w(ftp) }
# #
# validates :git_url, url: { protocols: %w(http https ssh git) } # validates :git_url, addressable_url: { schemes: %w(http https ssh git) }
# end # end
# #
# This validator can also block urls pointing to localhost or the local network to # 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. # protect against Server-side Request Forgery (SSRF), or check for the right port.
# #
# The available options are: # Configuration options:
# - protocols: Allowed protocols. Default: http and https # * <tt>message</tt> - A custom error message (default is: "must be a valid URL").
# - allow_localhost: Allow urls pointing to localhost. Default: true # * <tt>schemes</tt> - Array of URI schemes. Default: +['http', 'https']+
# - allow_local_network: Allow urls pointing to private network addresses. Default: true # * <tt>allow_localhost</tt> - Allow urls pointing to +localhost+. Default: +true+
# - ports: Allowed ports. Default: all. # * <tt>allow_local_network</tt> - Allow urls pointing to private network addresses. Default: +true+
# - enforce_user: Validate user format. Default: false # * <tt>allow_blank</tt> - Allow urls to be +blank+. Default: +false+
# - enforce_sanitization: Validate that there are no html/css/js tags. Default: false # * <tt>allow_nil</tt> - Allow urls to be +nil+. Default: +false+
# * <tt>ports</tt> - Allowed ports. Default: +all+.
# * <tt>enforce_user</tt> - Validate user format. Default: +false+
# * <tt>enforce_sanitization</tt> - Validate that there are no html/css/js tags. Default: +false+
# #
# Example: # Example:
# class User < ActiveRecord::Base # class User < ActiveRecord::Base
# validates :personal_url, url: { allow_localhost: false, allow_local_network: false} # validates :personal_url, addressable_url: { allow_localhost: false, allow_local_network: false}
# #
# validates :web_url, url: { ports: [80, 443] } # validates :web_url, addressable_url: { ports: [80, 443] }
# end # end
class UrlValidator < ActiveModel::EachValidator class AddressableUrlValidator < ActiveModel::EachValidator
DEFAULT_PROTOCOLS = %w(http https).freeze
attr_reader :record attr_reader :record
BLOCKER_VALIDATE_OPTIONS = {
schemes: %w(http https),
ports: [],
allow_localhost: true,
allow_local_network: true,
ascii_only: false,
enforce_user: false,
enforce_sanitization: false
}.freeze
DEFAULT_OPTIONS = BLOCKER_VALIDATE_OPTIONS.merge({
message: 'must be a valid URL'
}).freeze
def initialize(options)
options.reverse_merge!(DEFAULT_OPTIONS)
super(options)
end
def validate_each(record, attribute, value) def validate_each(record, attribute, value)
@record = record @record = record
unless value.present? unless value.present?
record.errors.add(attribute, 'must be a valid URL') record.errors.add(attribute, options.fetch(:message))
return return
end end
...@@ -63,36 +86,21 @@ class UrlValidator < ActiveModel::EachValidator ...@@ -63,36 +86,21 @@ class UrlValidator < ActiveModel::EachValidator
record.public_send("#{attribute}=", new_value) # rubocop:disable GitlabSecurity/PublicSend record.public_send("#{attribute}=", new_value) # rubocop:disable GitlabSecurity/PublicSend
end end
def default_options
# 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,
ascii_only: false,
enforce_user: false,
enforce_sanitization: false
}
end
def current_options def current_options
options = self.options.map do |option, value| options.map do |option, value|
[option, value.is_a?(Proc) ? value.call(record) : value] [option, value.is_a?(Proc) ? value.call(record) : value]
end.to_h end.to_h
default_options.merge(options)
end end
def blocker_args def blocker_args
current_options.slice(*default_options.keys).tap do |args| current_options.slice(*BLOCKER_VALIDATE_OPTIONS.keys).tap do |args|
if allow_setting_local_requests? if self.class.allow_setting_local_requests?
args[:allow_localhost] = args[:allow_local_network] = true args[:allow_localhost] = args[:allow_local_network] = true
end end
end end
end end
def allow_setting_local_requests? def self.allow_setting_local_requests?
# We cannot use Gitlab::CurrentSettings as ApplicationSetting itself # We cannot use Gitlab::CurrentSettings as ApplicationSetting itself
# uses UrlValidator to validate urls. This ends up in a cycle # uses UrlValidator to validate urls. This ends up in a cycle
# when Gitlab::CurrentSettings creates an ApplicationSetting which then # when Gitlab::CurrentSettings creates an ApplicationSetting which then
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
# PublicUrlValidator # PublicUrlValidator
# #
# Custom validator for URLs. This validator works like UrlValidator but # Custom validator for URLs. This validator works like AddressableUrlValidator but
# it blocks by default urls pointing to localhost or the local network. # it blocks by default urls pointing to localhost or the local network.
# #
# This validator accepts the same params UrlValidator does. # This validator accepts the same params UrlValidator does.
...@@ -12,17 +12,20 @@ ...@@ -12,17 +12,20 @@
# class User < ActiveRecord::Base # class User < ActiveRecord::Base
# validates :personal_url, public_url: true # validates :personal_url, public_url: true
# #
# validates :ftp_url, public_url: { protocols: %w(ftp) } # validates :ftp_url, public_url: { schemes: %w(ftp) }
# #
# validates :git_url, public_url: { allow_localhost: true, allow_local_network: true} # validates :git_url, public_url: { allow_localhost: true, allow_local_network: true}
# end # end
# #
class PublicUrlValidator < UrlValidator class PublicUrlValidator < AddressableUrlValidator
private DEFAULT_OPTIONS = {
allow_localhost: false,
allow_local_network: false
}.freeze
def default_options def initialize(options)
# By default block all urls pointing to localhost or the local network options.reverse_merge!(DEFAULT_OPTIONS)
super.merge(allow_localhost: false,
allow_local_network: false) super(options)
end end
end end
---
title: "Align UrlValidator to validate_url gem implementation"
merge_request: 27194
author: Horatiu Eugen Vlad
type: fixed
...@@ -18,8 +18,8 @@ class GeoNode < ApplicationRecord ...@@ -18,8 +18,8 @@ class GeoNode < ApplicationRecord
default_values url: ->(record) { record.class.current_node_url }, default_values url: ->(record) { record.class.current_node_url },
primary: false primary: false
validates :url, presence: true, uniqueness: { case_sensitive: false }, url: true validates :url, presence: true, uniqueness: { case_sensitive: false }, addressable_url: true
validates :internal_url, url: true, allow_blank: true, allow_nil: true validates :internal_url, addressable_url: true, allow_blank: true, allow_nil: true
validates :primary, uniqueness: { message: 'node already exists' }, if: :primary validates :primary, uniqueness: { message: 'node already exists' }, if: :primary
validates :enabled, if: :primary, acceptance: { message: 'Geo primary node cannot be disabled' } validates :enabled, if: :primary, acceptance: { message: 'Geo primary node cannot be disabled' }
......
...@@ -21,5 +21,5 @@ class GroupHook < ProjectHook ...@@ -21,5 +21,5 @@ class GroupHook < ProjectHook
belongs_to :group belongs_to :group
clear_validators! clear_validators!
validates :url, presence: true, url: true validates :url, presence: true, addressable_url: true
end end
...@@ -10,7 +10,7 @@ class GithubService < Service ...@@ -10,7 +10,7 @@ class GithubService < Service
delegate :api_url, :owner, :repository_name, to: :remote_project delegate :api_url, :owner, :repository_name, to: :remote_project
validates :token, presence: true, if: :activated? validates :token, presence: true, if: :activated?
validates :repository_url, url: true, allow_blank: true validates :repository_url, addressable_url: true, allow_blank: true
default_value_for :pipeline_events, true default_value_for :pipeline_events, true
......
...@@ -5,7 +5,7 @@ class JenkinsService < CiService ...@@ -5,7 +5,7 @@ class JenkinsService < CiService
before_update :reset_password before_update :reset_password
validates :jenkins_url, presence: true, url: true, if: :activated? validates :jenkins_url, presence: true, addressable_url: true, if: :activated?
validates :project_name, presence: true, if: :activated? validates :project_name, presence: true, if: :activated?
validates :username, presence: true, if: ->(service) { service.activated? && service.password_touched? && service.password.present? } validates :username, presence: true, if: ->(service) { service.activated? && service.password_touched? && service.password.present? }
......
...@@ -7,7 +7,7 @@ class SamlProvider < ApplicationRecord ...@@ -7,7 +7,7 @@ class SamlProvider < ApplicationRecord
has_many :identities has_many :identities
validates :group, presence: true, top_level_group: true validates :group, presence: true, top_level_group: true
validates :sso_url, presence: true, url: { protocols: %w(https), ascii_only: true } validates :sso_url, presence: true, addressable_url: { schemes: %w(https), ascii_only: true }
validates :certificate_fingerprint, presence: true, certificate_fingerprint: true validates :certificate_fingerprint, presence: true, certificate_fingerprint: true
after_initialize :set_defaults, if: :new_record? after_initialize :set_defaults, if: :new_record?
......
...@@ -8,7 +8,7 @@ module Gitlab ...@@ -8,7 +8,7 @@ module Gitlab
POST_METHOD = 'POST'.freeze POST_METHOD = 'POST'.freeze
INVALID_HTTP_METHOD = 'invalid. Only PUT and POST methods allowed.'.freeze INVALID_HTTP_METHOD = 'invalid. Only PUT and POST methods allowed.'.freeze
validates :url, url: true validates :url, addressable_url: true
validate do validate do
unless [PUT_METHOD, POST_METHOD].include?(http_method.upcase) unless [PUT_METHOD, POST_METHOD].include?(http_method.upcase)
......
...@@ -8,7 +8,7 @@ module Gitlab ...@@ -8,7 +8,7 @@ module Gitlab
BlockedUrlError = Class.new(StandardError) BlockedUrlError = Class.new(StandardError)
class << self class << self
def validate!(url, ports: [], protocols: [], allow_localhost: false, allow_local_network: true, ascii_only: false, enforce_user: false, enforce_sanitization: false) def validate!(url, ports: [], schemes: [], allow_localhost: false, allow_local_network: true, ascii_only: false, enforce_user: false, enforce_sanitization: false)
return true if url.nil? return true if url.nil?
# Param url can be a string, URI or Addressable::URI # Param url can be a string, URI or Addressable::URI
...@@ -20,7 +20,7 @@ module Gitlab ...@@ -20,7 +20,7 @@ module Gitlab
return true if internal?(uri) return true if internal?(uri)
port = get_port(uri) port = get_port(uri)
validate_protocol!(uri.scheme, protocols) validate_scheme!(uri.scheme, schemes)
validate_port!(port, ports) if ports.any? validate_port!(port, ports) if ports.any?
validate_user!(uri.user) if enforce_user validate_user!(uri.user) if enforce_user
validate_hostname!(uri.hostname) validate_hostname!(uri.hostname)
...@@ -85,9 +85,9 @@ module Gitlab ...@@ -85,9 +85,9 @@ module Gitlab
raise BlockedUrlError, "Only allowed ports are #{ports.join(', ')}, and any over 1024" raise BlockedUrlError, "Only allowed ports are #{ports.join(', ')}, and any over 1024"
end end
def validate_protocol!(protocol, protocols) def validate_scheme!(scheme, schemes)
if protocol.blank? || (protocols.any? && !protocols.include?(protocol)) if scheme.blank? || (schemes.any? && !schemes.include?(scheme))
raise BlockedUrlError, "Only allowed protocols are #{protocols.join(', ')}" raise BlockedUrlError, "Only allowed schemes are #{schemes.join(', ')}"
end end
end end
......
...@@ -79,7 +79,7 @@ describe Projects::MirrorsController do ...@@ -79,7 +79,7 @@ describe Projects::MirrorsController do
do_put(project, remote_mirrors_attributes: remote_mirror_attributes) do_put(project, remote_mirrors_attributes: remote_mirror_attributes)
expect(response).to redirect_to(project_settings_repository_path(project, anchor: 'js-push-remote-settings')) expect(response).to redirect_to(project_settings_repository_path(project, anchor: 'js-push-remote-settings'))
expect(flash[:alert]).to match(/Only allowed protocols are/) expect(flash[:alert]).to match(/Only allowed schemes are/)
end end
it 'does not create a RemoteMirror object' do it 'does not create a RemoteMirror object' do
......
...@@ -23,10 +23,10 @@ describe Gitlab::UrlBlocker do ...@@ -23,10 +23,10 @@ describe Gitlab::UrlBlocker do
expect(described_class.blocked_url?('https://gitlab.com:25/foo/foo.git', ports: ports)).to be true expect(described_class.blocked_url?('https://gitlab.com:25/foo/foo.git', ports: ports)).to be true
end end
it 'returns true for bad protocol' do it 'returns true for bad scheme' do
expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', protocols: ['https'])).to be false expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', schemes: ['https'])).to be false
expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git')).to be false expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git')).to be false
expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', protocols: ['http'])).to be true expect(described_class.blocked_url?('https://gitlab.com/foo/foo.git', schemes: ['http'])).to be true
end end
it 'returns true for bad protocol on configured web/SSH host and ports' do it 'returns true for bad protocol on configured web/SSH host and ports' do
......
...@@ -306,7 +306,22 @@ describe API::CommitStatuses do ...@@ -306,7 +306,22 @@ describe API::CommitStatuses do
it 'responds with bad request status and validation errors' do it 'responds with bad request status and validation errors' do
expect(response).to have_gitlab_http_status(400) expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['target_url']) expect(json_response['message']['target_url'])
.to include 'is blocked: Only allowed protocols are http, https' .to include 'is blocked: Only allowed schemes are http, https'
end
end
context 'when target URL is an unsupported scheme' do
before do
post api(post_url, developer), params: {
state: 'pending',
target_url: 'git://example.com'
}
end
it 'responds with bad request status and validation errors' do
expect(response).to have_gitlab_http_status(400)
expect(json_response['message']['target_url'])
.to include 'is blocked: Only allowed schemes are http, https'
end end
end end
end end
......
RSpec.shared_examples 'url validator examples' do |protocols| RSpec.shared_examples 'url validator examples' do |schemes|
let(:validator) { described_class.new(attributes: [:link_url], **options) } let(:validator) { described_class.new(attributes: [:link_url], **options) }
let!(:badge) { build(:badge, link_url: 'http://www.example.com') } let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
subject { validator.validate_each(badge, :link_url, badge.link_url) } subject { validator.validate(badge) }
describe '#validates_each' do describe '#validate' do
context 'with no options' do context 'with no options' do
let(:options) { {} } let(:options) { {} }
it "allows #{protocols.join(',')} protocols by default" do it "allows #{schemes.join(',')} schemes by default" do
expect(validator.send(:default_options)[:protocols]).to eq protocols expect(validator.options[:schemes]).to eq schemes
end end
it 'checks that the url structure is valid' do it 'checks that the url structure is valid' do
...@@ -17,25 +17,25 @@ RSpec.shared_examples 'url validator examples' do |protocols| ...@@ -17,25 +17,25 @@ RSpec.shared_examples 'url validator examples' do |protocols|
subject subject
expect(badge.errors.empty?).to be false expect(badge.errors).to be_present
end end
end end
context 'with protocols' do context 'with schemes' do
let(:options) { { protocols: %w[http] } } let(:options) { { schemes: %w(http) } }
it 'allows urls with the defined protocols' do it 'allows urls with the defined schemes' do
subject subject
expect(badge.errors.empty?).to be true expect(badge.errors).to be_empty
end end
it 'add error if the url protocol does not match the selected ones' do it 'add error if the url scheme does not match the selected ones' do
badge.link_url = 'https://www.example.com' badge.link_url = 'https://www.example.com'
subject subject
expect(badge.errors.empty?).to be false expect(badge.errors).to be_present
end end
end end
end end
......
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
require 'spec_helper' require 'spec_helper'
describe UrlValidator do describe AddressableUrlValidator do
let!(:badge) { build(:badge, link_url: 'http://www.example.com') } let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
subject { validator.validate_each(badge, :link_url, badge.link_url) } subject { validator.validate(badge) }
include_examples 'url validator examples', described_class::DEFAULT_PROTOCOLS include_examples 'url validator examples', described_class::DEFAULT_OPTIONS[:schemes]
describe 'validations' do describe 'validations' do
include_context 'invalid urls' include_context 'invalid urls'
...@@ -14,13 +14,13 @@ describe UrlValidator do ...@@ -14,13 +14,13 @@ describe UrlValidator do
let(:validator) { described_class.new(attributes: [:link_url]) } let(:validator) { described_class.new(attributes: [:link_url]) }
it 'returns error when url is nil' do it 'returns error when url is nil' do
expect(validator.validate_each(badge, :link_url, nil)).to be_nil expect(validator.validate_each(badge, :link_url, nil)).to be_falsey
expect(badge.errors.first[1]).to eq 'must be a valid URL' expect(badge.errors.first[1]).to eq validator.options.fetch(:message)
end end
it 'returns error when url is empty' do it 'returns error when url is empty' do
expect(validator.validate_each(badge, :link_url, '')).to be_nil expect(validator.validate_each(badge, :link_url, '')).to be_falsey
expect(badge.errors.first[1]).to eq 'must be a valid URL' expect(badge.errors.first[1]).to eq validator.options.fetch(:message)
end end
it 'does not allow urls with CR or LF characters' do it 'does not allow urls with CR or LF characters' do
...@@ -30,6 +30,17 @@ describe UrlValidator do ...@@ -30,6 +30,17 @@ describe UrlValidator do
end end
end end
end end
it 'provides all arguments to UrlBlock validate' do
expect(Gitlab::UrlBlocker)
.to receive(:validate!)
.with(badge.link_url, described_class::BLOCKER_VALIDATE_OPTIONS)
.and_return(true)
subject
expect(badge.errors).to be_empty
end
end end
context 'by default' do context 'by default' do
...@@ -40,7 +51,7 @@ describe UrlValidator do ...@@ -40,7 +51,7 @@ describe UrlValidator do
subject subject
expect(badge.errors.empty?).to be true expect(badge.errors).to be_empty
end end
it 'does not block urls pointing to the local network' do it 'does not block urls pointing to the local network' do
...@@ -48,7 +59,23 @@ describe UrlValidator do ...@@ -48,7 +59,23 @@ describe UrlValidator do
subject subject
expect(badge.errors.empty?).to be true expect(badge.errors).to be_empty
end
it 'does block nil urls' do
badge.link_url = nil
subject
expect(badge.errors).to be_present
end
it 'does block blank urls' do
badge.link_url = '\n\r \n'
subject
expect(badge.errors).to be_present
end end
it 'strips urls' do it 'strips urls' do
...@@ -67,6 +94,40 @@ describe UrlValidator do ...@@ -67,6 +94,40 @@ describe UrlValidator do
end end
end end
context 'when message is set' do
let(:message) { 'is blocked: test message' }
let(:validator) { described_class.new(attributes: [:link_url], allow_nil: false, message: message) }
it 'does block nil url with provided error message' do
expect(validator.validate_each(badge, :link_url, nil)).to be_falsey
expect(badge.errors.first[1]).to eq message
end
end
context 'when allow_nil is set to true' do
let(:validator) { described_class.new(attributes: [:link_url], allow_nil: true) }
it 'does not block nil urls' do
badge.link_url = nil
subject
expect(badge.errors).to be_empty
end
end
context 'when allow_blank is set to true' do
let(:validator) { described_class.new(attributes: [:link_url], allow_blank: true) }
it 'does not block blank urls' do
badge.link_url = "\n\r \n"
subject
expect(badge.errors).to be_empty
end
end
context 'when allow_localhost is set to false' do context 'when allow_localhost is set to false' do
let(:validator) { described_class.new(attributes: [:link_url], allow_localhost: false) } let(:validator) { described_class.new(attributes: [:link_url], allow_localhost: false) }
...@@ -75,7 +136,21 @@ describe UrlValidator do ...@@ -75,7 +136,21 @@ describe UrlValidator do
subject subject
expect(badge.errors.empty?).to be false expect(badge.errors).to be_present
end
context 'when allow_setting_local_requests is set to true' do
it 'does not block urls pointing to localhost' do
expect(described_class)
.to receive(:allow_setting_local_requests?)
.and_return(true)
badge.link_url = 'https://127.0.0.1'
subject
expect(badge.errors).to be_empty
end
end end
end end
...@@ -87,7 +162,21 @@ describe UrlValidator do ...@@ -87,7 +162,21 @@ describe UrlValidator do
subject subject
expect(badge.errors.empty?).to be false expect(badge.errors).to be_present
end
context 'when allow_setting_local_requests is set to true' do
it 'does not block urls pointing to local network' do
expect(described_class)
.to receive(:allow_setting_local_requests?)
.and_return(true)
badge.link_url = 'https://192.168.1.1'
subject
expect(badge.errors).to be_empty
end
end end
end end
...@@ -100,7 +189,7 @@ describe UrlValidator do ...@@ -100,7 +189,7 @@ describe UrlValidator do
it 'does not block any port' do it 'does not block any port' do
subject subject
expect(badge.errors.empty?).to be true expect(badge.errors).to be_empty
end end
end end
...@@ -110,7 +199,7 @@ describe UrlValidator do ...@@ -110,7 +199,7 @@ describe UrlValidator do
it 'blocks urls with a different port' do it 'blocks urls with a different port' do
subject subject
expect(badge.errors.empty?).to be false expect(badge.errors).to be_present
end end
end end
end end
...@@ -127,7 +216,7 @@ describe UrlValidator do ...@@ -127,7 +216,7 @@ describe UrlValidator do
subject subject
expect(badge.errors.empty?).to be false expect(badge.errors).to be_present
end end
end end
...@@ -139,7 +228,7 @@ describe UrlValidator do ...@@ -139,7 +228,7 @@ describe UrlValidator do
subject subject
expect(badge.errors.empty?).to be true expect(badge.errors).to be_empty
end end
end end
end end
...@@ -156,7 +245,7 @@ describe UrlValidator do ...@@ -156,7 +245,7 @@ describe UrlValidator do
subject subject
expect(badge.errors.empty?).to be false expect(badge.errors).to be_present
end end
end end
...@@ -168,7 +257,7 @@ describe UrlValidator do ...@@ -168,7 +257,7 @@ describe UrlValidator do
subject subject
expect(badge.errors.empty?).to be true expect(badge.errors).to be_empty
end end
end end
end end
...@@ -191,7 +280,7 @@ describe UrlValidator do ...@@ -191,7 +280,7 @@ describe UrlValidator do
subject subject
expect(badge.errors.empty?).to be false expect(badge.errors).to be_present
end end
it 'prevents unsafe internal urls' do it 'prevents unsafe internal urls' do
...@@ -199,7 +288,7 @@ describe UrlValidator do ...@@ -199,7 +288,7 @@ describe UrlValidator do
subject subject
expect(badge.errors.empty?).to be false expect(badge.errors).to be_present
end end
it 'allows safe urls' do it 'allows safe urls' do
...@@ -207,7 +296,7 @@ describe UrlValidator do ...@@ -207,7 +296,7 @@ describe UrlValidator do
subject subject
expect(badge.errors.empty?).to be true expect(badge.errors).to be_empty
end end
end end
...@@ -219,7 +308,7 @@ describe UrlValidator do ...@@ -219,7 +308,7 @@ describe UrlValidator do
subject subject
expect(badge.errors.empty?).to be true expect(badge.errors).to be_empty
end end
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe PublicUrlValidator do describe PublicUrlValidator do
include_examples 'url validator examples', described_class::DEFAULT_PROTOCOLS include_examples 'url validator examples', AddressableUrlValidator::DEFAULT_OPTIONS[:schemes]
context 'by default' do context 'by default' do
let(:validator) { described_class.new(attributes: [:link_url]) } let(:validator) { described_class.new(attributes: [:link_url]) }
let!(:badge) { build(:badge, link_url: 'http://www.example.com') } let!(:badge) { build(:badge, link_url: 'http://www.example.com') }
subject { validator.validate_each(badge, :link_url, badge.link_url) } subject { validator.validate(badge) }
it 'blocks urls pointing to localhost' do it 'blocks urls pointing to localhost' do
badge.link_url = 'https://127.0.0.1' badge.link_url = 'https://127.0.0.1'
subject subject
expect(badge.errors.empty?).to be false expect(badge.errors).to be_present
end end
it 'blocks urls pointing to the local network' do it 'blocks urls pointing to the local network' do
...@@ -22,7 +22,7 @@ describe PublicUrlValidator do ...@@ -22,7 +22,7 @@ describe PublicUrlValidator do
subject subject
expect(badge.errors.empty?).to be false expect(badge.errors).to be_present
end end
end end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment