Commit 6cb750a2 authored by Yorick Peterse's avatar Yorick Peterse

Merge dev.gitlab.org master into GitLab.com master

parents 51a66a58 3dcf3cfd
......@@ -478,6 +478,24 @@ entry.
- Removes EE differences for environment_item.vue.
## 11.9.12 (2019-05-30)
### Security (12 changes, 1 of them is from the community)
- Protect Gitlab::HTTP against DNS rebinding attack.
- Fix project visibility level validation. (Peter Marko)
- Update Knative version.
- Add DNS rebinding protection settings.
- Prevent XSS injection in note imports.
- Prevent invalid branch for merge request.
- Filter relative links in wiki for XSS.
- Fix confidential issue label disclosure on milestone view.
- Fix url redaction for issue links.
- Resolve: Milestones leaked via search API.
- Prevent bypass of restriction disabling web password sign in.
- Hide confidential issue title on unsubscribe for anonymous users.
## 11.9.10 (2019-04-26)
### Security (5 changes)
......
# frozen_string_literal: true
module ImportUrlParams
def import_url_params
{ import_url: import_params_to_full_url(params[:project]) }
end
def import_params_to_full_url(params)
Gitlab::UrlSanitizer.new(
params[:import_url],
credentials: {
user: params[:import_url_user],
password: params[:import_url_password]
}
).full_url
end
end
......@@ -26,16 +26,22 @@ module MilestoneActions
end
end
# rubocop:disable Gitlab/ModuleWithInstanceVariables
def labels
respond_to do |format|
format.html { redirect_to milestone_redirect_path }
format.json do
milestone_labels = @milestone.issue_labels_visible_by_user(current_user)
render json: tabs_json("shared/milestones/_labels_tab", {
labels: @milestone.labels.map { |label| label.present(issuable_subject: @milestone.parent) } # rubocop:disable Gitlab/ModuleWithInstanceVariables
labels: milestone_labels.map do |label|
label.present(issuable_subject: @milestone.parent)
end
})
end
end
end
# rubocop:enable Gitlab/ModuleWithInstanceVariables
private
......
......@@ -2,6 +2,7 @@
class Projects::ImportsController < Projects::ApplicationController
include ContinueParams
include ImportUrlParams
# Authorize
before_action :authorize_admin_project!
......@@ -67,10 +68,12 @@ class Projects::ImportsController < Projects::ApplicationController
end
def import_params_attributes
[:import_url]
[]
end
def import_params
params.require(:project).permit(import_params_attributes)
params.require(:project)
.permit(import_params_attributes)
.merge(import_url_params)
end
end
......@@ -7,6 +7,7 @@ class ProjectsController < Projects::ApplicationController
include PreviewMarkdown
include SendFileUpload
include RecordUserLastActivity
include ImportUrlParams
prepend_before_action(only: [:show]) { authenticate_sessionless_user!(:rss) }
......@@ -333,6 +334,7 @@ class ProjectsController < Projects::ApplicationController
def project_params(attributes: [])
params.require(:project)
.permit(project_params_attributes + attributes)
.merge(import_url_params)
end
def project_params_attributes
......
......@@ -18,6 +18,7 @@ class SessionsController < Devise::SessionsController
prepend_before_action :store_redirect_uri, only: [:new]
prepend_before_action :ldap_servers, only: [:new, :create]
prepend_before_action :require_no_authentication_without_flash, only: [:new, :create]
prepend_before_action :ensure_password_authentication_enabled!, if: :password_based_login?, only: [:create]
before_action :auto_sign_in_with_provider, only: [:new]
before_action :load_recaptcha
......@@ -138,6 +139,14 @@ class SessionsController < Devise::SessionsController
end
# rubocop: enable CodeReuse/ActiveRecord
def ensure_password_authentication_enabled!
render_403 unless Gitlab::CurrentSettings.password_authentication_enabled_for_web?
end
def password_based_login?
user_params[:login].present? || user_params[:password].present?
end
def user_params
params.require(:user).permit(:login, :password, :remember_me, :otp_attempt, :device_response)
end
......
......@@ -160,6 +160,7 @@ module ApplicationSettingsHelper
:akismet_api_key,
:akismet_enabled,
:allow_local_requests_from_hooks_and_services,
:dns_rebinding_protection_enabled,
:archive_builds_in_human_readable,
:authorized_keys_enabled,
:auto_devops_enabled,
......
......@@ -100,4 +100,8 @@ module NotificationsHelper
css_class: "icon notifications-icon js-notifications-icon"
)
end
def show_unsubscribe_title?(noteable)
can?(current_user, "read_#{noteable.to_ability_name}".to_sym, noteable)
end
end
......@@ -21,6 +21,7 @@ module ApplicationSettingImplementation
after_sign_up_text: nil,
akismet_enabled: false,
allow_local_requests_from_hooks_and_services: false,
dns_rebinding_protection_enabled: true,
authorized_keys_enabled: true, # TODO default to false if the instance is configured to use AuthorizedKeysCommand
container_registry_token_expire_delay: 5,
default_artifacts_expire_in: '30 days',
......
......@@ -589,6 +589,8 @@ class MergeRequest < ApplicationRecord
return
end
[:source_branch, :target_branch].each { |attr| validate_branch_name(attr) }
if opened?
similar_mrs = target_project
.merge_requests
......@@ -609,6 +611,16 @@ class MergeRequest < ApplicationRecord
end
end
def validate_branch_name(attr)
return unless changes_include?(attr)
branch = read_attribute(attr)
return unless branch
errors.add(attr) unless Gitlab::GitRefValidator.validate_merge_request_branch(branch)
end
def validate_target_project
return true if target_project.merge_requests_enabled?
......
......@@ -407,6 +407,7 @@ class Project < ApplicationRecord
scope :with_builds_enabled, -> { with_feature_enabled(:builds) }
scope :with_issues_enabled, -> { with_feature_enabled(:issues) }
scope :with_issues_available_for_user, ->(current_user) { with_feature_available_for_user(:issues, current_user) }
scope :with_merge_requests_available_for_user, ->(current_user) { with_feature_available_for_user(:merge_requests, current_user) }
scope :with_merge_requests_enabled, -> { with_feature_enabled(:merge_requests) }
scope :with_remote_mirrors, -> { joins(:remote_mirrors).where(remote_mirrors: { enabled: true }).distinct }
......@@ -597,6 +598,17 @@ class Project < ApplicationRecord
def group_ids
joins(:namespace).where(namespaces: { type: 'Group' }).select(:namespace_id)
end
# Returns ids of projects with milestones available for given user
#
# Used on queries to find milestones which user can see
# For example: Milestone.where(project_id: ids_with_milestone_available_for(user))
def ids_with_milestone_available_for(user)
with_issues_enabled = with_issues_available_for_user(user).select(:id)
with_merge_requests_enabled = with_merge_requests_available_for_user(user).select(:id)
from_union([with_issues_enabled, with_merge_requests_enabled]).select(:id)
end
end
def all_pipelines
......
......@@ -8,4 +8,12 @@
= f.label :allow_local_requests_from_hooks_and_services, class: 'form-check-label' do
Allow requests to the local network from hooks and services
.form-group
.form-check
= f.check_box :dns_rebinding_protection_enabled, class: 'form-check-input'
= f.label :dns_rebinding_protection_enabled, class: 'form-check-label' do
= _('Enforce DNS rebinding attack protection')
%span.form-text.text-muted
= _('Resolves IP addresses once and uses them to submit requests')
= f.submit 'Save changes', class: "btn btn-success"
- noteable = @sent_notification.noteable
- noteable_type = @sent_notification.noteable_type.titleize.downcase
- noteable_text = %(#{noteable.title} (#{noteable.to_reference}))
- noteable_text = show_unsubscribe_title?(noteable) ? %(#{noteable.title} (#{noteable.to_reference})) : %(#{noteable.to_reference})
- page_title _("Unsubscribe"), noteable_text, noteable_type.pluralize, @sent_notification.project.full_name
%h3.page-title
......
- ci_cd_only = local_assigns.fetch(:ci_cd_only, false)
- import_url = Gitlab::UrlSanitizer.new(f.object.import_url)
.form-group.import-url-data
= f.label :import_url, class: 'label-bold' do
%span
= _('Git repository URL')
.import-url-data
.form-group
= f.label :import_url, class: 'label-bold' do
%span
= _('Git repository URL')
= f.text_field :import_url, value: import_url.sanitized_url,
autocomplete: 'off', class: 'form-control', placeholder: 'https://gitlab.company.com/group/project.git', required: true
= f.text_field :import_url, autocomplete: 'off', class: 'form-control', placeholder: 'https://username:password@gitlab.company.com/group/project.git', required: true
.row
.form-group.col-md-6
= f.label :import_url_user, class: 'label-bold' do
%span
= _('Username (optional)')
= f.text_field :import_url_user, value: import_url.user, class: 'form-control', required: false, autocomplete: 'new-password'
.form-group.col-md-6
= f.label :import_url_password, class: 'label-bold' do
%span
= _('Password (optional)')
= f.password_field :import_url_password, class: 'form-control', required: false, autocomplete: 'new-password'
.info-well.prepend-top-20
.well-segment
......@@ -13,7 +28,7 @@
%li
= _('The repository must be accessible over <code>http://</code>, <code>https://</code> or <code>git://</code>.').html_safe
%li
= _('If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>.').html_safe
= _('If your HTTP repository is not publicly accessible, add your credentials.')
%li
= import_will_timeout_message(ci_cd_only)
%li
......
---
title: Protect Gitlab::HTTP against DNS rebinding attack
merge_request:
author:
type: security
---
title: Prevent invalid branch for merge request
merge_request:
author:
type: security
---
title: Fix confidential issue label disclosure on milestone view
merge_request:
author:
type: security
---
title: Fix url redaction for issue links
merge_request:
author:
type: security
---
title: 'Resolve: Milestones leaked via search API'
merge_request:
author:
type: security
---
title: Add extra fields for handling basic auth on import by url page
merge_request:
author:
type: security
---
title: Prevent bypass of restriction disabling web password sign in
merge_request:
author:
type: security
---
title: Hide confidential issue title on unsubscribe for anonymous users
merge_request:
author:
type: security
......@@ -2,14 +2,14 @@
# This monkey patches the HTTParty used in https://github.com/hipchat/hipchat-rb.
module HipChat
class Client
connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter
connection_adapter ::Gitlab::HTTPConnectionAdapter
end
class Room
connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter
connection_adapter ::Gitlab::HTTPConnectionAdapter
end
class User
connection_adapter ::Gitlab::ProxyHTTPConnectionAdapter
connection_adapter ::Gitlab::HTTPConnectionAdapter
end
end
# This override allows passing `@hostname_override` to the SNI protocol,
# which is used to lookup the correct SSL certificate in the
# request handshake process.
#
# Given we've forced the HTTP request to be sent to the resolved
# IP address in a few scenarios (e.g.: `Gitlab::HTTP` through
# `Gitlab::UrlBlocker.validate!`), we need to provide the _original_
# hostname via SNI in order to have a clean connection setup.
#
# This is ultimately needed in order to avoid DNS rebinding attacks
# through HTTP requests.
#
class OpenSSL::SSL::SSLContext
attr_accessor :hostname_override
end
class OpenSSL::SSL::SSLSocket
module HostnameOverride
# rubocop: disable Gitlab/ModuleWithInstanceVariables
def hostname=(hostname)
super(@context.hostname_override || hostname)
end
def post_connection_check(hostname)
super(@context.hostname_override || hostname)
end
# rubocop: enable Gitlab/ModuleWithInstanceVariables
end
prepend HostnameOverride
end
class Net::HTTP
attr_accessor :hostname_override
SSL_IVNAMES << :@hostname_override
SSL_ATTRIBUTES << :hostname_override
module HostnameOverride
def addr_port
return super unless hostname_override
addr = hostname_override
default_port = use_ssl? ? Net::HTTP.https_default_port : Net::HTTP.http_default_port
default_port == port ? addr : "#{addr}:#{port}"
end
end
prepend HostnameOverride
end
# frozen_string_literal: true
# See http://doc.gitlab.com/ce/development/migration_style_guide.html
# for more information on how to write migrations for GitLab.
class AddDnsRebindingProtectionEnabledToApplicationSettings < ActiveRecord::Migration[5.1]
include Gitlab::Database::MigrationHelpers
DOWNTIME = false
disable_ddl_transaction!
def up
add_column_with_default(:application_settings, :dns_rebinding_protection_enabled,
:boolean,
default: true,
allow_null: false)
end
def down
remove_column(:application_settings, :dns_rebinding_protection_enabled)
end
end
......@@ -193,6 +193,7 @@ ActiveRecord::Schema.define(version: 20190530154715) do
t.integer "elasticsearch_replicas", default: 1, null: false
t.text "encrypted_lets_encrypt_private_key"
t.text "encrypted_lets_encrypt_private_key_iv"
t.boolean "dns_rebinding_protection_enabled", default: true, null: false
t.index ["usage_stats_set_by_user_id"], name: "index_application_settings_on_usage_stats_set_by_user_id", using: :btree
end
......
......@@ -70,8 +70,11 @@ module Banzai
# Build the raw <a> tag just with a link as href and content if
# it's originally a link pattern. We shouldn't return a plain text href.
original_link =
if link_reference == 'true' && href = original_content
%(<a href="#{href}">#{href}</a>)
if link_reference == 'true'
href = node.attr('href')
content = original_content
%(<a href="#{href}">#{content}</a>)
end
# The reference should be replaced by the original link's content,
......
......@@ -40,6 +40,7 @@ module Gitlab
SUBDOMAIN_REGEX = %r{\Ahttps://[a-z0-9]+\.gitlab\.com\z}.freeze
VERSION = File.read(root.join("VERSION")).strip.freeze
INSTALLATION_TYPE = File.read(root.join("INSTALLATION_TYPE")).strip.freeze
HTTP_PROXY_ENV_VARS = %w(http_proxy https_proxy HTTP_PROXY HTTPS_PROXY).freeze
def self.com?
# Check `gl_subdomain?` as well to keep parity with gitlab.com
......@@ -66,6 +67,10 @@ module Gitlab
end
end
def self.http_proxy_env?
HTTP_PROXY_ENV_VARS.any? { |name| ENV[name] }
end
def self.process_name
return 'sidekiq' if Sidekiq.server?
return 'console' if defined?(Rails::Console)
......
......@@ -5,12 +5,15 @@
module Gitlab
module GitRefValidator
extend self
EXPANDED_PREFIXES = %w[refs/heads/ refs/remotes/].freeze
DISALLOWED_PREFIXES = %w[-].freeze
# Validates a given name against the git reference specification
#
# Returns true for a valid reference name, false otherwise
def validate(ref_name)
not_allowed_prefixes = %w(refs/heads/ refs/remotes/ -)
return false if ref_name.start_with?(*not_allowed_prefixes)
return false if ref_name.start_with?(*(EXPANDED_PREFIXES + DISALLOWED_PREFIXES))
return false if ref_name == 'HEAD'
begin
......@@ -19,5 +22,21 @@ module Gitlab
return false
end
end
def validate_merge_request_branch(ref_name)
return false if ref_name.start_with?(*DISALLOWED_PREFIXES)
expanded_name = if ref_name.start_with?(*EXPANDED_PREFIXES)
ref_name
else
"refs/heads/#{ref_name}"
end
begin
Rugged::Reference.valid_name?(expanded_name)
rescue ArgumentError
return false
end
end
end
end
......@@ -18,7 +18,7 @@ module Gitlab
include HTTParty # rubocop:disable Gitlab/HTTParty
connection_adapter ProxyHTTPConnectionAdapter
connection_adapter HTTPConnectionAdapter
def self.perform_request(http_method, path, options, &block)
super
......
......@@ -10,17 +10,19 @@
#
# This option will take precedence over the global setting.
module Gitlab
class ProxyHTTPConnectionAdapter < HTTParty::ConnectionAdapter
class HTTPConnectionAdapter < HTTParty::ConnectionAdapter
def connection
unless allow_local_requests?
begin
Gitlab::UrlBlocker.validate!(uri, allow_local_network: false)
rescue Gitlab::UrlBlocker::BlockedUrlError => e
raise Gitlab::HTTP::BlockedUrlError, "URL '#{uri}' is blocked: #{e.message}"
end
begin
@uri, hostname = Gitlab::UrlBlocker.validate!(uri, allow_local_network: allow_local_requests?,
allow_localhost: allow_local_requests?,
dns_rebind_protection: dns_rebind_protection?)
rescue Gitlab::UrlBlocker::BlockedUrlError => e
raise Gitlab::HTTP::BlockedUrlError, "URL '#{uri}' is blocked: #{e.message}"
end
super
super.tap do |http|
http.hostname_override = hostname if hostname
end
end
private
......@@ -29,6 +31,12 @@ module Gitlab
options.fetch(:allow_local_requests, allow_settings_local_requests?)
end
def dns_rebind_protection?
return false if Gitlab.http_proxy_env?
Gitlab::CurrentSettings.dns_rebinding_protection_enabled?
end
def allow_settings_local_requests?
Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services?
end
......
......@@ -138,6 +138,12 @@ module Gitlab
project
end
def filter_milestones_by_project(milestones)
return Milestone.none unless Ability.allowed?(@current_user, :read_milestone, @project)
milestones.where(project_id: project.id) # rubocop: disable CodeReuse/ActiveRecord
end
def repository_project_ref
@repository_project_ref ||= repository_ref || project.default_branch
end
......
......@@ -103,9 +103,11 @@ module Gitlab
# rubocop: disable CodeReuse/ActiveRecord
def milestones
milestones = Milestone.where(project_id: project_ids_relation)
milestones = milestones.search(query)
milestones.reorder('milestones.updated_at DESC')
milestones = Milestone.search(query)
milestones = filter_milestones_by_project(milestones)
milestones.reorder('updated_at DESC')
end
# rubocop: enable CodeReuse/ActiveRecord
......@@ -123,6 +125,26 @@ module Gitlab
'projects'
end
# Filter milestones by authorized projects.
# For performance reasons project_id is being plucked
# to be used on a smaller query.
#
# rubocop: disable CodeReuse/ActiveRecord
def filter_milestones_by_project(milestones)
project_ids =
milestones.where(project_id: project_ids_relation)
.select(:project_id).distinct
.pluck(:project_id)
return Milestone.none if project_ids.nil?
authorized_project_ids_relation =
Project.where(id: project_ids).ids_with_milestone_available_for(current_user)
milestones.where(project_id: authorized_project_ids_relation)
end
# rubocop: enable CodeReuse/ActiveRecord
# rubocop: disable CodeReuse/ActiveRecord
def project_ids_relation
limit_projects.select(:id).reorder(nil)
......
......@@ -8,38 +8,68 @@ module Gitlab
BlockedUrlError = Class.new(StandardError)
class << self
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?
# Validates the given url according to the constraints specified by arguments.
#
# ports - Raises error if the given URL port does is not between given ports.
# allow_localhost - Raises error if URL resolves to a localhost IP address and argument is true.
# allow_local_network - Raises error if URL resolves to a link-local address and argument is true.
# ascii_only - Raises error if URL has unicode characters and argument is true.
# enforce_user - Raises error if URL user doesn't start with alphanumeric characters and argument is true.
# enforce_sanitization - Raises error if URL includes any HTML/CSS/JS tags and argument is true.
#
# Returns an array with [<uri>, <original-hostname>].
# rubocop:disable Metrics/CyclomaticComplexity
# rubocop:disable Metrics/ParameterLists
def validate!(
url,
ports: [],
schemes: [],
allow_localhost: false,
allow_local_network: true,
ascii_only: false,
enforce_user: false,
enforce_sanitization: false,
dns_rebind_protection: true)
# rubocop:enable Metrics/CyclomaticComplexity
# rubocop:enable Metrics/ParameterLists
return [nil, nil] if url.nil?
# Param url can be a string, URI or Addressable::URI
uri = parse_url(url)
validate_html_tags!(uri) if enforce_sanitization
# Allow imports from the GitLab instance itself but only from the configured ports
return true if internal?(uri)
hostname = uri.hostname
port = get_port(uri)
validate_scheme!(uri.scheme, schemes)
validate_port!(port, ports) if ports.any?
validate_user!(uri.user) if enforce_user
validate_hostname!(uri.hostname)
validate_unicode_restriction!(uri) if ascii_only
unless internal?(uri)
validate_scheme!(uri.scheme, schemes)
validate_port!(port, ports) if ports.any?
validate_user!(uri.user) if enforce_user
validate_hostname!(hostname)
validate_unicode_restriction!(uri) if ascii_only
end
begin
addrs_info = Addrinfo.getaddrinfo(uri.hostname, port, nil, :STREAM).map do |addr|
addrs_info = Addrinfo.getaddrinfo(hostname, port, nil, :STREAM).map do |addr|
addr.ipv6_v4mapped? ? addr.ipv6_to_ipv4 : addr
end
rescue SocketError
return true
return [uri, nil]
end
protected_uri_with_hostname = enforce_uri_hostname(addrs_info, uri, hostname, dns_rebind_protection)
# Allow url from the GitLab instance itself but only for the configured hostname and ports
return protected_uri_with_hostname if internal?(uri)
validate_localhost!(addrs_info) unless allow_localhost
validate_loopback!(addrs_info) unless allow_localhost
validate_local_network!(addrs_info) unless allow_local_network
validate_link_local!(addrs_info) unless allow_local_network
true
protected_uri_with_hostname
end
def blocked_url?(*args)
......@@ -52,6 +82,25 @@ module Gitlab
private
# Returns the given URI with IP address as hostname and the original hostname respectively
# in an Array.
#
# It checks whether the resolved IP address matches with the hostname. If not, it changes
# the hostname to the resolved IP address.
#
# The original hostname is used to validate the SSL, given in that scenario
# we'll be making the request to the IP address, instead of using the hostname.
def enforce_uri_hostname(addrs_info, uri, hostname, dns_rebind_protection)
address = addrs_info.first
ip_address = address&.ip_address
return [uri, nil] unless dns_rebind_protection && ip_address && ip_address != hostname
uri = uri.dup
uri.hostname = ip_address
[uri, hostname]
end
def get_port(uri)
uri.port || uri.default_port
end
......
......@@ -47,6 +47,10 @@ module Gitlab
@credentials ||= { user: @url.user.presence, password: @url.password.presence }
end
def user
credentials[:user]
end
def full_url
@full_url ||= generate_full_url.to_s
end
......
......@@ -3850,6 +3850,9 @@ msgstr ""
msgid "Ends at (UTC)"
msgstr ""
msgid "Enforce DNS rebinding attack protection"
msgstr ""
msgid "Enter at least three characters to search"
msgstr ""
......@@ -5147,7 +5150,7 @@ msgstr ""
msgid "If you lose your recovery codes you can generate new ones, invalidating all previous codes."
msgstr ""
msgid "If your HTTP repository is not publicly accessible, add authentication information to the URL: <code>https://username:password@gitlab.company.com/group/project.git</code>."
msgid "If your HTTP repository is not publicly accessible, add your credentials."
msgstr ""
msgid "ImageDiffViewer|2-up"
......@@ -6925,6 +6928,9 @@ msgstr ""
msgid "Password"
msgstr ""
msgid "Password (optional)"
msgstr ""
msgid "Password authentication is unavailable."
msgstr ""
......@@ -8472,6 +8478,9 @@ msgstr ""
msgid "Resolved by %{resolvedByName}"
msgstr ""
msgid "Resolves IP addresses once and uses them to submit requests"
msgstr ""
msgid "Response metrics (AWS ELB)"
msgstr ""
......@@ -11152,6 +11161,9 @@ msgstr ""
msgid "UserProfile|Your projects can be available publicly, internally, or privately, at your choice."
msgstr ""
msgid "Username (optional)"
msgstr ""
msgid "Username is already taken."
msgstr ""
......
# frozen_string_literal: true
require 'spec_helper'
describe ImportUrlParams do
let(:import_url_params) do
controller = OpenStruct.new(params: params).extend(described_class)
controller.import_url_params
end
context 'url and password separately provided' do
let(:params) do
ActionController::Parameters.new(project: {
import_url: 'https://url.com',
import_url_user: 'user', import_url_password: 'password'
})
end
describe '#import_url_params' do
it 'returns hash with import_url' do
expect(import_url_params).to eq(
import_url: "https://user:password@url.com"
)
end
end
end
context 'url with provided empty credentials' do
let(:params) do
ActionController::Parameters.new(project: {
import_url: 'https://user:password@url.com',
import_url_user: '', import_url_password: ''
})
end
describe '#import_url_params' do
it 'does not change the url' do
expect(import_url_params).to eq(
import_url: "https://user:password@url.com"
)
end
end
end
end
......@@ -3,6 +3,8 @@
require 'spec_helper'
describe Projects::Ci::LintsController do
include StubRequests
let(:project) { create(:project, :repository) }
let(:user) { create(:user) }
......@@ -70,7 +72,7 @@ describe Projects::Ci::LintsController do
context 'with a valid gitlab-ci.yml' do
before do
WebMock.stub_request(:get, remote_file_path).to_return(body: remote_file_content)
stub_full_request(remote_file_path).to_return(body: remote_file_content)
project.add_developer(user)
post :create, params: { namespace_id: project.namespace, project_id: project, content: content }
......
......@@ -122,4 +122,19 @@ describe Projects::ImportsController do
end
end
end
describe 'POST #create' do
let(:params) { { import_url: 'https://github.com/vim/vim.git', import_url_user: 'user', import_url_password: 'password' } }
let(:project) { create(:project) }
before do
allow(RepositoryImportWorker).to receive(:perform_async)
post :create, params: { project: params, namespace_id: project.namespace.to_param, project_id: project }
end
it 'sets import_url to the project' do
expect(project.reload.import_url).to eq('https://user:password@github.com/vim/vim.git')
end
end
end
......@@ -175,6 +175,40 @@ describe Projects::MilestonesController do
end
end
describe '#labels' do
render_views
context 'as json' do
let!(:guest) { create(:user, username: 'guest1') }
let!(:group) { create(:group, :public) }
let!(:project) { create(:project, :public, group: group) }
let!(:label) { create(:label, title: 'test_label_on_private_issue', project: project) }
let!(:confidential_issue) { create(:labeled_issue, confidential: true, project: project, milestone: milestone, labels: [label]) }
it 'does not render labels of private issues if user has no access' do
sign_in(guest)
get :labels, params: { namespace_id: group.id, project_id: project.id, id: milestone.iid }, format: :json
expect(response).to have_gitlab_http_status(200)
expect(response.content_type).to eq 'application/json'
expect(json_response['html']).not_to include(label.title)
end
it 'does render labels of private issues if user has access' do
sign_in(user)
get :labels, params: { namespace_id: group.id, project_id: project.id, id: milestone.iid }, format: :json
expect(response).to have_gitlab_http_status(200)
expect(response.content_type).to eq 'application/json'
expect(json_response['html']).to include(label.title)
end
end
end
context 'promotion succeeds' do
before do
group.add_developer(user)
......
......@@ -4,15 +4,31 @@ require 'rails_helper'
describe SentNotificationsController do
let(:user) { create(:user) }
let(:project) { create(:project) }
let(:sent_notification) { create(:sent_notification, project: project, noteable: issue, recipient: user) }
let(:project) { create(:project, :public) }
let(:private_project) { create(:project, :private) }
let(:sent_notification) { create(:sent_notification, project: target_project, noteable: noteable, recipient: user) }
let(:issue) do
create(:issue, project: project, author: user) do |issue|
issue.subscriptions.create(user: user, project: project, subscribed: true)
create(:issue, project: target_project) do |issue|
issue.subscriptions.create(user: user, project: target_project, subscribed: true)
end
end
let(:confidential_issue) do
create(:issue, project: target_project, confidential: true) do |issue|
issue.subscriptions.create(user: user, project: target_project, subscribed: true)
end
end
let(:merge_request) do
create(:merge_request, source_project: target_project, target_project: target_project) do |mr|
mr.subscriptions.create(user: user, project: target_project, subscribed: true)
end
end
let(:noteable) { issue }
let(:target_project) { project }
describe 'GET unsubscribe' do
context 'when the user is not logged in' do
context 'when the force param is passed' do
......@@ -34,20 +50,93 @@ describe SentNotificationsController do
end
context 'when the force param is not passed' do
render_views
before do
get(:unsubscribe, params: { id: sent_notification.reply_key })
end
it 'does not unsubscribe the user' do
expect(issue.subscribed?(user, project)).to be_truthy
shared_examples 'unsubscribing as anonymous' do
it 'does not unsubscribe the user' do
expect(noteable.subscribed?(user, target_project)).to be_truthy
end
it 'does not set the flash message' do
expect(controller).not_to set_flash[:notice]
end
it 'renders unsubscribe page' do
expect(response.status).to eq(200)
expect(response).to render_template :unsubscribe
end
end
it 'does not set the flash message' do
expect(controller).not_to set_flash[:notice]
context 'when project is public' do
context 'when unsubscribing from issue' do
let(:noteable) { issue }
it 'shows issue title' do
expect(response.body).to include(issue.title)
end
it_behaves_like 'unsubscribing as anonymous'
end
context 'when unsubscribing from confidential issue' do
let(:noteable) { confidential_issue }
it 'does not show issue title' do
expect(response.body).not_to include(confidential_issue.title)
expect(response.body).to include(confidential_issue.to_reference)
end
it_behaves_like 'unsubscribing as anonymous'
end
context 'when unsubscribing from merge request' do
let(:noteable) { merge_request }
it 'shows merge request title' do
expect(response.body).to include(merge_request.title)
end
it_behaves_like 'unsubscribing as anonymous'
end
end
it 'redirects to the login page' do
expect(response).to render_template :unsubscribe
context 'when project is not public' do
let(:target_project) { private_project }
context 'when unsubscribing from issue' do
let(:noteable) { issue }
it 'shows issue title' do
expect(response.body).not_to include(issue.title)
end
it_behaves_like 'unsubscribing as anonymous'
end
context 'when unsubscribing from confidential issue' do
let(:noteable) { confidential_issue }
it 'does not show issue title' do
expect(response.body).not_to include(confidential_issue.title)
expect(response.body).to include(confidential_issue.to_reference)
end
it_behaves_like 'unsubscribing as anonymous'
end
context 'when unsubscribing from merge request' do
let(:noteable) { merge_request }
it 'shows merge request title' do
expect(response.body).not_to include(merge_request.title)
end
it_behaves_like 'unsubscribing as anonymous'
end
end
end
end
......
......@@ -58,7 +58,26 @@ describe SessionsController do
it 'authenticates user correctly' do
post(:create, params: { user: user_params })
expect(subject.current_user). to eq user
expect(subject.current_user).to eq user
end
context 'with password authentication disabled' do
before do
stub_application_setting(password_authentication_enabled_for_web: false)
end
it 'does not sign in the user' do
post(:create, params: { user: user_params })
expect(@request.env['warden']).not_to be_authenticated
expect(subject.current_user).to be_nil
end
it 'returns status 403' do
post(:create, params: { user: user_params })
expect(response.status).to eq 403
end
end
it 'creates an audit log record' do
......@@ -153,6 +172,19 @@ describe SessionsController do
end
end
context 'with password authentication disabled' do
before do
stub_application_setting(password_authentication_enabled_for_web: false)
end
it 'allows 2FA stage of non-password login' do
authenticate_2fa(otp_attempt: user.current_otp)
expect(@request.env['warden']).to be_authenticated
expect(subject.current_user).to eq user
end
end
##
# See #14900 issue
#
......
......@@ -332,16 +332,19 @@ describe 'Admin updates settings' do
end
context 'Network page' do
it 'Enable outbound requests' do
it 'Changes Outbound requests settings' do
visit network_admin_application_settings_path
page.within('.as-outbound') do
check 'Allow requests to the local network from hooks and services'
# Enabled by default
uncheck 'Enforce DNS rebinding attack protection'
click_button 'Save changes'
end
expect(page).to have_content "Application settings saved successfully"
expect(Gitlab::CurrentSettings.allow_local_requests_from_hooks_and_services).to be true
expect(Gitlab::CurrentSettings.dns_rebinding_protection_enabled).to be false
end
end
......
......@@ -76,7 +76,7 @@ describe 'issuable list' do
create(:issue, project: project, author: user)
else
create(:merge_request, source_project: project, source_branch: generate(:branch))
source_branch = FFaker::Name.name
source_branch = FFaker::Lorem.characters(8)
pipeline = create(:ci_empty_pipeline, project: project, ref: source_branch, status: %w(running failed success).sample, sha: 'any')
create(:merge_request, title: FFaker::Lorem.sentence, source_project: project, source_branch: source_branch, head_pipeline: pipeline)
end
......
......@@ -10,7 +10,17 @@ describe('New Project', () => {
setFixtures(`
<div class='toggle-import-form'>
<div class='import-url-data'>
<input id="project_import_url" />
<div class="form-group">
<input id="project_import_url" />
</div>
<div id="import-url-auth-method">
<div class="form-group">
<input id="project-import-url-user" />
</div>
<div class="form-group">
<input id="project_import_url_password" />
</div>
</div>
<input id="project_name" />
<input id="project_path" />
</div>
......@@ -119,7 +129,7 @@ describe('New Project', () => {
});
it('changes project path for HTTPS URL in $projectImportUrl', () => {
$projectImportUrl.val('https://username:password@gitlab.company.com/group/project.git');
$projectImportUrl.val('https://gitlab.company.com/group/project.git');
projectNew.deriveProjectPathFromUrl($projectImportUrl);
......
......@@ -13,10 +13,10 @@ describe Banzai::Redactor do
it 'redacts an array of documents' do
doc1 = Nokogiri::HTML
.fragment('<a class="gfm" data-reference-type="issue">foo</a>')
.fragment('<a class="gfm" href="https://www.gitlab.com" data-reference-type="issue">foo</a>')
doc2 = Nokogiri::HTML
.fragment('<a class="gfm" data-reference-type="issue">bar</a>')
.fragment('<a class="gfm" href="https://www.gitlab.com" data-reference-type="issue">bar</a>')
redacted_data = redactor.redact([doc1, doc2])
......@@ -27,7 +27,7 @@ describe Banzai::Redactor do
end
it 'replaces redacted reference with inner HTML' do
doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue'>foo</a>")
doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue'>foo</a>")
redactor.redact([doc])
expect(doc.to_html).to eq('foo')
end
......@@ -35,20 +35,24 @@ describe Banzai::Redactor do
context 'when data-original attribute provided' do
let(:original_content) { '<code>foo</code>' }
it 'replaces redacted reference with original content' do
doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue' data-original='#{original_content}'>bar</a>")
doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue' data-original='#{original_content}'>bar</a>")
redactor.redact([doc])
expect(doc.to_html).to eq(original_content)
end
end
it 'returns <a> tag with original href if it is originally a link reference' do
href = 'http://localhost:3000'
doc = Nokogiri::HTML
.fragment("<a class='gfm' data-reference-type='issue' data-original=#{href} data-link-reference='true'>#{href}</a>")
redactor.redact([doc])
it 'does not replace redacted reference with original content if href is given' do
html = "<a href='https://www.gitlab.com' data-link-reference='true' class='gfm' data-reference-type='issue' data-reference-type='issue' data-original='Marge'>Marge</a>"
doc = Nokogiri::HTML.fragment(html)
redactor.redact([doc])
expect(doc.to_html).to eq('<a href="https://www.gitlab.com">Marge</a>')
end
expect(doc.to_html).to eq('<a href="http://localhost:3000">http://localhost:3000</a>')
it 'uses the original content as the link content if given' do
html = "<a href='https://www.gitlab.com' data-link-reference='true' class='gfm' data-reference-type='issue' data-reference-type='issue' data-original='Homer'>Marge</a>"
doc = Nokogiri::HTML.fragment(html)
redactor.redact([doc])
expect(doc.to_html).to eq('<a href="https://www.gitlab.com">Homer</a>')
end
end
end
......@@ -61,7 +65,7 @@ describe Banzai::Redactor do
end
it 'redacts an issue attached' do
doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue' data-issue='#{issue.id}'>foo</a>")
doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue' data-issue='#{issue.id}'>foo</a>")
redactor.redact([doc])
......@@ -69,7 +73,7 @@ describe Banzai::Redactor do
end
it 'redacts an external issue' do
doc = Nokogiri::HTML.fragment("<a class='gfm' data-reference-type='issue' data-external-issue='#{issue.id}' data-project='#{project.id}'>foo</a>")
doc = Nokogiri::HTML.fragment("<a class='gfm' href='https://www.gitlab.com' data-reference-type='issue' data-external-issue='#{issue.id}' data-project='#{project.id}'>foo</a>")
redactor.redact([doc])
......
......@@ -5,6 +5,7 @@ describe Gitlab::BitbucketImport::Importer do
before do
stub_omniauth_provider('bitbucket')
stub_feature_flags(stricter_mr_branch_name: false)
end
let(:statuses) do
......
......@@ -3,6 +3,8 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::File::Remote do
include StubRequests
let(:context) { described_class::Context.new(nil, '12345', nil, Set.new) }
let(:params) { { remote: location } }
let(:remote_file) { described_class.new(params, context) }
......@@ -46,7 +48,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
describe "#valid?" do
context 'when is a valid remote url' do
before do
WebMock.stub_request(:get, location).to_return(body: remote_file_content)
stub_full_request(location).to_return(body: remote_file_content)
end
it 'returns true' do
......@@ -92,7 +94,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
describe "#content" do
context 'with a valid remote file' do
before do
WebMock.stub_request(:get, location).to_return(body: remote_file_content)
stub_full_request(location).to_return(body: remote_file_content)
end
it 'returns the content of the file' do
......@@ -114,7 +116,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
let(:location) { 'https://asdasdasdaj48ggerexample.com' }
before do
WebMock.stub_request(:get, location).to_raise(SocketError.new('Some HTTP error'))
stub_full_request(location).to_raise(SocketError.new('Some HTTP error'))
end
it 'is nil' do
......@@ -144,7 +146,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
context 'when timeout error has been raised' do
before do
WebMock.stub_request(:get, location).to_timeout
stub_full_request(location).to_timeout
end
it 'returns error message about a timeout' do
......@@ -154,7 +156,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
context 'when HTTP error has been raised' do
before do
WebMock.stub_request(:get, location).to_raise(Gitlab::HTTP::Error)
stub_full_request(location).to_raise(Gitlab::HTTP::Error)
end
it 'returns error message about a HTTP error' do
......@@ -164,7 +166,7 @@ describe Gitlab::Ci::Config::External::File::Remote do
context 'when response has 404 status' do
before do
WebMock.stub_request(:get, location).to_return(body: remote_file_content, status: 404)
stub_full_request(location).to_return(body: remote_file_content, status: 404)
end
it 'returns error message about a timeout' do
......
......@@ -3,6 +3,8 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::Mapper do
include StubRequests
set(:project) { create(:project, :repository) }
set(:user) { create(:user) }
......@@ -18,7 +20,7 @@ describe Gitlab::Ci::Config::External::Mapper do
end
before do
WebMock.stub_request(:get, remote_url).to_return(body: file_content)
stub_full_request(remote_url).to_return(body: file_content)
end
describe '#process' do
......
......@@ -3,6 +3,8 @@
require 'spec_helper'
describe Gitlab::Ci::Config::External::Processor do
include StubRequests
set(:project) { create(:project, :repository) }
set(:another_project) { create(:project, :repository) }
set(:user) { create(:user) }
......@@ -42,7 +44,7 @@ describe Gitlab::Ci::Config::External::Processor do
let(:values) { { include: remote_file, image: 'ruby:2.2' } }
before do
WebMock.stub_request(:get, remote_file).to_raise(SocketError.new('Some HTTP error'))
stub_full_request(remote_file).and_raise(SocketError.new('Some HTTP error'))
end
it 'raises an error' do
......@@ -75,7 +77,7 @@ describe Gitlab::Ci::Config::External::Processor do
end
before do
WebMock.stub_request(:get, remote_file).to_return(body: external_file_content)
stub_full_request(remote_file).to_return(body: external_file_content)
end
it 'appends the file to the values' do
......@@ -145,7 +147,7 @@ describe Gitlab::Ci::Config::External::Processor do
allow_any_instance_of(Gitlab::Ci::Config::External::File::Local)
.to receive(:fetch_local_content).and_return(local_file_content)
WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content)
stub_full_request(remote_file).to_return(body: remote_file_content)
end
it 'appends the files to the values' do
......@@ -191,7 +193,8 @@ describe Gitlab::Ci::Config::External::Processor do
end
it 'takes precedence' do
WebMock.stub_request(:get, remote_file).to_return(body: remote_file_content)
stub_full_request(remote_file).to_return(body: remote_file_content)
expect(processor.perform[:image]).to eq('ruby:2.2')
end
end
......@@ -231,7 +234,8 @@ describe Gitlab::Ci::Config::External::Processor do
HEREDOC
end
WebMock.stub_request(:get, 'http://my.domain.com/config.yml').to_return(body: 'remote_build: { script: echo Hello World }')
stub_full_request('http://my.domain.com/config.yml')
.to_return(body: 'remote_build: { script: echo Hello World }')
end
context 'when project is public' do
......@@ -273,8 +277,10 @@ describe Gitlab::Ci::Config::External::Processor do
context 'when config includes an external configuration file via SSL web request' do
before do
stub_request(:get, 'https://sha256.badssl.com/fake.yml').to_return(body: 'image: ruby:2.6', status: 200)
stub_request(:get, 'https://self-signed.badssl.com/fake.yml')
stub_full_request('https://sha256.badssl.com/fake.yml', ip_address: '8.8.8.8')
.to_return(body: 'image: ruby:2.6', status: 200)
stub_full_request('https://self-signed.badssl.com/fake.yml', ip_address: '8.8.8.9')
.to_raise(OpenSSL::SSL::SSLError.new('SSL_connect returned=1 errno=0 state=error: certificate verify failed (self signed certificate)'))
end
......
require 'spec_helper'
describe Gitlab::Ci::Config do
include StubRequests
set(:user) { create(:user) }
let(:config) do
......@@ -216,8 +218,7 @@ describe Gitlab::Ci::Config do
end
before do
WebMock.stub_request(:get, remote_location)
.to_return(body: remote_file_content)
stub_full_request(remote_location).to_return(body: remote_file_content)
allow(project.repository)
.to receive(:blob_data_at).and_return(local_file_content)
......
......@@ -3,6 +3,8 @@ require 'spec_helper'
module Gitlab
module Ci
describe YamlProcessor do
include StubRequests
subject { described_class.new(config, user: nil) }
describe '#build_attributes' do
......@@ -648,7 +650,7 @@ module Gitlab
end
before do
WebMock.stub_request(:get, 'https://gitlab.com/awesome-project/raw/master/.before-script-template.yml')
stub_full_request('https://gitlab.com/awesome-project/raw/master/.before-script-template.yml')
.to_return(
status: 200,
headers: { 'Content-Type' => 'application/json' },
......
require 'spec_helper'
describe Gitlab::GitRefValidator do
it { expect(described_class.validate('feature/new')).to be_truthy }
it { expect(described_class.validate('implement_@all')).to be_truthy }
it { expect(described_class.validate('my_new_feature')).to be_truthy }
it { expect(described_class.validate('my-branch')).to be_truthy }
it { expect(described_class.validate('#1')).to be_truthy }
it { expect(described_class.validate('feature/refs/heads/foo')).to be_truthy }
it { expect(described_class.validate('feature/~new/')).to be_falsey }
it { expect(described_class.validate('feature/^new/')).to be_falsey }
it { expect(described_class.validate('feature/:new/')).to be_falsey }
it { expect(described_class.validate('feature/?new/')).to be_falsey }
it { expect(described_class.validate('feature/*new/')).to be_falsey }
it { expect(described_class.validate('feature/[new/')).to be_falsey }
it { expect(described_class.validate('feature/new/')).to be_falsey }
it { expect(described_class.validate('feature/new.')).to be_falsey }
it { expect(described_class.validate('feature\@{')).to be_falsey }
it { expect(described_class.validate('feature\new')).to be_falsey }
it { expect(described_class.validate('feature//new')).to be_falsey }
it { expect(described_class.validate('feature new')).to be_falsey }
it { expect(described_class.validate('refs/heads/')).to be_falsey }
it { expect(described_class.validate('refs/remotes/')).to be_falsey }
it { expect(described_class.validate('refs/heads/feature')).to be_falsey }
it { expect(described_class.validate('refs/remotes/origin')).to be_falsey }
it { expect(described_class.validate('-')).to be_falsey }
it { expect(described_class.validate('-branch')).to be_falsey }
it { expect(described_class.validate('.tag')).to be_falsey }
it { expect(described_class.validate('my branch')).to be_falsey }
it { expect(described_class.validate("\xA0\u0000\xB0")).to be_falsey }
using RSpec::Parameterized::TableSyntax
context '.validate' do
it { expect(described_class.validate('feature/new')).to be true }
it { expect(described_class.validate('implement_@all')).to be true }
it { expect(described_class.validate('my_new_feature')).to be true }
it { expect(described_class.validate('my-branch')).to be true }
it { expect(described_class.validate('#1')).to be true }
it { expect(described_class.validate('feature/refs/heads/foo')).to be true }
it { expect(described_class.validate('feature/~new/')).to be false }
it { expect(described_class.validate('feature/^new/')).to be false }
it { expect(described_class.validate('feature/:new/')).to be false }
it { expect(described_class.validate('feature/?new/')).to be false }
it { expect(described_class.validate('feature/*new/')).to be false }
it { expect(described_class.validate('feature/[new/')).to be false }
it { expect(described_class.validate('feature/new/')).to be false }
it { expect(described_class.validate('feature/new.')).to be false }
it { expect(described_class.validate('feature\@{')).to be false }
it { expect(described_class.validate('feature\new')).to be false }
it { expect(described_class.validate('feature//new')).to be false }
it { expect(described_class.validate('feature new')).to be false }
it { expect(described_class.validate('refs/heads/')).to be false }
it { expect(described_class.validate('refs/remotes/')).to be false }
it { expect(described_class.validate('refs/heads/feature')).to be false }
it { expect(described_class.validate('refs/remotes/origin')).to be false }
it { expect(described_class.validate('-')).to be false }
it { expect(described_class.validate('-branch')).to be false }
it { expect(described_class.validate('+foo:bar')).to be false }
it { expect(described_class.validate('foo:bar')).to be false }
it { expect(described_class.validate('.tag')).to be false }
it { expect(described_class.validate('my branch')).to be false }
it { expect(described_class.validate("\xA0\u0000\xB0")).to be false }
end
context '.validate_merge_request_branch' do
it { expect(described_class.validate_merge_request_branch('HEAD')).to be true }
it { expect(described_class.validate_merge_request_branch('feature/new')).to be true }
it { expect(described_class.validate_merge_request_branch('implement_@all')).to be true }
it { expect(described_class.validate_merge_request_branch('my_new_feature')).to be true }
it { expect(described_class.validate_merge_request_branch('my-branch')).to be true }
it { expect(described_class.validate_merge_request_branch('#1')).to be true }
it { expect(described_class.validate_merge_request_branch('feature/refs/heads/foo')).to be true }
it { expect(described_class.validate_merge_request_branch('feature/~new/')).to be false }
it { expect(described_class.validate_merge_request_branch('feature/^new/')).to be false }
it { expect(described_class.validate_merge_request_branch('feature/:new/')).to be false }
it { expect(described_class.validate_merge_request_branch('feature/?new/')).to be false }
it { expect(described_class.validate_merge_request_branch('feature/*new/')).to be false }
it { expect(described_class.validate_merge_request_branch('feature/[new/')).to be false }
it { expect(described_class.validate_merge_request_branch('feature/new/')).to be false }
it { expect(described_class.validate_merge_request_branch('feature/new.')).to be false }
it { expect(described_class.validate_merge_request_branch('feature\@{')).to be false }
it { expect(described_class.validate_merge_request_branch('feature\new')).to be false }
it { expect(described_class.validate_merge_request_branch('feature//new')).to be false }
it { expect(described_class.validate_merge_request_branch('feature new')).to be false }
it { expect(described_class.validate_merge_request_branch('refs/heads/master')).to be true }
it { expect(described_class.validate_merge_request_branch('refs/heads/')).to be false }
it { expect(described_class.validate_merge_request_branch('refs/remotes/')).to be false }
it { expect(described_class.validate_merge_request_branch('-')).to be false }
it { expect(described_class.validate_merge_request_branch('-branch')).to be false }
it { expect(described_class.validate_merge_request_branch('+foo:bar')).to be false }
it { expect(described_class.validate_merge_request_branch('foo:bar')).to be false }
it { expect(described_class.validate_merge_request_branch('.tag')).to be false }
it { expect(described_class.validate_merge_request_branch('my branch')).to be false }
it { expect(described_class.validate_merge_request_branch("\xA0\u0000\xB0")).to be false }
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Gitlab::HTTPConnectionAdapter do
describe '#connection' do
context 'when local requests are not allowed' do
it 'sets up the connection' do
uri = URI('https://example.org')
connection = described_class.new(uri).connection
expect(connection).to be_a(Net::HTTP)
expect(connection.address).to eq('93.184.216.34')
expect(connection.hostname_override).to eq('example.org')
expect(connection.addr_port).to eq('example.org')
expect(connection.port).to eq(443)
end
it 'raises error when it is a request to local address' do
uri = URI('http://172.16.0.0/12')
expect { described_class.new(uri).connection }
.to raise_error(Gitlab::HTTP::BlockedUrlError,
"URL 'http://172.16.0.0/12' is blocked: Requests to the local network are not allowed")
end
it 'raises error when it is a request to localhost address' do
uri = URI('http://127.0.0.1')
expect { described_class.new(uri).connection }
.to raise_error(Gitlab::HTTP::BlockedUrlError,
"URL 'http://127.0.0.1' is blocked: Requests to localhost are not allowed")
end
context 'when port different from URL scheme is used' do
it 'sets up the addr_port accordingly' do
uri = URI('https://example.org:8080')
connection = described_class.new(uri).connection
expect(connection.address).to eq('93.184.216.34')
expect(connection.hostname_override).to eq('example.org')
expect(connection.addr_port).to eq('example.org:8080')
expect(connection.port).to eq(8080)
end
end
end
context 'when DNS rebinding protection is disabled' do
it 'sets up the connection' do
stub_application_setting(dns_rebinding_protection_enabled: false)
uri = URI('https://example.org')
connection = described_class.new(uri).connection
expect(connection).to be_a(Net::HTTP)
expect(connection.address).to eq('example.org')
expect(connection.hostname_override).to eq(nil)
expect(connection.addr_port).to eq('example.org')
expect(connection.port).to eq(443)
end
end
context 'when http(s) environment variable is set' do
it 'sets up the connection' do
stub_env('https_proxy' => 'https://my.proxy')
uri = URI('https://example.org')
connection = described_class.new(uri).connection
expect(connection).to be_a(Net::HTTP)
expect(connection.address).to eq('example.org')
expect(connection.hostname_override).to eq(nil)
expect(connection.addr_port).to eq('example.org')
expect(connection.port).to eq(443)
end
end
context 'when local requests are allowed' do
it 'sets up the connection' do
uri = URI('https://example.org')
connection = described_class.new(uri, allow_local_requests: true).connection
expect(connection).to be_a(Net::HTTP)
expect(connection.address).to eq('93.184.216.34')
expect(connection.hostname_override).to eq('example.org')
expect(connection.addr_port).to eq('example.org')
expect(connection.port).to eq(443)
end
it 'sets up the connection when it is a local network' do
uri = URI('http://172.16.0.0/12')
connection = described_class.new(uri, allow_local_requests: true).connection
expect(connection).to be_a(Net::HTTP)
expect(connection.address).to eq('172.16.0.0')
expect(connection.hostname_override).to be(nil)
expect(connection.addr_port).to eq('172.16.0.0')
expect(connection.port).to eq(80)
end
it 'sets up the connection when it is localhost' do
uri = URI('http://127.0.0.1')
connection = described_class.new(uri, allow_local_requests: true).connection
expect(connection).to be_a(Net::HTTP)
expect(connection.address).to eq('127.0.0.1')
expect(connection.hostname_override).to be(nil)
expect(connection.addr_port).to eq('127.0.0.1')
expect(connection.port).to eq(80)
end
end
end
end
require 'spec_helper'
describe Gitlab::HTTP do
include StubRequests
context 'when allow_local_requests' do
it 'sends the request to the correct URI' do
stub_full_request('https://example.org:8080', ip_address: '8.8.8.8').to_return(status: 200)
described_class.get('https://example.org:8080', allow_local_requests: false)
expect(WebMock).to have_requested(:get, 'https://8.8.8.8:8080').once
end
end
context 'when not allow_local_requests' do
it 'sends the request to the correct URI' do
stub_full_request('https://example.org:8080')
described_class.get('https://example.org:8080', allow_local_requests: true)
expect(WebMock).to have_requested(:get, 'https://8.8.8.9:8080').once
end
end
describe 'allow_local_requests_from_hooks_and_services is' do
before do
WebMock.stub_request(:get, /.*/).to_return(status: 200, body: 'Success')
......@@ -21,6 +43,8 @@ describe Gitlab::HTTP do
context 'if allow_local_requests set to true' do
it 'override the global value and allow requests to localhost or private network' do
stub_full_request('http://localhost:3003')
expect { described_class.get('http://localhost:3003', allow_local_requests: true) }.not_to raise_error
end
end
......@@ -32,6 +56,8 @@ describe Gitlab::HTTP do
end
it 'allow requests to localhost' do
stub_full_request('http://localhost:3003')
expect { described_class.get('http://localhost:3003') }.not_to raise_error
end
......@@ -49,7 +75,7 @@ describe Gitlab::HTTP do
describe 'handle redirect loops' do
before do
WebMock.stub_request(:any, "http://example.org").to_raise(HTTParty::RedirectionTooDeep.new("Redirection Too Deep"))
stub_full_request("http://example.org", method: :any).to_raise(HTTParty::RedirectionTooDeep.new("Redirection Too Deep"))
end
it 'handles GET requests' do
......
require 'spec_helper'
describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
include StubRequests
let(:example_url) { 'http://www.example.com' }
let(:strategy) { subject.new(url: example_url, http_method: 'post') }
let!(:project) { create(:project, :with_export) }
......@@ -35,7 +37,7 @@ describe Gitlab::ImportExport::AfterExportStrategies::WebUploadStrategy do
context 'when upload fails' do
it 'stores the export error' do
stub_request(:post, example_url).to_return(status: [404, 'Page not found'])
stub_full_request(example_url, method: :post).to_return(status: [404, 'Page not found'])
strategy.execute(user, project)
......
......@@ -256,4 +256,28 @@ describe Gitlab::SearchResults do
expect(results.objects('merge_requests')).not_to include merge_request
end
context 'milestones' do
it 'returns correct set of milestones' do
private_project_1 = create(:project, :private)
private_project_2 = create(:project, :private)
internal_project = create(:project, :internal)
public_project_1 = create(:project, :public)
public_project_2 = create(:project, :public, :issues_disabled, :merge_requests_disabled)
private_project_1.add_developer(user)
# milestones that should not be visible
create(:milestone, project: private_project_2, title: 'Private project without access milestone')
create(:milestone, project: public_project_2, title: 'Public project with milestones disabled milestone')
# milestones that should be visible
milestone_1 = create(:milestone, project: private_project_1, title: 'Private project with access milestone', state: 'closed')
milestone_2 = create(:milestone, project: internal_project, title: 'Internal project milestone')
milestone_3 = create(:milestone, project: public_project_1, title: 'Public project with milestones enabled milestone')
# Global search scope takes user authorized projects, internal projects and public projects.
limit_projects = ProjectsFinder.new(current_user: user).execute
milestones = described_class.new(user, limit_projects, 'milestone').objects('milestones')
expect(milestones).to match_array([milestone_1, milestone_2, milestone_3])
end
end
end
......@@ -2,6 +2,87 @@
require 'spec_helper'
describe Gitlab::UrlBlocker do
describe '#validate!' do
context 'when URI is nil' do
let(:import_url) { nil }
it 'returns no URI and hostname' do
uri, hostname = described_class.validate!(import_url)
expect(uri).to be(nil)
expect(hostname).to be(nil)
end
end
context 'when URI is internal' do
let(:import_url) { 'http://localhost' }
it 'returns URI and no hostname' do
uri, hostname = described_class.validate!(import_url)
expect(uri).to eq(Addressable::URI.parse('http://[::1]'))
expect(hostname).to eq('localhost')
end
end
context 'when the URL hostname is a domain' do
let(:import_url) { 'https://example.org' }
it 'returns URI and hostname' do
uri, hostname = described_class.validate!(import_url)
expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34'))
expect(hostname).to eq('example.org')
end
end
context 'when the URL hostname is an IP address' do
let(:import_url) { 'https://93.184.216.34' }
it 'returns URI and no hostname' do
uri, hostname = described_class.validate!(import_url)
expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34'))
expect(hostname).to be(nil)
end
end
context 'disabled DNS rebinding protection' do
context 'when URI is internal' do
let(:import_url) { 'http://localhost' }
it 'returns URI and no hostname' do
uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false)
expect(uri).to eq(Addressable::URI.parse('http://localhost'))
expect(hostname).to be(nil)
end
end
context 'when the URL hostname is a domain' do
let(:import_url) { 'https://example.org' }
it 'returns URI and no hostname' do
uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false)
expect(uri).to eq(Addressable::URI.parse('https://example.org'))
expect(hostname).to eq(nil)
end
end
context 'when the URL hostname is an IP address' do
let(:import_url) { 'https://93.184.216.34' }
it 'returns URI and no hostname' do
uri, hostname = described_class.validate!(import_url, dns_rebind_protection: false)
expect(uri).to eq(Addressable::URI.parse('https://93.184.216.34'))
expect(hostname).to be(nil)
end
end
end
end
describe '#blocked_url?' do
let(:ports) { Project::VALID_IMPORT_PORTS }
......@@ -208,7 +289,7 @@ describe Gitlab::UrlBlocker do
end
def stub_domain_resolv(domain, ip)
address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false)
address = double(ip_address: ip, ipv4_private?: true, ipv6_link_local?: false, ipv4_loopback?: false, ipv6_loopback?: false, ipv4?: false)
allow(Addrinfo).to receive(:getaddrinfo).with(domain, any_args).and_return([address])
allow(address).to receive(:ipv6_v4mapped?).and_return(false)
end
......
......@@ -115,6 +115,40 @@ describe Gitlab::UrlSanitizer do
end
end
describe '#user' do
context 'credentials in hash' do
it 'overrides URL-provided user' do
sanitizer = described_class.new('http://a:b@example.com', credentials: { user: 'c', password: 'd' })
expect(sanitizer.user).to eq('c')
end
end
context 'credentials in URL' do
where(:url, :user) do
'http://foo:bar@example.com' | 'foo'
'http://foo:bar:baz@example.com' | 'foo'
'http://:bar@example.com' | nil
'http://foo:@example.com' | 'foo'
'http://foo@example.com' | 'foo'
'http://:@example.com' | nil
'http://@example.com' | nil
'http://example.com' | nil
# Other invalid URLs
nil | nil
'' | nil
'no' | nil
end
with_them do
subject { described_class.new(url).user }
it { is_expected.to eq(user) }
end
end
end
describe '#full_url' do
context 'credentials in hash' do
where(:credentials, :userinfo) do
......
......@@ -109,4 +109,34 @@ describe Gitlab do
expect(described_class.ee?).to eq(false)
end
end
describe '.http_proxy_env?' do
it 'returns true when lower case https' do
stub_env('https_proxy', 'https://my.proxy')
expect(described_class.http_proxy_env?).to eq(true)
end
it 'returns true when upper case https' do
stub_env('HTTPS_PROXY', 'https://my.proxy')
expect(described_class.http_proxy_env?).to eq(true)
end
it 'returns true when lower case http' do
stub_env('http_proxy', 'http://my.proxy')
expect(described_class.http_proxy_env?).to eq(true)
end
it 'returns true when upper case http' do
stub_env('HTTP_PROXY', 'http://my.proxy')
expect(described_class.http_proxy_env?).to eq(true)
end
it 'returns false when not set' do
expect(described_class.http_proxy_env?).to eq(false)
end
end
end
......@@ -2,6 +2,7 @@ require 'spec_helper'
describe Mattermost::Session, type: :request do
include ExclusiveLeaseHelpers
include StubRequests
let(:user) { create(:user) }
......@@ -24,7 +25,7 @@ describe Mattermost::Session, type: :request do
let(:location) { 'http://location.tld' }
let(:cookie_header) {'MMOAUTH=taskik8az7rq8k6rkpuas7htia; Path=/;'}
let!(:stub) do
WebMock.stub_request(:get, "#{mattermost_url}/oauth/gitlab/login")
stub_full_request("#{mattermost_url}/oauth/gitlab/login")
.to_return(headers: { 'location' => location, 'Set-Cookie' => cookie_header }, status: 302)
end
......@@ -63,7 +64,7 @@ describe Mattermost::Session, type: :request do
end
before do
WebMock.stub_request(:get, "#{mattermost_url}/signup/gitlab/complete")
stub_full_request("#{mattermost_url}/signup/gitlab/complete")
.with(query: hash_including({ 'state' => state }))
.to_return do |request|
post "/oauth/token",
......@@ -80,7 +81,7 @@ describe Mattermost::Session, type: :request do
end
end
WebMock.stub_request(:post, "#{mattermost_url}/api/v4/users/logout")
stub_full_request("#{mattermost_url}/api/v4/users/logout", method: :post)
.to_return(headers: { Authorization: 'token thisworksnow' }, status: 200)
end
......
......@@ -173,6 +173,42 @@ describe MergeRequest do
end
end
context 'for branch' do
before do
stub_feature_flags(stricter_mr_branch_name: false)
end
using RSpec::Parameterized::TableSyntax
where(:branch_name, :valid) do
'foo' | true
'foo:bar' | false
'+foo:bar' | false
'foo bar' | false
'-foo' | false
'HEAD' | true
'refs/heads/master' | true
end
with_them do
it "validates source_branch" do
subject = build(:merge_request, source_branch: branch_name, target_branch: 'master')
subject.valid?
expect(subject.errors.added?(:source_branch)).to eq(!valid)
end
it "validates target_branch" do
subject = build(:merge_request, source_branch: 'master', target_branch: branch_name)
subject.valid?
expect(subject.errors.added?(:target_branch)).to eq(!valid)
end
end
end
context 'for forks' do
let(:project) { create(:project) }
let(:fork1) { fork_project(project) }
......
......@@ -3,6 +3,8 @@
require 'spec_helper'
describe AssemblaService do
include StubRequests
describe "Associations" do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
......@@ -23,12 +25,12 @@ describe AssemblaService do
)
@sample_data = Gitlab::DataBuilder::Push.build_sample(project, user)
@api_url = 'https://atlas.assembla.com/spaces/project_name/github_tool?secret_key=verySecret'
WebMock.stub_request(:post, @api_url)
stub_full_request(@api_url, method: :post)
end
it "calls Assembla API" do
@assembla_service.execute(@sample_data)
expect(WebMock).to have_requested(:post, @api_url).with(
expect(WebMock).to have_requested(:post, stubbed_hostname(@api_url)).with(
body: /#{@sample_data[:before]}.*#{@sample_data[:after]}.*#{project.path}/
).once
end
......
......@@ -4,6 +4,7 @@ require 'spec_helper'
describe BambooService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
include StubRequests
let(:bamboo_url) { 'http://gitlab.com/bamboo' }
......@@ -257,7 +258,7 @@ describe BambooService, :use_clean_rails_memory_store_caching do
end
def stub_bamboo_request(url, status, body)
WebMock.stub_request(:get, url).to_return(
stub_full_request(url).to_return(
status: status,
headers: { 'Content-Type' => 'application/json' },
body: body
......
......@@ -4,6 +4,7 @@ require 'spec_helper'
describe BuildkiteService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
include StubRequests
let(:project) { create(:project) }
......@@ -110,10 +111,9 @@ describe BuildkiteService, :use_clean_rails_memory_store_caching do
body ||= %q({"status":"success"})
buildkite_full_url = 'https://gitlab.buildkite.com/status/secret-sauce-status-token.json?commit=123'
WebMock.stub_request(:get, buildkite_full_url).to_return(
status: status,
headers: { 'Content-Type' => 'application/json' },
body: body
)
stub_full_request(buildkite_full_url)
.to_return(status: status,
headers: { 'Content-Type' => 'application/json' },
body: body)
end
end
......@@ -3,6 +3,8 @@
require 'spec_helper'
describe CampfireService do
include StubRequests
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
......@@ -49,39 +51,37 @@ describe CampfireService do
it "calls Campfire API to get a list of rooms and speak in a room" do
# make sure a valid list of rooms is returned
body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms.json')
WebMock.stub_request(:get, @rooms_url).with(basic_auth: @auth).to_return(
stub_full_request(@rooms_url).with(basic_auth: @auth).to_return(
body: body,
status: 200,
headers: @headers
)
# stub the speak request with the room id found in the previous request's response
speak_url = 'https://project-name.campfirenow.com/room/123/speak.json'
WebMock.stub_request(:post, speak_url).with(basic_auth: @auth)
stub_full_request(speak_url, method: :post).with(basic_auth: @auth)
@campfire_service.execute(@sample_data)
expect(WebMock).to have_requested(:get, @rooms_url).once
expect(WebMock).to have_requested(:post, speak_url).with(
body: /#{project.path}.*#{@sample_data[:before]}.*#{@sample_data[:after]}/
).once
expect(WebMock).to have_requested(:get, stubbed_hostname(@rooms_url)).once
expect(WebMock).to have_requested(:post, stubbed_hostname(speak_url))
.with(body: /#{project.path}.*#{@sample_data[:before]}.*#{@sample_data[:after]}/).once
end
it "calls Campfire API to get a list of rooms but shouldn't speak in a room" do
# return a list of rooms that do not contain a room named 'test-room'
body = File.read(Rails.root + 'spec/fixtures/project_services/campfire/rooms2.json')
WebMock.stub_request(:get, @rooms_url).with(basic_auth: @auth).to_return(
stub_full_request(@rooms_url).with(basic_auth: @auth).to_return(
body: body,
status: 200,
headers: @headers
)
# we want to make sure no request is sent to the /speak endpoint, here is a basic
# regexp that matches this endpoint
speak_url = 'https://verySecret:X@project-name.campfirenow.com/room/.*/speak.json'
@campfire_service.execute(@sample_data)
expect(WebMock).to have_requested(:get, @rooms_url).once
expect(WebMock).not_to have_requested(:post, /#{speak_url}/)
expect(WebMock).to have_requested(:get, 'https://8.8.8.9/rooms.json').once
expect(WebMock).not_to have_requested(:post, '*/room/.*/speak.json')
end
end
end
......@@ -3,6 +3,8 @@
require 'spec_helper'
describe PivotaltrackerService do
include StubRequests
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
......@@ -53,12 +55,12 @@ describe PivotaltrackerService do
end
before do
WebMock.stub_request(:post, url)
stub_full_request(url, method: :post)
end
it 'posts correct message' do
service.execute(push_data)
expect(WebMock).to have_requested(:post, url).with(
expect(WebMock).to have_requested(:post, stubbed_hostname(url)).with(
body: {
'source_commit' => {
'commit_id' => '21c12ea',
......@@ -85,14 +87,14 @@ describe PivotaltrackerService do
service.execute(push_data(branch: 'master'))
service.execute(push_data(branch: 'v10'))
expect(WebMock).to have_requested(:post, url).twice
expect(WebMock).to have_requested(:post, stubbed_hostname(url)).twice
end
it 'does not post message if branch is not in the list' do
service.execute(push_data(branch: 'mas'))
service.execute(push_data(branch: 'v11'))
expect(WebMock).not_to have_requested(:post, url)
expect(WebMock).not_to have_requested(:post, stubbed_hostname(url))
end
end
end
......
......@@ -3,6 +3,8 @@
require 'spec_helper'
describe PushoverService do
include StubRequests
describe 'Associations' do
it { is_expected.to belong_to :project }
it { is_expected.to have_one :service_hook }
......@@ -57,13 +59,13 @@ describe PushoverService do
sound: sound
)
WebMock.stub_request(:post, api_url)
stub_full_request(api_url, method: :post, ip_address: '8.8.8.8')
end
it 'calls Pushover API' do
pushover.execute(sample_data)
expect(WebMock).to have_requested(:post, api_url).once
expect(WebMock).to have_requested(:post, 'https://8.8.8.8/1/messages.json').once
end
end
end
......@@ -4,6 +4,7 @@ require 'spec_helper'
describe TeamcityService, :use_clean_rails_memory_store_caching do
include ReactiveCachingHelpers
include StubRequests
let(:teamcity_url) { 'http://gitlab.com/teamcity' }
......@@ -212,7 +213,7 @@ describe TeamcityService, :use_clean_rails_memory_store_caching do
body ||= %Q({"build":{"status":"#{build_status}","id":"666"}})
WebMock.stub_request(:get, teamcity_full_url).with(basic_auth: auth).to_return(
stub_full_request(teamcity_full_url).with(basic_auth: auth).to_return(
status: status,
headers: { 'Content-Type' => 'application/json' },
body: body
......
......@@ -3170,6 +3170,23 @@ describe Project do
end
end
describe '.ids_with_milestone_available_for' do
let!(:user) { create(:user) }
it 'returns project ids with milestones available for user' do
project_1 = create(:project, :public, :merge_requests_disabled, :issues_disabled)
project_2 = create(:project, :public, :merge_requests_disabled)
project_3 = create(:project, :public, :issues_disabled)
project_4 = create(:project, :public)
project_4.project_feature.update(issues_access_level: ProjectFeature::PRIVATE, merge_requests_access_level: ProjectFeature::PRIVATE )
project_ids = described_class.ids_with_milestone_available_for(user).pluck(:id)
expect(project_ids).to include(project_2.id, project_3.id)
expect(project_ids).not_to include(project_1.id, project_4.id)
end
end
describe '.with_feature_available_for_user' do
let(:user) { create(:user) }
let(:feature) { MergeRequest }
......
......@@ -70,11 +70,30 @@ describe API::Search do
context 'for milestones scope' do
before do
create(:milestone, project: project, title: 'awesome milestone')
end
context 'when user can read project milestones' do
before do
get api('/search', user), params: { scope: 'milestones', search: 'awesome' }
end
get api('/search', user), params: { scope: 'milestones', search: 'awesome' }
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
end
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
context 'when user cannot read project milestones' do
before do
project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
end
it 'returns empty array' do
get api('/search', user), params: { scope: 'milestones', search: 'awesome' }
milestones = JSON.parse(response.body)
expect(milestones).to be_empty
end
end
end
context 'for users scope' do
......@@ -318,11 +337,30 @@ describe API::Search do
context 'for milestones scope' do
before do
create(:milestone, project: project, title: 'awesome milestone')
end
context 'when user can read milestones' do
before do
get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' }
end
get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' }
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
end
it_behaves_like 'response is correct', schema: 'public_api/v4/milestones'
context 'when user cannot read project milestones' do
before do
project.project_feature.update!(merge_requests_access_level: ProjectFeature::PRIVATE)
project.project_feature.update!(issues_access_level: ProjectFeature::PRIVATE)
end
it 'returns empty array' do
get api("/projects/#{project.id}/search", user), params: { scope: 'milestones', search: 'awesome' }
milestones = JSON.parse(response.body)
expect(milestones).to be_empty
end
end
end
context 'for users scope' do
......
require 'spec_helper'
describe API::SystemHooks do
include StubRequests
let(:user) { create(:user) }
let(:admin) { create(:admin) }
let!(:hook) { create(:system_hook, url: "http://example.com") }
before do
stub_request(:post, hook.url)
stub_full_request(hook.url, method: :post)
end
describe "GET /hooks" do
......@@ -68,6 +70,8 @@ describe API::SystemHooks do
end
it 'sets default values for events' do
stub_full_request('http://mep.mep', method: :post)
post api('/hooks', admin), params: { url: 'http://mep.mep' }
expect(response).to have_gitlab_http_status(201)
......@@ -78,6 +82,8 @@ describe API::SystemHooks do
end
it 'sets explicit values for events' do
stub_full_request('http://mep.mep', method: :post)
post api('/hooks', admin),
params: {
url: 'http://mep.mep',
......
......@@ -973,7 +973,7 @@ describe Ci::CreatePipelineService do
let(:merge_request) do
create(:merge_request,
source_project: project,
source_branch: ref_name,
source_branch: Gitlab::Git.ref_name(ref_name),
target_project: project,
target_branch: 'master')
end
......@@ -1004,7 +1004,7 @@ describe Ci::CreatePipelineService do
let(:merge_request) do
create(:merge_request,
source_project: project,
source_branch: ref_name,
source_branch: Gitlab::Git.ref_name(ref_name),
target_project: project,
target_branch: 'master')
end
......@@ -1033,7 +1033,7 @@ describe Ci::CreatePipelineService do
let(:merge_request) do
create(:merge_request,
source_project: project,
source_branch: ref_name,
source_branch: Gitlab::Git.ref_name(ref_name),
target_project: project,
target_branch: 'master')
end
......
......@@ -2,6 +2,8 @@
require 'spec_helper'
describe Projects::LfsPointers::LfsDownloadService do
include StubRequests
let(:project) { create(:project) }
let(:lfs_content) { SecureRandom.random_bytes(10) }
let(:oid) { Digest::SHA256.hexdigest(lfs_content) }
......@@ -62,7 +64,7 @@ describe Projects::LfsPointers::LfsDownloadService do
describe '#execute' do
context 'when file download succeeds' do
before do
WebMock.stub_request(:get, download_link).to_return(body: lfs_content)
stub_full_request(download_link).to_return(body: lfs_content)
end
it_behaves_like 'lfs object is created'
......@@ -104,7 +106,7 @@ describe Projects::LfsPointers::LfsDownloadService do
let(:size) { 1 }
before do
WebMock.stub_request(:get, download_link).to_return(body: lfs_content)
stub_full_request(download_link).to_return(body: lfs_content)
end
it_behaves_like 'no lfs object is created'
......@@ -118,7 +120,7 @@ describe Projects::LfsPointers::LfsDownloadService do
context 'when downloaded lfs file has a different oid' do
before do
WebMock.stub_request(:get, download_link).to_return(body: lfs_content)
stub_full_request(download_link).to_return(body: lfs_content)
allow_any_instance_of(Digest::SHA256).to receive(:hexdigest).and_return('foobar')
end
......@@ -136,7 +138,7 @@ describe Projects::LfsPointers::LfsDownloadService do
let(:lfs_object) { LfsDownloadObject.new(oid: oid, size: size, link: download_link_with_credentials) }
before do
WebMock.stub_request(:get, download_link).with(headers: { 'Authorization' => 'Basic dXNlcjpwYXNzd29yZA==' }).to_return(body: lfs_content)
stub_full_request(download_link).with(headers: { 'Authorization' => 'Basic dXNlcjpwYXNzd29yZA==' }).to_return(body: lfs_content)
end
it 'the request adds authorization headers' do
......@@ -149,7 +151,7 @@ describe Projects::LfsPointers::LfsDownloadService do
let(:local_request_setting) { true }
before do
WebMock.stub_request(:get, download_link).to_return(body: lfs_content)
stub_full_request(download_link, ip_address: '192.168.2.120').to_return(body: lfs_content)
end
it_behaves_like 'lfs object is created'
......@@ -173,7 +175,8 @@ describe Projects::LfsPointers::LfsDownloadService do
with_them do
before do
WebMock.stub_request(:get, download_link).to_return(status: 301, headers: { 'Location' => redirect_link })
stub_full_request(download_link, ip_address: '192.168.2.120')
.to_return(status: 301, headers: { 'Location' => redirect_link })
end
it_behaves_like 'no lfs object is created'
......@@ -184,8 +187,8 @@ describe Projects::LfsPointers::LfsDownloadService do
let(:redirect_link) { "http://example.com/"}
before do
WebMock.stub_request(:get, download_link).to_return(status: 301, headers: { 'Location' => redirect_link })
WebMock.stub_request(:get, redirect_link).to_return(body: lfs_content)
stub_full_request(download_link).to_return(status: 301, headers: { 'Location' => redirect_link })
stub_full_request(redirect_link).to_return(body: lfs_content)
end
it_behaves_like 'lfs object is created'
......
......@@ -3,6 +3,8 @@
require 'spec_helper'
describe SubmitUsagePingService do
include StubRequests
context 'when usage ping is disabled' do
before do
stub_application_setting(usage_ping_enabled: false)
......@@ -99,7 +101,7 @@ describe SubmitUsagePingService do
end
def stub_response(body)
stub_request(:post, 'https://version.gitlab.com/usage_data')
stub_full_request('https://version.gitlab.com/usage_data', method: :post)
.to_return(
headers: { 'Content-Type' => 'application/json' },
body: body.to_json
......
......@@ -3,6 +3,8 @@
require 'spec_helper'
describe WebHookService do
include StubRequests
let(:project) { create(:project) }
let(:project_hook) { create(:project_hook) }
let(:headers) do
......@@ -67,11 +69,11 @@ describe WebHookService do
let(:project_hook) { create(:project_hook, url: 'https://demo:demo@example.org/') }
it 'uses the credentials' do
WebMock.stub_request(:post, url)
stub_full_request(url, method: :post)
service_instance.execute
expect(WebMock).to have_requested(:post, url).with(
expect(WebMock).to have_requested(:post, stubbed_hostname(url)).with(
headers: headers.merge('Authorization' => 'Basic ZGVtbzpkZW1v')
).once
end
......@@ -82,11 +84,11 @@ describe WebHookService do
let(:project_hook) { create(:project_hook, url: 'https://demo@example.org/') }
it 'uses the credentials anyways' do
WebMock.stub_request(:post, url)
stub_full_request(url, method: :post)
service_instance.execute
expect(WebMock).to have_requested(:post, url).with(
expect(WebMock).to have_requested(:post, stubbed_hostname(url)).with(
headers: headers.merge('Authorization' => 'Basic ZGVtbzo=')
).once
end
......
module StubRequests
IP_ADDRESS_STUB = '8.8.8.9'.freeze
# Fully stubs a request using WebMock class. This class also
# stubs the IP address the URL is translated to (DNS lookup).
#
# It expects the final request to go to the `ip_address` instead the given url.
# That's primarily a DNS rebind attack prevention of Gitlab::HTTP
# (see: Gitlab::UrlBlocker).
#
def stub_full_request(url, ip_address: IP_ADDRESS_STUB, port: 80, method: :get)
stub_dns(url, ip_address: ip_address, port: port)
url = stubbed_hostname(url, hostname: ip_address)
WebMock.stub_request(method, url)
end
def stub_dns(url, ip_address:, port: 80)
url = parse_url(url)
socket = Socket.sockaddr_in(port, ip_address)
addr = Addrinfo.new(socket)
# See Gitlab::UrlBlocker
allow(Addrinfo).to receive(:getaddrinfo)
.with(url.hostname, url.port, nil, :STREAM)
.and_return([addr])
end
def stubbed_hostname(url, hostname: IP_ADDRESS_STUB)
url = parse_url(url)
url.hostname = hostname
url.to_s
end
private
def parse_url(url)
url.is_a?(URI) ? url : URI(url)
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