Add latest changes from gitlab-org/gitlab@master

This commit is contained in:
GitLab Bot 2019-09-26 21:06:29 +00:00
parent e66d6781ef
commit 4309992515
22 changed files with 470 additions and 17 deletions

View File

@ -13,6 +13,7 @@ class ApplicationController < ActionController::Base
include WithPerformanceBar include WithPerformanceBar
include SessionlessAuthentication include SessionlessAuthentication
include ConfirmEmailWarning include ConfirmEmailWarning
include Gitlab::Tracking::ControllerConcern
before_action :authenticate_user! before_action :authenticate_user!
before_action :enforce_terms!, if: :should_enforce_terms? before_action :enforce_terms!, if: :should_enforce_terms?

View File

@ -265,6 +265,10 @@ module ApplicationSettingsHelper
:throttle_unauthenticated_enabled, :throttle_unauthenticated_enabled,
:throttle_unauthenticated_period_in_seconds, :throttle_unauthenticated_period_in_seconds,
:throttle_unauthenticated_requests_per_period, :throttle_unauthenticated_requests_per_period,
:throttle_protected_paths_enabled,
:throttle_protected_paths_period_in_seconds,
:throttle_protected_paths_requests_per_period,
:protected_paths_raw,
:time_tracking_limit_to_hours, :time_tracking_limit_to_hours,
:two_factor_grace_period, :two_factor_grace_period,
:unique_ips_limit_enabled, :unique_ips_limit_enabled,
@ -308,6 +312,10 @@ module ApplicationSettingsHelper
def instance_clusters_enabled? def instance_clusters_enabled?
can?(current_user, :read_cluster, Clusters::Instance.new) can?(current_user, :read_cluster, Clusters::Instance.new)
end end
def omnibus_protected_paths_throttle?
Rack::Attack.throttles.key?('protected paths')
end
end end
ApplicationSettingsHelper.prepend_if_ee('EE::ApplicationSettingsHelper') # rubocop: disable Cop/InjectEnterpriseEditionModule ApplicationSettingsHelper.prepend_if_ee('EE::ApplicationSettingsHelper') # rubocop: disable Cop/InjectEnterpriseEditionModule

View File

@ -210,6 +210,10 @@ class ApplicationSetting < ApplicationRecord
presence: true, presence: true,
if: :static_objects_external_storage_url? if: :static_objects_external_storage_url?
validates :protected_paths,
length: { maximum: 100, message: N_('is too long (maximum is 100 entries)') },
allow_nil: false
SUPPORTED_KEY_TYPES.each do |type| SUPPORTED_KEY_TYPES.each do |type|
validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type } validates :"#{type}_key_restriction", presence: true, key_restriction: { type: type }
end end

View File

@ -4,7 +4,7 @@ module ApplicationSettingImplementation
extend ActiveSupport::Concern extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize include Gitlab::Utils::StrongMemoize
DOMAIN_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace STRING_LIST_SEPARATOR = %r{\s*[,;]\s* # comma or semicolon, optionally surrounded by whitespace
| # or | # or
\s # any whitespace character \s # any whitespace character
| # or | # or
@ -16,6 +16,19 @@ module ApplicationSettingImplementation
FORBIDDEN_KEY_VALUE = KeyRestrictionValidator::FORBIDDEN FORBIDDEN_KEY_VALUE = KeyRestrictionValidator::FORBIDDEN
SUPPORTED_KEY_TYPES = %i[rsa dsa ecdsa ed25519].freeze SUPPORTED_KEY_TYPES = %i[rsa dsa ecdsa ed25519].freeze
DEFAULT_PROTECTED_PATHS = [
'/users/password',
'/users/sign_in',
'/api/v3/session.json',
'/api/v3/session',
'/api/v4/session.json',
'/api/v4/session',
'/users',
'/users/confirmation',
'/unsubscribes/',
'/import/github/personal_access_token'
].freeze
class_methods do class_methods do
def defaults def defaults
{ {
@ -92,6 +105,10 @@ module ApplicationSettingImplementation
throttle_unauthenticated_enabled: false, throttle_unauthenticated_enabled: false,
throttle_unauthenticated_period_in_seconds: 3600, throttle_unauthenticated_period_in_seconds: 3600,
throttle_unauthenticated_requests_per_period: 3600, throttle_unauthenticated_requests_per_period: 3600,
throttle_protected_paths_enabled: false,
throttle_protected_paths_in_seconds: 10,
throttle_protected_paths_per_period: 60,
protected_paths: DEFAULT_PROTECTED_PATHS,
time_tracking_limit_to_hours: false, time_tracking_limit_to_hours: false,
two_factor_grace_period: 48, two_factor_grace_period: 48,
unique_ips_limit_enabled: false, unique_ips_limit_enabled: false,
@ -149,11 +166,11 @@ module ApplicationSettingImplementation
end end
def domain_whitelist_raw=(values) def domain_whitelist_raw=(values)
self.domain_whitelist = domain_strings_to_array(values) self.domain_whitelist = strings_to_array(values)
end end
def domain_blacklist_raw=(values) def domain_blacklist_raw=(values)
self.domain_blacklist = domain_strings_to_array(values) self.domain_blacklist = strings_to_array(values)
end end
def domain_blacklist_file=(file) def domain_blacklist_file=(file)
@ -167,7 +184,7 @@ module ApplicationSettingImplementation
def outbound_local_requests_whitelist_raw=(values) def outbound_local_requests_whitelist_raw=(values)
clear_memoization(:outbound_local_requests_whitelist_arrays) clear_memoization(:outbound_local_requests_whitelist_arrays)
self.outbound_local_requests_whitelist = domain_strings_to_array(values) self.outbound_local_requests_whitelist = strings_to_array(values)
end end
def add_to_outbound_local_requests_whitelist(values_array) def add_to_outbound_local_requests_whitelist(values_array)
@ -200,8 +217,16 @@ module ApplicationSettingImplementation
end end
end end
def protected_paths_raw
array_to_string(self.protected_paths)
end
def protected_paths_raw=(values)
self.protected_paths = strings_to_array(values)
end
def asset_proxy_whitelist=(values) def asset_proxy_whitelist=(values)
values = domain_strings_to_array(values) if values.is_a?(String) values = strings_to_array(values) if values.is_a?(String)
# make sure we always whitelist the running host # make sure we always whitelist the running host
values << Gitlab.config.gitlab.host unless values.include?(Gitlab.config.gitlab.host) values << Gitlab.config.gitlab.host unless values.include?(Gitlab.config.gitlab.host)
@ -316,11 +341,11 @@ module ApplicationSettingImplementation
arr&.join("\n") arr&.join("\n")
end end
def domain_strings_to_array(values) def strings_to_array(values)
return [] unless values return [] unless values
values values
.split(DOMAIN_LIST_SEPARATOR) .split(STRING_LIST_SEPARATOR)
.map(&:strip) .map(&:strip)
.reject(&:empty?) .reject(&:empty?)
.uniq .uniq

View File

@ -0,0 +1,31 @@
= form_for @application_setting, url: network_admin_application_settings_path(anchor: 'js-protected-paths-settings'), html: { class: 'fieldset-form' } do |f|
= form_errors(@application_setting)
%fieldset
- if omnibus_protected_paths_throttle?
.bs-callout.bs-callout-danger
- relative_url_link = 'https://docs.gitlab.com/ee/user/admin_area/settings/protected_paths.html#migrating-from-omnibus'
- relative_url_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: relative_url_link }
= _("Omnibus Protected Paths throttle is active. From 12.4, Omnibus throttle is deprecated and will be removed in a future release. Please read the %{relative_url_link_start}Migrating Protected Paths documentation%{relative_url_link_end}.").html_safe % { relative_url_link_start: relative_url_link_start, relative_url_link_end: '</a>'.html_safe }
.form-group
.form-check
= f.check_box :throttle_protected_paths_enabled, class: 'form-check-input'
= f.label :throttle_protected_paths_enabled, class: 'form-check-label' do
= _('Enable protected paths rate limit')
%span.form-text.text-muted
= _('Helps reduce request volume for protected paths')
.form-group
= f.label :throttle_protected_paths_requests_per_period, 'Max requests per period per user', class: 'label-bold'
= f.number_field :throttle_protected_paths_requests_per_period, class: 'form-control'
.form-group
= f.label :throttle_protected_paths_period_in_seconds, 'Rate limit period in seconds', class: 'label-bold'
= f.number_field :throttle_protected_paths_period_in_seconds, class: 'form-control'
.form-group
= f.label :protected_paths, class: 'label-bold' do
- relative_url_link = 'https://docs.gitlab.com/omnibus/settings/configuration.html#configuring-a-relative-url-for-gitlab'
- relative_url_link_start = '<a href="%{url}" target="_blank" rel="noopener noreferrer">'.html_safe % { url: relative_url_link }
= _('All paths are relative to the GitLab URL. Do not include %{relative_url_link_start}relative URL%{relative_url_link_end}.').html_safe % { relative_url_link_start: relative_url_link_start, relative_url_link_end: '</a>'.html_safe }
= f.text_area :protected_paths_raw, placeholder: '/users/sign_in,/users/password', class: 'form-control', rows: 10
= f.submit 'Save changes', class: 'btn btn-success'

View File

@ -34,3 +34,14 @@
= _('Allow requests to the local network from hooks and services.') = _('Allow requests to the local network from hooks and services.')
.settings-content .settings-content
= render 'outbound' = render 'outbound'
%section.settings.as-protected-paths.no-animate#js-protected-paths-settings{ class: ('expanded' if expanded_by_default?) }
.settings-header
%h4
= _('Protected Paths')
%button.btn.btn-default.js-settings-toggle{ type: 'button' }
= expanded_by_default? ? _('Collapse') : _('Expand')
%p
= _('Configure paths to be protected by Rack Attack. A web server restart is required after changing these settings.')
.settings-content
= render 'protected_paths'

View File

@ -0,0 +1,5 @@
---
title: Allow users to configure protected paths from Admin panel
merge_request: 31246
author:
type: added

View File

@ -12,10 +12,18 @@ ActiveSupport::Notifications.subscribe('rack.attack') do |name, start, finish, r
path: req.fullpath path: req.fullpath
} }
if %w(throttle_authenticated_api throttle_authenticated_web).include? req.env['rack.attack.matched'] throttles_with_user_information = [
:throttle_authenticated_api,
:throttle_authenticated_web,
:throttle_authenticated_protected_paths_api,
:throttle_authenticated_protected_paths_web
]
if throttles_with_user_information.include? req.env['rack.attack.matched'].to_sym
user_id = req.env['rack.attack.match_discriminator'] user_id = req.env['rack.attack.match_discriminator']
user = User.find_by(id: user_id) user = User.find_by(id: user_id)
rack_attack_info[:throttle_type] = req.env['rack.attack.matched']
rack_attack_info[:user_id] = user_id rack_attack_info[:user_id] = user_id
rack_attack_info[:username] = user.username unless user.nil? rack_attack_info[:username] = user.username unless user.nil?
end end

View File

@ -3,6 +3,15 @@ module Gitlab::Throttle
Gitlab::CurrentSettings.current_application_settings Gitlab::CurrentSettings.current_application_settings
end end
def self.protected_paths_enabled?
!self.omnibus_protected_paths_present? &&
self.settings.throttle_protected_paths_enabled?
end
def self.omnibus_protected_paths_present?
Rack::Attack.throttles.key?('protected paths')
end
def self.unauthenticated_options def self.unauthenticated_options
limit_proc = proc { |req| settings.throttle_unauthenticated_requests_per_period } limit_proc = proc { |req| settings.throttle_unauthenticated_requests_per_period }
period_proc = proc { |req| settings.throttle_unauthenticated_period_in_seconds.seconds } period_proc = proc { |req| settings.throttle_unauthenticated_period_in_seconds.seconds }
@ -20,6 +29,13 @@ module Gitlab::Throttle
period_proc = proc { |req| settings.throttle_authenticated_web_period_in_seconds.seconds } period_proc = proc { |req| settings.throttle_authenticated_web_period_in_seconds.seconds }
{ limit: limit_proc, period: period_proc } { limit: limit_proc, period: period_proc }
end end
def self.protected_paths_options
limit_proc = proc { |req| settings.throttle_protected_paths_requests_per_period }
period_proc = proc { |req| settings.throttle_protected_paths_period_in_seconds.seconds }
{ limit: limit_proc, period: period_proc }
end
end end
class Rack::Attack class Rack::Attack
@ -42,6 +58,28 @@ class Rack::Attack
req.authenticated_user_id([:api, :rss, :ics]) req.authenticated_user_id([:api, :rss, :ics])
end end
throttle('throttle_unauthenticated_protected_paths', Gitlab::Throttle.protected_paths_options) do |req|
Gitlab::Throttle.protected_paths_enabled? &&
req.unauthenticated? &&
!req.should_be_skipped? &&
req.protected_path? &&
req.ip
end
throttle('throttle_authenticated_protected_paths_api', Gitlab::Throttle.protected_paths_options) do |req|
Gitlab::Throttle.protected_paths_enabled? &&
req.api_request? &&
req.protected_path? &&
req.authenticated_user_id([:api])
end
throttle('throttle_authenticated_protected_paths_web', Gitlab::Throttle.protected_paths_options) do |req|
Gitlab::Throttle.protected_paths_enabled? &&
req.web_request? &&
req.protected_path? &&
req.authenticated_user_id([:api, :rss, :ics])
end
class Request class Request
def unauthenticated? def unauthenticated?
!authenticated_user_id([:api, :rss, :ics]) !authenticated_user_id([:api, :rss, :ics])
@ -66,6 +104,24 @@ class Rack::Attack
def web_request? def web_request?
!api_request? !api_request?
end end
def protected_path?
!protected_path_regex.nil?
end
def protected_path_regex
path =~ protected_paths_regex
end
private
def protected_paths
Gitlab::CurrentSettings.current_application_settings.protected_paths
end
def protected_paths_regex
Regexp.union(protected_paths.map { |path| /\A#{Regexp.escape(path)}/ })
end
end end
end end

View File

@ -0,0 +1,25 @@
# frozen_string_literal: true
class AddThrottleProtectedPathColumns < ActiveRecord::Migration[5.2]
DOWNTIME = false
DEFAULT_PROTECTED_PATHS = [
'/users/password',
'/users/sign_in',
'/api/v3/session.json',
'/api/v3/session',
'/api/v4/session.json',
'/api/v4/session',
'/users',
'/users/confirmation',
'/unsubscribes/',
'/import/github/personal_access_token'
]
def change
add_column :application_settings, :throttle_protected_paths_enabled, :boolean, default: true, null: false
add_column :application_settings, :throttle_protected_paths_requests_per_period, :integer, default: 10, null: false
add_column :application_settings, :throttle_protected_paths_period_in_seconds, :integer, default: 60, null: false
add_column :application_settings, :protected_paths, :string, array: true, limit: 255, default: DEFAULT_PROTECTED_PATHS
end
end

View File

@ -322,6 +322,10 @@ ActiveRecord::Schema.define(version: 2019_09_26_041216) do
t.string "encrypted_asset_proxy_secret_key_iv" t.string "encrypted_asset_proxy_secret_key_iv"
t.string "static_objects_external_storage_url", limit: 255 t.string "static_objects_external_storage_url", limit: 255
t.string "static_objects_external_storage_auth_token", limit: 255 t.string "static_objects_external_storage_auth_token", limit: 255
t.boolean "throttle_protected_paths_enabled", default: true, null: false
t.integer "throttle_protected_paths_requests_per_period", default: 10, null: false
t.integer "throttle_protected_paths_period_in_seconds", default: 60, null: false
t.string "protected_paths", limit: 255, default: ["/users/password", "/users/sign_in", "/api/v3/session.json", "/api/v3/session", "/api/v4/session.json", "/api/v4/session", "/users", "/users/confirmation", "/unsubscribes/", "/import/github/personal_access_token"], array: true
t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id" t.index ["custom_project_templates_group_id"], name: "index_application_settings_on_custom_project_templates_group_id"
t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id" t.index ["file_template_project_id"], name: "index_application_settings_on_file_template_project_id"
t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id" t.index ["instance_administration_project_id"], name: "index_applicationsettings_on_instance_administration_project_id"

View File

@ -33,8 +33,13 @@ the `author` field. GitLab team members **should not**.
## What warrants a changelog entry? ## What warrants a changelog entry?
- Any change that introduces a database migration **must** have a changelog entry.
- Any user-facing change **should** have a changelog entry. Example: "GitLab now - Any user-facing change **should** have a changelog entry. Example: "GitLab now
uses system fonts for all text." uses system fonts for all text."
- Performance improvements **should** have a changelog entry.
- _Any_ contribution from a community member, no matter how small, **may** have
a changelog entry regardless of these guidelines if the contributor wants one.
Example: "Fixed a typo on the search results page."
- Any docs-only changes **should not** have a changelog entry. - Any docs-only changes **should not** have a changelog entry.
- Any change behind a feature flag **should not** have a changelog entry. The entry should be added [in the merge request removing the feature flags](feature_flags/development.md). - Any change behind a feature flag **should not** have a changelog entry. The entry should be added [in the merge request removing the feature flags](feature_flags/development.md).
- A fix for a regression introduced and then fixed in the same release (i.e., - A fix for a regression introduced and then fixed in the same release (i.e.,
@ -43,12 +48,6 @@ the `author` field. GitLab team members **should not**.
- Any developer-facing change (e.g., refactoring, technical debt remediation, - Any developer-facing change (e.g., refactoring, technical debt remediation,
test suite changes) **should not** have a changelog entry. Example: "Reduce test suite changes) **should not** have a changelog entry. Example: "Reduce
database records created during Cycle Analytics model spec." database records created during Cycle Analytics model spec."
- _Any_ contribution from a community member, no matter how small, **may** have
a changelog entry regardless of these guidelines if the contributor wants one.
Example: "Fixed a typo on the search results page."
- Performance improvements **should** have a changelog entry.
- Any change that introduces a database migration **must** have a
changelog entry.
## Writing good changelog entries ## Writing good changelog entries

View File

@ -24,6 +24,7 @@ module Gitlab
# Allow2Ban.filter will return false if this IP has not failed too often yet # Allow2Ban.filter will return false if this IP has not failed too often yet
@banned = Rack::Attack::Allow2Ban.filter(ip, config) do @banned = Rack::Attack::Allow2Ban.filter(ip, config) do
# If we return false here, the failure for this IP is ignored by Allow2Ban # If we return false here, the failure for this IP is ignored by Allow2Ban
# If we return true here, the count for the IP is incremented.
ip_can_be_banned? ip_can_be_banned?
end end
end end

View File

@ -6,6 +6,17 @@ module Gitlab
module Tracking module Tracking
SNOWPLOW_NAMESPACE = 'gl' SNOWPLOW_NAMESPACE = 'gl'
module ControllerConcern
extend ActiveSupport::Concern
protected
def track_event(action = action_name, **args)
category = args.delete(:category) || self.class.name
Gitlab::Tracking.event(category, action.to_s, **args)
end
end
class << self class << self
def enabled? def enabled?
Gitlab::CurrentSettings.snowplow_enabled? Gitlab::CurrentSettings.snowplow_enabled?

View File

@ -1270,6 +1270,9 @@ msgstr ""
msgid "All merge conflicts were resolved. The merge request can now be merged." msgid "All merge conflicts were resolved. The merge request can now be merged."
msgstr "" msgstr ""
msgid "All paths are relative to the GitLab URL. Do not include %{relative_url_link_start}relative URL%{relative_url_link_end}."
msgstr ""
msgid "All projects" msgid "All projects"
msgstr "" msgstr ""
@ -4038,6 +4041,9 @@ msgstr ""
msgid "Configure limits for web and API requests." msgid "Configure limits for web and API requests."
msgstr "" msgstr ""
msgid "Configure paths to be protected by Rack Attack. A web server restart is required after changing these settings."
msgstr ""
msgid "Configure push mirrors." msgid "Configure push mirrors."
msgstr "" msgstr ""
@ -5680,6 +5686,9 @@ msgstr ""
msgid "Enable or disable version check and usage ping." msgid "Enable or disable version check and usage ping."
msgstr "" msgstr ""
msgid "Enable protected paths rate limit"
msgstr ""
msgid "Enable proxy" msgid "Enable proxy"
msgstr "" msgstr ""
@ -8058,6 +8067,9 @@ msgstr ""
msgid "Helps prevent bots from creating accounts." msgid "Helps prevent bots from creating accounts."
msgstr "" msgstr ""
msgid "Helps reduce request volume for protected paths"
msgstr ""
msgid "Hide archived projects" msgid "Hide archived projects"
msgstr "" msgstr ""
@ -10706,6 +10718,9 @@ msgstr ""
msgid "OmniAuth" msgid "OmniAuth"
msgstr "" msgstr ""
msgid "Omnibus Protected Paths throttle is active. From 12.4, Omnibus throttle is deprecated and will be removed in a future release. Please read the %{relative_url_link_start}Migrating Protected Paths documentation%{relative_url_link_end}."
msgstr ""
msgid "Onboarding" msgid "Onboarding"
msgstr "" msgstr ""
@ -12571,6 +12586,9 @@ msgstr ""
msgid "Protected Environments" msgid "Protected Environments"
msgstr "" msgstr ""
msgid "Protected Paths"
msgstr ""
msgid "Protected Tag" msgid "Protected Tag"
msgstr "" msgstr ""
@ -18958,6 +18976,9 @@ msgstr ""
msgid "is not an email you own" msgid "is not an email you own"
msgstr "" msgstr ""
msgid "is too long (maximum is 100 entries)"
msgstr ""
msgid "is too long (maximum is 1000 entries)" msgid "is too long (maximum is 1000 entries)"
msgstr "" msgstr ""

View File

@ -56,6 +56,8 @@ describe ApplicationController do
end end
end end
it_behaves_like 'a Trackable Controller'
describe '#add_gon_variables' do describe '#add_gon_variables' do
before do before do
Gon.clear Gon.clear

View File

@ -470,6 +470,8 @@ describe 'Issues' do
expect(page).to have_content 'None' expect(page).to have_content 'None'
end end
wait_for_requests
expect(issue.reload.assignees).to be_empty expect(issue.reload.assignees).to be_empty
end end

View File

@ -51,6 +51,10 @@ describe ApplicationSetting do
it { is_expected.to allow_value(nil).for(:static_objects_external_storage_url) } it { is_expected.to allow_value(nil).for(:static_objects_external_storage_url) }
it { is_expected.to allow_value(http).for(:static_objects_external_storage_url) } it { is_expected.to allow_value(http).for(:static_objects_external_storage_url) }
it { is_expected.to allow_value(https).for(:static_objects_external_storage_url) } it { is_expected.to allow_value(https).for(:static_objects_external_storage_url) }
it { is_expected.to allow_value(['/example'] * 100).for(:protected_paths) }
it { is_expected.not_to allow_value(['/example'] * 101).for(:protected_paths) }
it { is_expected.not_to allow_value(nil).for(:protected_paths) }
it { is_expected.to allow_value([]).for(:protected_paths) }
context "when user accepted let's encrypt terms of service" do context "when user accepted let's encrypt terms of service" do
before do before do

View File

@ -12,7 +12,9 @@ describe 'Rack Attack global throttles' do
throttle_authenticated_api_requests_per_period: 100, throttle_authenticated_api_requests_per_period: 100,
throttle_authenticated_api_period_in_seconds: 1, throttle_authenticated_api_period_in_seconds: 1,
throttle_authenticated_web_requests_per_period: 100, throttle_authenticated_web_requests_per_period: 100,
throttle_authenticated_web_period_in_seconds: 1 throttle_authenticated_web_period_in_seconds: 1,
throttle_authenticated_protected_paths_request_per_period: 100,
throttle_authenticated_protected_paths_in_seconds: 1
} }
end end
@ -35,6 +37,10 @@ describe 'Rack Attack global throttles' do
let(:url_api_internal) { '/api/v4/internal/check' } let(:url_api_internal) { '/api/v4/internal/check' }
before do before do
# Disabling protected paths throttle, otherwise requests to
# '/users/sign_in' are caught by this throttle.
settings_to_set[:throttle_protected_paths_enabled] = false
# Set low limits # Set low limits
settings_to_set[:throttle_unauthenticated_requests_per_period] = requests_per_period settings_to_set[:throttle_unauthenticated_requests_per_period] = requests_per_period
settings_to_set[:throttle_unauthenticated_period_in_seconds] = period_in_seconds settings_to_set[:throttle_unauthenticated_period_in_seconds] = period_in_seconds
@ -203,6 +209,159 @@ describe 'Rack Attack global throttles' do
it_behaves_like 'rate-limited web authenticated requests' it_behaves_like 'rate-limited web authenticated requests'
end end
describe 'protected paths' do
context 'unauthenticated requests' do
let(:protected_path_that_does_not_require_authentication) do
'/users/confirmation'
end
before do
settings_to_set[:throttle_protected_paths_requests_per_period] = requests_per_period # 1
settings_to_set[:throttle_protected_paths_period_in_seconds] = period_in_seconds # 10_000
end
context 'when protected paths throttle is disabled' do
before do
settings_to_set[:throttle_protected_paths_enabled] = false
stub_application_setting(settings_to_set)
end
it 'allows requests over the rate limit' do
(1 + requests_per_period).times do
get protected_path_that_does_not_require_authentication
expect(response).to have_http_status 200
end
end
end
context 'when protected paths throttle is enabled' do
before do
settings_to_set[:throttle_protected_paths_enabled] = true
stub_application_setting(settings_to_set)
end
it 'rejects requests over the rate limit' do
requests_per_period.times do
get protected_path_that_does_not_require_authentication
expect(response).to have_http_status 200
end
expect_rejection { get protected_path_that_does_not_require_authentication }
end
context 'when Omnibus throttle is present' do
before do
allow(Gitlab::Throttle)
.to receive(:omnibus_protected_paths_present?).and_return(true)
end
it 'allows requests over the rate limit' do
(1 + requests_per_period).times do
get protected_path_that_does_not_require_authentication
expect(response).to have_http_status 200
end
end
end
end
end
context 'API requests authenticated with personal access token', :api do
let(:user) { create(:user) }
let(:token) { create(:personal_access_token, user: user) }
let(:other_user) { create(:user) }
let(:other_user_token) { create(:personal_access_token, user: other_user) }
let(:throttle_setting_prefix) { 'throttle_protected_paths' }
let(:api_partial_url) { '/users' }
let(:protected_paths) do
[
'/api/v4/users'
]
end
before do
settings_to_set[:protected_paths] = protected_paths
stub_application_setting(settings_to_set)
end
context 'with the token in the query string' do
let(:get_args) { [api(api_partial_url, personal_access_token: token)] }
let(:other_user_get_args) { [api(api_partial_url, personal_access_token: other_user_token)] }
it_behaves_like 'rate-limited token-authenticated requests'
end
context 'with the token in the headers' do
let(:get_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(token)) }
let(:other_user_get_args) { api_get_args_with_token_headers(api_partial_url, personal_access_token_headers(other_user_token)) }
it_behaves_like 'rate-limited token-authenticated requests'
end
context 'when Omnibus throttle is present' do
let(:get_args) { [api(api_partial_url, personal_access_token: token)] }
let(:other_user_get_args) { [api(api_partial_url, personal_access_token: other_user_token)] }
before do
settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds
settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true
stub_application_setting(settings_to_set)
allow(Gitlab::Throttle)
.to receive(:omnibus_protected_paths_present?).and_return(true)
end
it 'allows requests over the rate limit' do
(1 + requests_per_period).times do
get(*get_args)
expect(response).to have_http_status 200
end
end
end
end
describe 'web requests authenticated with regular login' do
let(:throttle_setting_prefix) { 'throttle_protected_paths' }
let(:user) { create(:user) }
let(:url_that_requires_authentication) { '/dashboard/snippets' }
let(:protected_paths) do
[
url_that_requires_authentication
]
end
before do
settings_to_set[:protected_paths] = protected_paths
stub_application_setting(settings_to_set)
end
it_behaves_like 'rate-limited web authenticated requests'
context 'when Omnibus throttle is present' do
before do
settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
settings_to_set[:"#{throttle_setting_prefix}_period_in_seconds"] = period_in_seconds
settings_to_set[:"#{throttle_setting_prefix}_enabled"] = true
stub_application_setting(settings_to_set)
allow(Gitlab::Throttle)
.to receive(:omnibus_protected_paths_present?).and_return(true)
login_as(user)
end
it 'allows requests over the rate limit' do
(1 + requests_per_period).times do
get url_that_requires_authentication
expect(response).to have_http_status 200
end
end
end
end
end
def api_get_args_with_token_headers(partial_url, token_headers) def api_get_args_with_token_headers(partial_url, token_headers)
["/api/#{API::API.version}#{partial_url}", params: nil, headers: token_headers] ["/api/#{API::API.version}#{partial_url}", params: nil, headers: token_headers]
end end

View File

@ -295,4 +295,26 @@ describe ApplicationSettings::UpdateService do
expect(application_settings.raw_blob_request_limit).to eq(600) expect(application_settings.raw_blob_request_limit).to eq(600)
end end
end end
context 'when protected path settings are passed' do
let(:params) do
{
throttle_protected_paths_enabled: 1,
throttle_protected_paths_period_in_seconds: 600,
throttle_protected_paths_requests_per_period: 100,
protected_paths_raw: "/users/password\r\n/users/sign_in\r\n"
}
end
it 'updates protected path settings' do
subject.execute
application_settings.reload
expect(application_settings.throttle_protected_paths_enabled).to be_truthy
expect(application_settings.throttle_protected_paths_period_in_seconds).to eq(600)
expect(application_settings.throttle_protected_paths_requests_per_period).to eq(100)
expect(application_settings.protected_paths).to eq(['/users/password', '/users/sign_in'])
end
end
end end

View File

@ -8,6 +8,14 @@
# * period_in_seconds # * period_in_seconds
# * period # * period
shared_examples_for 'rate-limited token-authenticated requests' do shared_examples_for 'rate-limited token-authenticated requests' do
let(:throttle_types) do
{
"throttle_protected_paths" => "throttle_authenticated_protected_paths_api",
"throttle_authenticated_api" => "throttle_authenticated_api",
"throttle_authenticated_web" => "throttle_authenticated_web"
}
end
before do before do
# Set low limits # Set low limits
settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period settings_to_set[:"#{throttle_setting_prefix}_requests_per_period"] = requests_per_period
@ -84,7 +92,8 @@ shared_examples_for 'rate-limited token-authenticated requests' do
request_method: 'GET', request_method: 'GET',
path: get_args.first, path: get_args.first,
user_id: user.id, user_id: user.id,
username: user.username username: user.username,
throttle_type: throttle_types[throttle_setting_prefix]
} }
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once
@ -116,6 +125,13 @@ end
# * period_in_seconds # * period_in_seconds
# * period # * period
shared_examples_for 'rate-limited web authenticated requests' do shared_examples_for 'rate-limited web authenticated requests' do
let(:throttle_types) do
{
"throttle_protected_paths" => "throttle_authenticated_protected_paths_web",
"throttle_authenticated_web" => "throttle_authenticated_web"
}
end
before do before do
login_as(user) login_as(user)
@ -196,7 +212,8 @@ shared_examples_for 'rate-limited web authenticated requests' do
request_method: 'GET', request_method: 'GET',
path: '/dashboard/snippets', path: '/dashboard/snippets',
user_id: user.id, user_id: user.id,
username: user.username username: user.username,
throttle_type: throttle_types[throttle_setting_prefix]
} }
expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once expect(Gitlab::AuthLogger).to receive(:error).with(arguments).once

View File

@ -0,0 +1,37 @@
# frozen_string_literal: true
shared_examples 'a Trackable Controller' do
describe '#track_event' do
before do
sign_in user
end
context 'with no params' do
controller(described_class) do
def index
track_event
head :ok
end
end
it 'tracks the action name' do
expect(Gitlab::Tracking).to receive(:event).with('AnonymousController', 'index', {})
get :index
end
end
context 'with params' do
controller(described_class) do
def index
track_event('some_event', category: 'SomeCategory', label: 'errorlabel')
head :ok
end
end
it 'tracks with the specified param' do
expect(Gitlab::Tracking).to receive(:event).with('SomeCategory', 'some_event', label: 'errorlabel')
get :index
end
end
end
end