Commit 6cec5a6a authored by Robert Speicher's avatar Robert Speicher

Merge branch 'master' of dev.gitlab.org:gitlab/gitlab-ee

parents 5d639b2d bf0d137e
Please view this file on the master branch, on stable branches it's out of date. Please view this file on the master branch, on stable branches it's out of date.
## 12.9.1 (2020-03-26)
### Security (1 change)
- Add NPM package versions SemVer validation.
## 12.9.0 (2020-03-22) ## 12.9.0 (2020-03-22)
### Removed (1 change) ### Removed (1 change)
......
...@@ -2,6 +2,32 @@ ...@@ -2,6 +2,32 @@
documentation](doc/development/changelog.md) for instructions on adding your own documentation](doc/development/changelog.md) for instructions on adding your own
entry. entry.
## 12.9.1 (2020-03-26)
### Security (16 changes)
- Add permission check for pipeline status of MR.
- Ignore empty remote_id params from Workhorse accelerated uploads.
- External user can not create personal snippet through API.
- Prevent malicious entry for group name.
- Restrict mirroring changes to admins only when mirroring is disabled.
- Reject all container registry requests from blocked users.
- Deny localhost requests on fogbugz importer.
- Redact notes in moved confidential issues.
- Fix UploadRewriter Path Traversal vulnerability.
- Block hotlinking to repository archives.
- Restrict access to project pipeline metrics reports.
- vulnerability_feedback records should be restricted to a dev role and above.
- Exclude Carrierwave remote URL methods from import.
- Update Nokogiri to fix CVE-2020-7595.
- Prevent updating trigger by other maintainers.
- Fix XSS vulnerability in `admin/email` "Recipient Group" dropdown.
### Fixed (1 change)
- Fix updating the authorized_keys file. !27798
## 12.9.0 (2020-03-22) ## 12.9.0 (2020-03-22)
### Security (1 change) ### Security (1 change)
......
...@@ -652,7 +652,7 @@ GEM ...@@ -652,7 +652,7 @@ GEM
netrc (0.11.0) netrc (0.11.0)
nio4r (2.5.2) nio4r (2.5.2)
no_proxy_fix (0.1.2) no_proxy_fix (0.1.2)
nokogiri (1.10.7) nokogiri (1.10.8)
mini_portile2 (~> 2.4.0) mini_portile2 (~> 2.4.0)
nokogumbo (1.5.0) nokogumbo (1.5.0)
nokogiri nokogiri
......
...@@ -45,8 +45,19 @@ export const updateExistingFrequentItem = (frequentItem, item) => { ...@@ -45,8 +45,19 @@ export const updateExistingFrequentItem = (frequentItem, item) => {
}; };
}; };
export const sanitizeItem = item => ({ export const sanitizeItem = item => {
// Only sanitize if the key exists on the item
const maybeSanitize = key => {
if (!Object.prototype.hasOwnProperty.call(item, key)) {
return {};
}
return { [key]: sanitize(item[key].toString(), { allowedTags: [] }) };
};
return {
...item, ...item,
name: sanitize(item.name.toString(), { allowedTags: [] }), ...maybeSanitize('name'),
namespace: sanitize(item.namespace.toString(), { allowedTags: [] }), ...maybeSanitize('namespace'),
}); };
};
# frozen_string_literal: true
module HotlinkInterceptor
extend ActiveSupport::Concern
def intercept_hotlinking!
return render_406 if Gitlab::HotlinkingDetector.intercept_hotlinking?(request)
end
private
def render_406
head :not_acceptable
end
end
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
class Import::FogbugzController < Import::BaseController class Import::FogbugzController < Import::BaseController
before_action :verify_fogbugz_import_enabled before_action :verify_fogbugz_import_enabled
before_action :user_map, only: [:new_user_map, :create_user_map] before_action :user_map, only: [:new_user_map, :create_user_map]
before_action :verify_blocked_uri, only: :callback
rescue_from Fogbugz::AuthenticationException, with: :fogbugz_unauthorized rescue_from Fogbugz::AuthenticationException, with: :fogbugz_unauthorized
...@@ -106,4 +107,21 @@ class Import::FogbugzController < Import::BaseController ...@@ -106,4 +107,21 @@ class Import::FogbugzController < Import::BaseController
def verify_fogbugz_import_enabled def verify_fogbugz_import_enabled
render_404 unless fogbugz_import_enabled? render_404 unless fogbugz_import_enabled?
end end
def verify_blocked_uri
Gitlab::UrlBlocker.validate!(
params[:uri],
{
allow_localhost: allow_local_requests?,
allow_local_network: allow_local_requests?,
schemes: %w(http https)
}
)
rescue Gitlab::UrlBlocker::BlockedUrlError => e
redirect_to new_import_fogbugz_url, alert: _('Specified URL cannot be used: "%{reason}"') % { reason: e.message }
end
def allow_local_requests?
Gitlab::CurrentSettings.allow_local_requests_from_web_hooks_and_services?
end
end end
...@@ -67,7 +67,7 @@ class Projects::MirrorsController < Projects::ApplicationController ...@@ -67,7 +67,7 @@ class Projects::MirrorsController < Projects::ApplicationController
end end
def check_mirror_available! def check_mirror_available!
Gitlab::CurrentSettings.current_application_settings.mirror_available || current_user&.admin? render_404 unless can?(current_user, :admin_remote_mirror, project)
end end
def mirror_params_attributes def mirror_params_attributes
......
...@@ -4,12 +4,14 @@ class Projects::RepositoriesController < Projects::ApplicationController ...@@ -4,12 +4,14 @@ class Projects::RepositoriesController < Projects::ApplicationController
include ExtractsPath include ExtractsPath
include StaticObjectExternalStorage include StaticObjectExternalStorage
include Gitlab::RateLimitHelpers include Gitlab::RateLimitHelpers
include HotlinkInterceptor
prepend_before_action(only: [:archive]) { authenticate_sessionless_user!(:archive) } prepend_before_action(only: [:archive]) { authenticate_sessionless_user!(:archive) }
# Authorize # Authorize
before_action :require_non_empty_project, except: :create before_action :require_non_empty_project, except: :create
before_action :archive_rate_limit!, only: :archive before_action :archive_rate_limit!, only: :archive
before_action :intercept_hotlinking!, only: :archive
before_action :assign_archive_vars, only: :archive before_action :assign_archive_vars, only: :archive
before_action :assign_append_sha, only: :archive before_action :assign_append_sha, only: :archive
before_action :authorize_download_code! before_action :authorize_download_code!
......
...@@ -70,6 +70,9 @@ class Group < Namespace ...@@ -70,6 +70,9 @@ class Group < Namespace
validates :variables, variable_duplicates: true validates :variables, variable_duplicates: true
validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 } validates :two_factor_grace_period, presence: true, numericality: { greater_than_or_equal_to: 0 }
validates :name,
format: { with: Gitlab::Regex.group_name_regex,
message: Gitlab::Regex.group_name_regex_message }
add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required } add_authentication_token_field :runners_token, encrypted: -> { Feature.enabled?(:groups_tokens_optional_encryption, default_enabled: true) ? :optional : :required }
......
...@@ -326,9 +326,7 @@ class Issue < ApplicationRecord ...@@ -326,9 +326,7 @@ class Issue < ApplicationRecord
true true
elsif project.owner == user elsif project.owner == user
true true
elsif confidential? elsif confidential? && !assignee_or_author?(user)
author == user ||
assignees.include?(user) ||
project.team.member?(user, Gitlab::Access::REPORTER) project.team.member?(user, Gitlab::Access::REPORTER)
else else
project.public? || project.public? ||
......
...@@ -53,7 +53,7 @@ class MergeRequestPollWidgetEntity < Grape::Entity ...@@ -53,7 +53,7 @@ class MergeRequestPollWidgetEntity < Grape::Entity
# CI related # CI related
expose :has_ci?, as: :has_ci expose :has_ci?, as: :has_ci
expose :ci_status do |merge_request| expose :ci_status, if: -> (mr, _) { presenter(mr).can_read_pipeline? } do |merge_request|
presenter(merge_request).ci_status presenter(merge_request).ci_status
end end
......
...@@ -318,7 +318,7 @@ module ObjectStorage ...@@ -318,7 +318,7 @@ module ObjectStorage
def cache!(new_file = sanitized_file) def cache!(new_file = sanitized_file)
# We intercept ::UploadedFile which might be stored on remote storage # We intercept ::UploadedFile which might be stored on remote storage
# We use that for "accelerated" uploads, where we store result on remote storage # We use that for "accelerated" uploads, where we store result on remote storage
if new_file.is_a?(::UploadedFile) && new_file.remote_id if new_file.is_a?(::UploadedFile) && new_file.remote_id.present?
return cache_remote_file!(new_file.remote_id, new_file.original_filename) return cache_remote_file!(new_file.remote_id, new_file.original_filename)
end end
......
- expanded = expanded_by_default? - expanded = expanded_by_default?
- protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|') - protocols = Gitlab::UrlSanitizer::ALLOWED_SCHEMES.join('|')
- mirror_settings_enabled = can?(current_user, :admin_remote_mirror, @project)
- mirror_settings_class = "#{'expanded' if expanded} #{'js-mirror-settings' if mirror_settings_enabled}".strip
%section.settings.project-mirror-settings.js-mirror-settings.no-animate#js-push-remote-settings{ class: ('expanded' if expanded), data: { qa_selector: 'mirroring_repositories_settings_section' } } %section.settings.project-mirror-settings.no-animate#js-push-remote-settings{ class: mirror_settings_class, data: { qa_selector: 'mirroring_repositories_settings_section' } }
.settings-header .settings-header
%h4= _('Mirroring repositories') %h4= _('Mirroring repositories')
%button.btn.js-settings-toggle %button.btn.js-settings-toggle
...@@ -11,6 +13,7 @@ ...@@ -11,6 +13,7 @@
= link_to _('Read more'), help_page_path('workflow/repository_mirroring'), target: '_blank' = link_to _('Read more'), help_page_path('workflow/repository_mirroring'), target: '_blank'
.settings-content .settings-content
- if mirror_settings_enabled
= form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'new-password', data: mirrors_form_data_attributes } do |f| = form_for @project, url: project_mirror_path(@project), html: { class: 'gl-show-field-errors js-mirror-form', autocomplete: 'new-password', data: mirrors_form_data_attributes } do |f|
.panel.panel-default .panel.panel-default
.panel-body .panel-body
...@@ -31,6 +34,11 @@ ...@@ -31,6 +34,11 @@
.panel-footer .panel-footer
= f.submit _('Mirror repository'), class: 'btn btn-success js-mirror-submit qa-mirror-repository-button', name: :update_remote_mirror = f.submit _('Mirror repository'), class: 'btn btn-success js-mirror-submit qa-mirror-repository-button', name: :update_remote_mirror
- else
.gl-alert.gl-alert-info{ role: 'alert' }
= sprite_icon('information-o', size: 16, css_class: 'gl-icon gl-alert-icon gl-alert-icon-no-title')
.gl-alert-body
= _('Mirror settings are only available to GitLab administrators.')
.panel.panel-default .panel.panel-default
.table-responsive .table-responsive
...@@ -61,8 +69,10 @@ ...@@ -61,8 +69,10 @@
- if mirror.last_error.present? - if mirror.last_error.present?
.badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true', qa_selector: 'mirror_error_badge' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error') .badge.mirror-error-badge{ data: { toggle: 'tooltip', html: 'true', qa_selector: 'mirror_error_badge' }, title: html_escape(mirror.last_error.try(:strip)) }= _('Error')
%td %td
- if mirror_settings_enabled
.btn-group.mirror-actions-group.pull-right{ role: 'group' } .btn-group.mirror-actions-group.pull-right{ role: 'group' }
- if mirror.ssh_key_auth? - if mirror.ssh_key_auth?
= clipboard_button(text: mirror.ssh_public_key, class: 'btn btn-default', title: _('Copy SSH public key'), qa_selector: 'copy_public_key_button') = clipboard_button(text: mirror.ssh_public_key, class: 'btn btn-default', title: _('Copy SSH public key'), qa_selector: 'copy_public_key_button')
= render 'shared/remote_mirror_update_button', remote_mirror: mirror = render 'shared/remote_mirror_update_button', remote_mirror: mirror
%button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o') %button.js-delete-mirror.qa-delete-mirror.rspec-delete-mirror.btn.btn-danger{ type: 'button', data: { mirror_id: mirror.id, toggle: 'tooltip', container: 'body' }, title: _('Remove') }= icon('trash-o')
---
title: Fix updating the authorized_keys file
merge_request: 27798
author:
type: fixed
import $ from 'jquery'; import $ from 'jquery';
import Api from '~/api'; import Api from '~/api';
import { sprintf, __ } from '~/locale'; import { sprintf, __ } from '~/locale';
import { sanitizeItem } from '~/frequent_items/utils';
const formatResult = selectedItem => { const formatResult = selectedItem => {
if (selectedItem.path_with_namespace) { if (selectedItem.path_with_namespace) {
...@@ -38,7 +39,7 @@ const AdminEmailSelect = () => { ...@@ -38,7 +39,7 @@ const AdminEmailSelect = () => {
const all = { const all = {
id: 'all', id: 'all',
}; };
const data = [all].concat(groups, projects.data); const data = [all].concat(groups, projects.data).map(sanitizeItem);
return query.callback({ return query.callback({
results: data, results: data,
}); });
......
...@@ -16,7 +16,8 @@ module EE ...@@ -16,7 +16,8 @@ module EE
before_action :whitelist_query_limiting_ee_merge, only: [:merge] before_action :whitelist_query_limiting_ee_merge, only: [:merge]
before_action :whitelist_query_limiting_ee_show, only: [:show] before_action :whitelist_query_limiting_ee_show, only: [:show]
before_action :authorize_read_pipeline!, only: [:container_scanning_reports, :dependency_scanning_reports, :sast_reports, :dast_reports] before_action :authorize_read_pipeline!, only: [:container_scanning_reports, :dependency_scanning_reports,
:sast_reports, :dast_reports, :metrics_reports]
end end
def approve def approve
......
...@@ -30,6 +30,7 @@ class Packages::Package < ApplicationRecord ...@@ -30,6 +30,7 @@ class Packages::Package < ApplicationRecord
validate :valid_conan_package_recipe, if: :conan? validate :valid_conan_package_recipe, if: :conan?
validate :valid_npm_package_name, if: :npm? validate :valid_npm_package_name, if: :npm?
validate :package_already_taken, if: :npm? validate :package_already_taken, if: :npm?
validates :version, format: { with: Gitlab::Regex.semver_regex }, if: :npm?
enum package_type: { maven: 1, npm: 2, conan: 3, nuget: 4, pypi: 5 } enum package_type: { maven: 1, npm: 2, conan: 3, nuget: 4, pypi: 5 }
......
...@@ -170,6 +170,7 @@ module EE ...@@ -170,6 +170,7 @@ module EE
rule { can?(:developer_access) }.policy do rule { can?(:developer_access) }.policy do
enable :admin_board enable :admin_board
enable :read_vulnerability_feedback
enable :create_vulnerability_feedback enable :create_vulnerability_feedback
enable :destroy_vulnerability_feedback enable :destroy_vulnerability_feedback
enable :update_vulnerability_feedback enable :update_vulnerability_feedback
...@@ -185,8 +186,6 @@ module EE ...@@ -185,8 +186,6 @@ module EE
rule { can?(:public_access) }.enable :read_package rule { can?(:public_access) }.enable :read_package
rule { can?(:read_build) & can?(:download_code) }.enable :read_security_findings
rule { security_dashboard_enabled & can?(:developer_access) }.enable :read_vulnerability rule { security_dashboard_enabled & can?(:developer_access) }.enable :read_vulnerability
rule { can?(:read_merge_request) & can?(:read_pipeline) }.enable :read_merge_train rule { can?(:read_merge_request) & can?(:read_pipeline) }.enable :read_merge_train
...@@ -201,8 +200,6 @@ module EE ...@@ -201,8 +200,6 @@ module EE
rule { threat_monitoring_enabled & (auditor | can?(:developer_access)) }.enable :read_threat_monitoring rule { threat_monitoring_enabled & (auditor | can?(:developer_access)) }.enable :read_threat_monitoring
rule { can?(:read_security_findings) }.enable :read_vulnerability_feedback
rule { dependency_scanning_enabled & can?(:download_code) }.enable :read_dependencies rule { dependency_scanning_enabled & can?(:download_code) }.enable :read_dependencies
rule { license_scanning_enabled & can?(:download_code) }.enable :read_licenses rule { license_scanning_enabled & can?(:download_code) }.enable :read_licenses
......
...@@ -43,6 +43,11 @@ module EE ...@@ -43,6 +43,11 @@ module EE
maven_app_name_regex maven_app_name_regex
end end
def semver_regex
# see the official regex: https://semver.org/#is-there-a-suggested-regular-expression-regex-to-check-a-semver-string
@semver_regex ||= %r{\A(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?\z}.freeze
end
def feature_flag_regex def feature_flag_regex
/\A[a-z]([-_a-z0-9]*[a-z0-9])?\z/ /\A[a-z]([-_a-z0-9]*[a-z0-9])?\z/
end end
......
...@@ -90,6 +90,29 @@ shared_examples 'approvals' do ...@@ -90,6 +90,29 @@ shared_examples 'approvals' do
end end
end end
shared_examples 'authorize read pipeline' do
context 'public project with private builds' do
let(:comparison_status) { {} }
let(:project) { create(:project, :public, :builds_private) }
it 'restricts access to signed out users' do
sign_out user
subject
expect(response).to have_gitlab_http_status(:not_found)
end
it 'restricts access to other users' do
sign_in create(:user)
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
describe Projects::MergeRequestsController do describe Projects::MergeRequestsController do
include ProjectForksHelper include ProjectForksHelper
...@@ -462,20 +485,7 @@ describe Projects::MergeRequestsController do ...@@ -462,20 +485,7 @@ describe Projects::MergeRequestsController do
end end
end end
context 'public project with private builds' do it_behaves_like 'authorize read pipeline'
let(:comparison_status) { {} }
let(:project) { create(:project, :public, :builds_private) }
before do
sign_out user
end
it 'restricts unauthorized access' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end end
describe 'GET #container_scanning_reports' do describe 'GET #container_scanning_reports' do
...@@ -545,20 +555,7 @@ describe Projects::MergeRequestsController do ...@@ -545,20 +555,7 @@ describe Projects::MergeRequestsController do
end end
end end
context 'public project with private builds' do it_behaves_like 'authorize read pipeline'
let(:comparison_status) { {} }
let(:project) { create(:project, :public, :builds_private) }
before do
sign_out user
end
it 'restricts unauthorized access' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end end
describe 'GET #sast_reports' do describe 'GET #sast_reports' do
...@@ -628,20 +625,7 @@ describe Projects::MergeRequestsController do ...@@ -628,20 +625,7 @@ describe Projects::MergeRequestsController do
end end
end end
context 'public project with private builds' do it_behaves_like 'authorize read pipeline'
let(:comparison_status) { {} }
let(:project) { create(:project, :public, :builds_private) }
before do
sign_out user
end
it 'restricts unauthorized access' do
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end end
describe 'GET #dast_reports' do describe 'GET #dast_reports' do
...@@ -711,26 +695,7 @@ describe Projects::MergeRequestsController do ...@@ -711,26 +695,7 @@ describe Projects::MergeRequestsController do
end end
end end
context 'public project with private builds' do it_behaves_like 'authorize read pipeline'
let(:comparison_status) { {} }
let(:project) { create(:project, :public, :builds_private) }
it 'restricts access to signed out users' do
sign_out user
subject
expect(response).to have_gitlab_http_status(:not_found)
end
it 'restricts access to other users' do
sign_in create(:user)
subject
expect(response).to have_gitlab_http_status(:not_found)
end
end
end end
describe 'GET #license_management_reports' do describe 'GET #license_management_reports' do
...@@ -868,6 +833,8 @@ describe Projects::MergeRequestsController do ...@@ -868,6 +833,8 @@ describe Projects::MergeRequestsController do
expect(json_response).to eq({ 'status_reason' => 'Failed to parse test reports' }) expect(json_response).to eq({ 'status_reason' => 'Failed to parse test reports' })
end end
end end
it_behaves_like 'authorize read pipeline'
end end
it_behaves_like DescriptionDiffActions do it_behaves_like DescriptionDiffActions do
......
...@@ -25,6 +25,10 @@ describe Projects::VulnerabilityFeedbackController do ...@@ -25,6 +25,10 @@ describe Projects::VulnerabilityFeedbackController do
let!(:vuln_feedback_5) { create(:vulnerability_feedback, :merge_request, :dependency_scanning, project: project, author: user, pipeline: pipeline_1, merge_request: merge_request) } let!(:vuln_feedback_5) { create(:vulnerability_feedback, :merge_request, :dependency_scanning, project: project, author: user, pipeline: pipeline_1, merge_request: merge_request) }
context '@vulnerability_feedback' do context '@vulnerability_feedback' do
before do
sign_in(user)
end
it 'returns a successful 200 response' do it 'returns a successful 200 response' do
list_feedbacks list_feedbacks
......
...@@ -202,7 +202,7 @@ describe SubscriptionsController do ...@@ -202,7 +202,7 @@ describe SubscriptionsController do
group.save group.save
subject subject
expect(response.body).to eq({ name: ["can't be blank"] }.to_json) expect(response.body).to include({ name: ["can't be blank", Gitlab::Regex.group_name_regex_message] }.to_json)
end end
end end
......
...@@ -103,4 +103,17 @@ describe Gitlab::Regex do ...@@ -103,4 +103,17 @@ describe Gitlab::Regex do
it { is_expected.not_to match('my package name') } it { is_expected.not_to match('my package name') }
it { is_expected.not_to match('!!()()') } it { is_expected.not_to match('!!()()') }
end end
describe '.semver_regex' do
subject { described_class.semver_regex }
it { is_expected.to match('1.2.3') }
it { is_expected.to match('1.2.3-beta') }
it { is_expected.to match('1.2.3-alpha.3') }
it { is_expected.not_to match('1') }
it { is_expected.not_to match('1.2') }
it { is_expected.not_to match('1./2.3') }
it { is_expected.not_to match('../../../../../1.2.3') }
it { is_expected.not_to match('%2e%2e%2f1.2.3') }
end
end end
...@@ -80,6 +80,21 @@ RSpec.describe Packages::Package, type: :model do ...@@ -80,6 +80,21 @@ RSpec.describe Packages::Package, type: :model do
it { is_expected.not_to allow_value("my(dom$$$ain)com.my-app").for(:name) } it { is_expected.not_to allow_value("my(dom$$$ain)com.my-app").for(:name) }
end end
describe '#version' do
context 'npm package' do
subject { create(:npm_package) }
it { is_expected.to allow_value('1.2.3').for(:version) }
it { is_expected.to allow_value('1.2.3-beta').for(:version) }
it { is_expected.to allow_value('1.2.3-alpha.3').for(:version) }
it { is_expected.not_to allow_value('1').for(:version) }
it { is_expected.not_to allow_value('1.2').for(:version) }
it { is_expected.not_to allow_value('1./2.3').for(:version) }
it { is_expected.not_to allow_value('../../../../../1.2.3').for(:version) }
it { is_expected.not_to allow_value('%2e%2e%2f1.2.3').for(:version) }
end
end
describe '#package_already_taken' do describe '#package_already_taken' do
context 'npm package' do context 'npm package' do
let!(:package) { create(:npm_package) } let!(:package) { create(:npm_package) }
...@@ -173,7 +188,7 @@ RSpec.describe Packages::Package, type: :model do ...@@ -173,7 +188,7 @@ RSpec.describe Packages::Package, type: :model do
end end
describe '.has_version' do describe '.has_version' do
let!(:package4) { create(:npm_package, version: nil) } let!(:package4) { create(:nuget_package, version: nil) }
subject { described_class.has_version } subject { described_class.has_version }
......
...@@ -51,7 +51,7 @@ describe ProjectPolicy do ...@@ -51,7 +51,7 @@ describe ProjectPolicy do
read_environment read_deployment read_merge_request read_pages read_environment read_deployment read_merge_request read_pages
create_merge_request_in award_emoji create_merge_request_in award_emoji
read_project_security_dashboard read_vulnerability read_project_security_dashboard read_vulnerability
read_vulnerability_feedback read_security_findings read_software_license_policy read_software_license_policy
read_threat_monitoring read_merge_train read_threat_monitoring read_merge_train
] ]
end end
...@@ -331,121 +331,11 @@ describe ProjectPolicy do ...@@ -331,121 +331,11 @@ describe ProjectPolicy do
end end
end end
describe 'read_vulnerability_feedback' do
context 'with private project' do
let(:current_user) { admin }
let(:project) { create(:project, :private, namespace: owner.namespace) }
where(role: %w[admin owner maintainer developer reporter])
with_them do
let(:current_user) { public_send(role) }
it { is_expected.to be_allowed(:read_vulnerability_feedback) }
end
context 'with guest' do
let(:current_user) { guest }
it { is_expected.to be_disallowed(:read_vulnerability_feedback) }
end
context 'with non member' do
let(:current_user) { create(:user) }
it { is_expected.to be_disallowed(:read_vulnerability_feedback) }
end
context 'with anonymous' do
let(:current_user) { nil }
it { is_expected.to be_disallowed(:read_vulnerability_feedback) }
end
end
context 'with public project' do
let(:current_user) { create(:user) }
context 'with limited access to both builds and merge requests' do
context 'when builds enabled for project members' do
let(:project) { create(:project, :public, :merge_requests_private, :builds_private) }
it { is_expected.not_to be_allowed(:read_vulnerability_feedback) }
end
context 'when public builds disabled' do
let(:project) { create(:project, :public, :merge_requests_private, public_builds: false) }
it { is_expected.not_to be_allowed(:read_vulnerability_feedback) }
end
end
context 'with limited access to merge requests' do
let(:project) { create(:project, :public, :merge_requests_private) }
it { is_expected.to be_allowed(:read_vulnerability_feedback) }
end
context 'with public access to repository' do
let(:project) { create(:project, :public) }
it { is_expected.to be_allowed(:read_vulnerability_feedback) }
end
end
end
describe 'read_security_findings' do
context 'with private project' do
let(:project) { create(:project, :private, namespace: owner.namespace) }
context 'with reporter or above' do
let(:current_user) { reporter }
it { is_expected.to be_allowed(:read_security_findings) }
end
context 'with non member' do
let(:current_user) { create(:user) }
it { is_expected.to be_disallowed(:read_security_findings) }
end
context 'with anonymous' do
let(:current_user) { nil }
it { is_expected.to be_disallowed(:read_security_findings) }
end
end
context 'with public project' do
let(:current_user) { create(:user) }
context 'with limited access to builds' do
context 'when builds enabled only for project members' do
let(:project) { create(:project, :public, :builds_private) }
it { is_expected.not_to be_allowed(:read_security_findings) }
end
context 'when public builds disabled' do
let(:project) { create(:project, :public, public_builds: false) }
it { is_expected.not_to be_allowed(:read_security_findings) }
end
end
context 'with public access to repository' do
let(:project) { create(:project, :public) }
it { is_expected.to be_allowed(:read_security_findings) }
end
end
end
describe 'vulnerability feedback permissions' do describe 'vulnerability feedback permissions' do
subject { described_class.new(current_user, project) } subject { described_class.new(current_user, project) }
where(permission: %i[ where(permission: %i[
read_vulnerability_feedback
create_vulnerability_feedback create_vulnerability_feedback
update_vulnerability_feedback update_vulnerability_feedback
destroy_vulnerability_feedback destroy_vulnerability_feedback
......
...@@ -230,21 +230,25 @@ describe API::NpmPackages do ...@@ -230,21 +230,25 @@ describe API::NpmPackages do
end end
describe 'PUT /api/v4/projects/:id/packages/npm/:package_name' do describe 'PUT /api/v4/projects/:id/packages/npm/:package_name' do
context 'when params are correct' do RSpec.shared_examples 'handling invalid record with 400 error' do
context 'invalid package record' do
context 'unscoped package' do
let(:package_name) { 'my_unscoped_package' }
let(:params) { upload_params(package_name) }
it 'handles an ActiveRecord::RecordInvalid exception with 400 error' do it 'handles an ActiveRecord::RecordInvalid exception with 400 error' do
expect { upload_package_with_token(package_name, params) } expect { upload_package_with_token(package_name, params) }
.not_to change { project.packages.count } .not_to change { project.packages.count }
expect(response).to have_gitlab_http_status(:bad_request) expect(response).to have_gitlab_http_status(:bad_request)
end end
end
context 'when params are correct' do
context 'invalid package record' do
context 'unscoped package' do
let(:package_name) { 'my_unscoped_package' }
let(:params) { upload_params(package_name: package_name) }
it_behaves_like 'handling invalid record with 400 error'
context 'with empty versions' do context 'with empty versions' do
let(:params) { upload_params(package_name).merge!(versions: {}) } let(:params) { upload_params(package_name: package_name).merge!(versions: {}) }
it 'throws a 400 error' do it 'throws a 400 error' do
expect { upload_package_with_token(package_name, params) } expect { upload_package_with_token(package_name, params) }
...@@ -257,20 +261,37 @@ describe API::NpmPackages do ...@@ -257,20 +261,37 @@ describe API::NpmPackages do
context 'invalid package name' do context 'invalid package name' do
let(:package_name) { "@#{group.path}/my_inv@@lid_package_name" } let(:package_name) { "@#{group.path}/my_inv@@lid_package_name" }
let(:params) { upload_params(package_name) } let(:params) { upload_params(package_name: package_name) }
it 'handles an ActiveRecord::RecordInvalid exception with 400 error' do it_behaves_like 'handling invalid record with 400 error'
expect { upload_package_with_token(package_name, params) } end
.not_to change { project.packages.count }
expect(response).to have_gitlab_http_status(:bad_request) context 'invalid package version' do
using RSpec::Parameterized::TableSyntax
let(:package_name) { "@#{group.path}/my_package_name" }
where(:version) do
[
'1',
'1.2',
'1./2.3',
'../../../../../1.2.3',
'%2e%2e%2f1.2.3'
]
end
with_them do
let(:params) { upload_params(package_name: package_name, package_version: version) }
it_behaves_like 'handling invalid record with 400 error'
end end
end end
end end
context 'scoped package' do context 'scoped package' do
let(:package_name) { "@#{group.path}/my_package_name" } let(:package_name) { "@#{group.path}/my_package_name" }
let(:params) { upload_params(package_name) } let(:params) { upload_params(package_name: package_name) }
context 'with access token' do context 'with access token' do
subject { upload_package_with_token(package_name, params) } subject { upload_package_with_token(package_name, params) }
...@@ -319,7 +340,7 @@ describe API::NpmPackages do ...@@ -319,7 +340,7 @@ describe API::NpmPackages do
context 'package creation fails' do context 'package creation fails' do
let(:package_name) { "@#{group.path}/my_package_name" } let(:package_name) { "@#{group.path}/my_package_name" }
let(:params) { upload_params(package_name) } let(:params) { upload_params(package_name: package_name) }
it 'returns an error if the package already exists' do it 'returns an error if the package already exists' do
create(:npm_package, project: project, version: '1.0.1', name: "@#{group.path}/my_package_name") create(:npm_package, project: project, version: '1.0.1', name: "@#{group.path}/my_package_name")
...@@ -332,7 +353,7 @@ describe API::NpmPackages do ...@@ -332,7 +353,7 @@ describe API::NpmPackages do
context 'with dependencies' do context 'with dependencies' do
let(:package_name) { "@#{group.path}/my_package_name" } let(:package_name) { "@#{group.path}/my_package_name" }
let(:params) { upload_params(package_name, 'npm/payload_with_duplicated_packages.json') } let(:params) { upload_params(package_name: package_name, file: 'npm/payload_with_duplicated_packages.json') }
it 'creates npm package with file and dependencies' do it 'creates npm package with file and dependencies' do
expect { upload_package_with_token(package_name, params) } expect { upload_package_with_token(package_name, params) }
...@@ -347,7 +368,7 @@ describe API::NpmPackages do ...@@ -347,7 +368,7 @@ describe API::NpmPackages do
context 'with existing dependencies' do context 'with existing dependencies' do
before do before do
name = "@#{group.path}/existing_package" name = "@#{group.path}/existing_package"
upload_package_with_token(name, upload_params(name, 'npm/payload_with_duplicated_packages.json')) upload_package_with_token(name, upload_params(package_name: name, file: 'npm/payload_with_duplicated_packages.json'))
end end
it 'reuses them' do it 'reuses them' do
...@@ -373,10 +394,11 @@ describe API::NpmPackages do ...@@ -373,10 +394,11 @@ describe API::NpmPackages do
upload_package(package_name, params.merge(job_token: job.token)) upload_package(package_name, params.merge(job_token: job.token))
end end
def upload_params(package_name, file = 'npm/payload.json') def upload_params(package_name:, package_version: '1.0.1', file: 'npm/payload.json')
JSON.parse( JSON.parse(
fixture_file(file, dir: 'ee') fixture_file(file, dir: 'ee')
.gsub('@root/npm-test', package_name)) .gsub('@root/npm-test', package_name)
.gsub('1.0.1', package_version))
end end
end end
......
...@@ -5,7 +5,7 @@ describe Packages::Npm::CreatePackageService do ...@@ -5,7 +5,7 @@ describe Packages::Npm::CreatePackageService do
let(:namespace) {create(:namespace)} let(:namespace) {create(:namespace)}
let(:project) { create(:project, namespace: namespace) } let(:project) { create(:project, namespace: namespace) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:version) { '1.0.1'.freeze } let(:version) { '1.0.1' }
let(:params) do let(:params) do
JSON.parse( JSON.parse(
...@@ -37,7 +37,6 @@ describe Packages::Npm::CreatePackageService do ...@@ -37,7 +37,6 @@ describe Packages::Npm::CreatePackageService do
expect(package.version).to eq(version) expect(package.version).to eq(version)
end end
it { is_expected.to be_valid }
it { expect(subject.name).to eq(package_name) } it { expect(subject.name).to eq(package_name) }
it { expect(subject.version).to eq(version) } it { expect(subject.version).to eq(version) }
end end
...@@ -77,5 +76,23 @@ describe Packages::Npm::CreatePackageService do ...@@ -77,5 +76,23 @@ describe Packages::Npm::CreatePackageService do
it { expect(subject[:http_status]).to eq 400 } it { expect(subject[:http_status]).to eq 400 }
it { expect(subject[:message]).to eq 'Version is empty.' } it { expect(subject[:message]).to eq 'Version is empty.' }
end end
context 'with invalid versions' do
using RSpec::Parameterized::TableSyntax
where(:version) do
[
'1',
'1.2',
'1./2.3',
'../../../../../1.2.3',
'%2e%2e%2f1.2.3'
]
end
with_them do
it { expect { subject }.to raise_error(ActiveRecord::RecordInvalid, 'Validation failed: Version is invalid') }
end
end
end end
end end
...@@ -37,7 +37,7 @@ describe Packages::Npm::CreateTagService do ...@@ -37,7 +37,7 @@ describe Packages::Npm::CreateTagService do
end end
context 'on same package with different version' do context 'on same package with different version' do
let!(:package2) { create(:npm_package, project: package.project, name: package.name, version: '5.0.0testing') } let!(:package2) { create(:npm_package, project: package.project, name: package.name, version: '5.0.0-testing') }
it { expect { subject }.to not_change { Packages::Tag.count } } it { expect { subject }.to not_change { Packages::Tag.count } }
it { expect(subject.name).to eq(tag_name) } it { expect(subject.name).to eq(tag_name) }
......
...@@ -367,6 +367,10 @@ module API ...@@ -367,6 +367,10 @@ module API
render_api_error!('405 Method Not Allowed', 405) render_api_error!('405 Method Not Allowed', 405)
end end
def not_acceptable!
render_api_error!('406 Not Acceptable', 406)
end
def service_unavailable! def service_unavailable!
render_api_error!('503 Service Unavailable', 503) render_api_error!('503 Service Unavailable', 503)
end end
......
...@@ -95,6 +95,8 @@ module API ...@@ -95,6 +95,8 @@ module API
render_api_error!({ error: ::Gitlab::RateLimitHelpers::ARCHIVE_RATE_LIMIT_REACHED_MESSAGE }, 429) render_api_error!({ error: ::Gitlab::RateLimitHelpers::ARCHIVE_RATE_LIMIT_REACHED_MESSAGE }, 429)
end end
not_acceptable! if Gitlab::HotlinkingDetector.intercept_hotlinking?(request)
send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true send_git_archive user_project.repository, ref: params[:sha], format: params[:format], append_sha: true
rescue rescue
not_found!('File') not_found!('File')
......
...@@ -74,6 +74,8 @@ module API ...@@ -74,6 +74,8 @@ module API
desc: 'The visibility of the snippet' desc: 'The visibility of the snippet'
end end
post do post do
authorize! :create_snippet
attrs = declared_params(include_missing: false).merge(request: request, api: true) attrs = declared_params(include_missing: false).merge(request: request, api: true)
service_response = ::Snippets::CreateService.new(nil, current_user, attrs).execute service_response = ::Snippets::CreateService.new(nil, current_user, attrs).execute
snippet = service_response.payload[:snippet] snippet = service_response.payload[:snippet]
......
...@@ -109,6 +109,8 @@ module API ...@@ -109,6 +109,8 @@ module API
trigger = user_project.triggers.find(params.delete(:trigger_id)) trigger = user_project.triggers.find(params.delete(:trigger_id))
break not_found!('Trigger') unless trigger break not_found!('Trigger') unless trigger
authorize! :admin_trigger, trigger
if trigger.update(declared_params(include_missing: false)) if trigger.update(declared_params(include_missing: false))
present trigger, with: Entities::Trigger, current_user: current_user present trigger, with: Entities::Trigger, current_user: current_user
else else
......
...@@ -171,6 +171,8 @@ module Gitlab ...@@ -171,6 +171,8 @@ module Gitlab
if valid_oauth_token?(token) if valid_oauth_token?(token)
user = User.find_by(id: token.resource_owner_id) user = User.find_by(id: token.resource_owner_id)
return unless user.can?(:log_in)
Gitlab::Auth::Result.new(user, nil, :oauth, full_authentication_abilities) Gitlab::Auth::Result.new(user, nil, :oauth, full_authentication_abilities)
end end
end end
...@@ -182,7 +184,7 @@ module Gitlab ...@@ -182,7 +184,7 @@ module Gitlab
token = PersonalAccessTokensFinder.new(state: 'active').find_by_token(password) token = PersonalAccessTokensFinder.new(state: 'active').find_by_token(password)
if token && valid_scoped_token?(token, all_available_scopes) if token && valid_scoped_token?(token, all_available_scopes) && token.user.can?(:log_in)
Gitlab::Auth::Result.new(token.user, nil, :personal_access_token, abilities_for_scopes(token.scopes)) Gitlab::Auth::Result.new(token.user, nil, :personal_access_token, abilities_for_scopes(token.scopes))
end end
end end
...@@ -260,6 +262,8 @@ module Gitlab ...@@ -260,6 +262,8 @@ module Gitlab
return unless build.project.builds_enabled? return unless build.project.builds_enabled?
if build.user if build.user
return unless build.user.can?(:log_in)
# If user is assigned to build, use restricted credentials of user # If user is assigned to build, use restricted credentials of user
Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities) Gitlab::Auth::Result.new(build.user, build.project, :build, build_authentication_abilities)
else else
......
...@@ -22,6 +22,8 @@ module Gitlab ...@@ -22,6 +22,8 @@ module Gitlab
return @text unless needs_rewrite? return @text unless needs_rewrite?
@text.gsub(@pattern) do |markdown| @text.gsub(@pattern) do |markdown|
Gitlab::Utils.check_path_traversal!($~[:file])
file = find_file(@source_project, $~[:secret], $~[:file]) file = find_file(@source_project, $~[:secret], $~[:file])
break markdown unless file.try(:exists?) break markdown unless file.try(:exists?)
......
# frozen_string_literal: true
module Gitlab
class HotlinkingDetector
IMAGE_FORMATS = %w(image/jpeg image/apng image/png image/webp image/svg+xml image/*).freeze
MEDIA_FORMATS = %w(video/webm video/ogg video/* application/ogg audio/webm audio/ogg audio/wav audio/*).freeze
CSS_FORMATS = %w(text/css).freeze
INVALID_FORMATS = (IMAGE_FORMATS + MEDIA_FORMATS + CSS_FORMATS).freeze
INVALID_FETCH_MODES = %w(cors no-cors websocket).freeze
class << self
def intercept_hotlinking?(request)
request_accepts = parse_request_accepts(request)
return false unless Feature.enabled?(:repository_archive_hotlinking_interception, default_enabled: true)
# Block attempts to embed as JS
return true if sec_fetch_invalid?(request)
# If no Accept header was set, skip the rest
return false if request_accepts.empty?
# Workaround for IE8 weirdness
return false if IMAGE_FORMATS.include?(request_accepts.first) && request_accepts.include?("application/x-ms-application")
# Block all other media requests if the first format is a media type
return true if INVALID_FORMATS.include?(request_accepts.first)
false
end
private
def sec_fetch_invalid?(request)
fetch_mode = request.headers["Sec-Fetch-Mode"]
return if fetch_mode.blank?
return true if INVALID_FETCH_MODES.include?(fetch_mode)
end
def parse_request_accepts(request)
# Rails will already have parsed the Accept header
return request.accepts if request.respond_to?(:accepts)
# Grape doesn't parse it, so we can use the Rails system for this
return Mime::Type.parse(request.headers["Accept"]) if request.respond_to?(:headers) && request.headers["Accept"].present?
[]
end
end
end
end
...@@ -11,7 +11,14 @@ module Gitlab ...@@ -11,7 +11,14 @@ module Gitlab
'discussion_id', 'discussion_id',
'custom_attributes' 'custom_attributes'
].freeze ].freeze
PROHIBITED_REFERENCES = Regexp.union(/\Acached_markdown_version\Z/, /_id\Z/, /_ids\Z/, /_html\Z/, /attributes/).freeze PROHIBITED_REFERENCES = Regexp.union(
/\Acached_markdown_version\Z/,
/_id\Z/,
/_ids\Z/,
/_html\Z/,
/attributes/,
/\Aremote_\w+_(url|urls|request_header)\Z/ # carrierwave automatically creates these attribute methods for uploads
).freeze
def self.clean(*args) def self.clean(*args)
new(*args).clean new(*args).clean
......
...@@ -16,6 +16,14 @@ module Gitlab ...@@ -16,6 +16,14 @@ module Gitlab
"It must start with letter, digit, emoji or '_'." "It must start with letter, digit, emoji or '_'."
end end
def group_name_regex
project_name_regex
end
def group_name_regex_message
project_name_regex_message
end
## ##
# Docker Distribution Registry repository / tag name rules # Docker Distribution Registry repository / tag name rules
# #
......
...@@ -12860,6 +12860,9 @@ msgstr "" ...@@ -12860,6 +12860,9 @@ msgstr ""
msgid "Mirror repository" msgid "Mirror repository"
msgstr "" msgstr ""
msgid "Mirror settings are only available to GitLab administrators."
msgstr ""
msgid "Mirror user" msgid "Mirror user"
msgstr "" msgstr ""
...@@ -18862,6 +18865,9 @@ msgstr "" ...@@ -18862,6 +18865,9 @@ msgstr ""
msgid "Specified URL cannot be used." msgid "Specified URL cannot be used."
msgstr "" msgstr ""
msgid "Specified URL cannot be used: \"%{reason}\""
msgstr ""
msgid "Specify an e-mail address regex pattern to identify default internal users." msgid "Specify an e-mail address regex pattern to identify default internal users."
msgstr "" msgstr ""
......
...@@ -258,6 +258,18 @@ describe GroupsController do ...@@ -258,6 +258,18 @@ describe GroupsController do
end end
end end
end end
context "malicious group name" do
subject { post :create, params: { group: { name: "<script>alert('Mayday!');</script>", path: "invalid_group_url" } } }
before do
sign_in(user)
end
it { expect { subject }.not_to change { Group.count } }
it { expect(subject).to render_template(:new) }
end
end end
describe 'GET #index' do describe 'GET #index' do
...@@ -836,6 +848,16 @@ describe GroupsController do ...@@ -836,6 +848,16 @@ describe GroupsController do
put :update, params: { id: group.to_param, group: { name: 'world' } } put :update, params: { id: group.to_param, group: { name: 'world' } }
end.to change { group.reload.name } end.to change { group.reload.name }
end end
context "malicious group name" do
subject { put :update, params: { id: group.to_param, group: { name: "<script>alert('Attack!');</script>" } } }
it { is_expected.to render_template(:edit) }
it 'does not update name' do
expect { subject }.not_to change { group.reload.name }
end
end
end end
describe 'DELETE #destroy' do describe 'DELETE #destroy' do
......
...@@ -25,6 +25,35 @@ describe Import::FogbugzController do ...@@ -25,6 +25,35 @@ describe Import::FogbugzController do
expect(session[:fogbugz_uri]).to eq(uri) expect(session[:fogbugz_uri]).to eq(uri)
expect(response).to redirect_to(new_user_map_import_fogbugz_path) expect(response).to redirect_to(new_user_map_import_fogbugz_path)
end end
context 'verify url' do
shared_examples 'denies local request' do |reason|
it 'does not allow requests' do
post :callback, params: { uri: uri, email: 'test@example.com', password: 'mypassword' }
expect(response).to redirect_to(new_import_fogbugz_url)
expect(flash[:alert]).to eq("Specified URL cannot be used: \"#{reason}\"")
end
end
context 'when host is localhost' do
let(:uri) { 'https://localhost:3000' }
include_examples 'denies local request', 'Requests to localhost are not allowed'
end
context 'when host is on local network' do
let(:uri) { 'http://192.168.0.1/' }
include_examples 'denies local request', 'Requests to the local network are not allowed'
end
context 'when host is ftp protocol' do
let(:uri) { 'ftp://testing' }
include_examples 'denies local request', 'Only allowed schemes are http, https'
end
end
end end
describe 'POST #create_user_map' do describe 'POST #create_user_map' do
......
...@@ -5,6 +5,72 @@ require 'spec_helper' ...@@ -5,6 +5,72 @@ require 'spec_helper'
describe Projects::MirrorsController do describe Projects::MirrorsController do
include ReactiveCachingHelpers include ReactiveCachingHelpers
shared_examples 'only admin is allowed when mirroring is disabled' do
let(:subject_action) { raise 'subject_action is required' }
let(:user) { project.owner }
let(:project_settings_path) { project_settings_repository_path(project, anchor: 'js-push-remote-settings') }
context 'when project mirroring is enabled' do
it 'allows requests from a maintainer' do
sign_in(user)
subject_action
expect(response).to redirect_to(project_settings_path)
end
it 'allows requests from an admin user' do
user.update!(admin: true)
sign_in(user)
subject_action
expect(response).to redirect_to(project_settings_path)
end
end
context 'when project mirroring is disabled' do
before do
stub_application_setting(mirror_available: false)
end
it 'disallows requests from a maintainer' do
sign_in(user)
subject_action
expect(response).to have_gitlab_http_status(:not_found)
end
it 'allows requests from an admin user' do
user.update!(admin: true)
sign_in(user)
subject_action
expect(response).to redirect_to(project_settings_path)
end
end
end
describe 'Access control' do
let(:project) { create(:project, :repository) }
describe '#update' do
include_examples 'only admin is allowed when mirroring is disabled' do
let(:subject_action) do
do_put(project, remote_mirrors_attributes: { '0' => { 'enabled' => 1, 'url' => 'http://foo.com' } })
end
end
end
describe '#update_now' do
include_examples 'only admin is allowed when mirroring is disabled' do
let(:options) { { namespace_id: project.namespace, project_id: project } }
let(:subject_action) do
get :update_now, params: options.merge(sync_remote: true)
end
end
end
end
describe 'setting up a remote mirror' do describe 'setting up a remote mirror' do
let_it_be(:project) { create(:project, :repository) } let_it_be(:project) { create(:project, :repository) }
......
...@@ -28,6 +28,12 @@ describe Projects::RepositoriesController do ...@@ -28,6 +28,12 @@ describe Projects::RepositoriesController do
sign_in(user) sign_in(user)
end end
it_behaves_like "hotlink interceptor" do
let(:http_request) do
get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master" }, format: "zip"
end
end
it "uses Gitlab::Workhorse" do it "uses Gitlab::Workhorse" do
get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master" }, format: "zip" get :archive, params: { namespace_id: project.namespace, project_id: project, id: "master" }, format: "zip"
......
...@@ -26,11 +26,7 @@ describe 'Projects > Settings > Repository settings' do ...@@ -26,11 +26,7 @@ describe 'Projects > Settings > Repository settings' do
let(:role) { :maintainer } let(:role) { :maintainer }
context 'remote mirror settings' do context 'remote mirror settings' do
let(:user2) { create(:user) }
before do before do
project.add_maintainer(user2)
visit project_settings_repository_path(project) visit project_settings_repository_path(project)
end end
...@@ -90,6 +86,18 @@ describe 'Projects > Settings > Repository settings' do ...@@ -90,6 +86,18 @@ describe 'Projects > Settings > Repository settings' do
expect(page).to have_selector('[title="Copy SSH public key"]') expect(page).to have_selector('[title="Copy SSH public key"]')
end end
context 'when project mirroring is disabled' do
before do
stub_application_setting(mirror_available: false)
visit project_settings_repository_path(project)
end
it 'hides remote mirror settings' do
expect(page.find('.project-mirror-settings')).not_to have_selector('form')
expect(page).to have_content('Mirror settings are only available to GitLab administrators.')
end
end
def select_direction(direction = 'push') def select_direction(direction = 'push')
direction_select = find('#mirror_direction') direction_select = find('#mirror_direction')
...@@ -154,4 +162,31 @@ describe 'Projects > Settings > Repository settings' do ...@@ -154,4 +162,31 @@ describe 'Projects > Settings > Repository settings' do
expect(mirror).not_to have_selector('.rspec-update-now-button') expect(mirror).not_to have_selector('.rspec-update-now-button')
end end
end end
context 'for admin' do
shared_examples_for 'shows mirror settings' do
it 'shows mirror settings' do
expect(page.find('.project-mirror-settings')).to have_selector('form')
expect(page).not_to have_content('Changing mirroring setting is disabled for non-admin users.')
end
end
before do
stub_application_setting(mirror_available: mirror_available)
user.update!(admin: true)
visit project_settings_repository_path(project)
end
context 'when project mirroring is enabled' do
let(:mirror_available) { true }
include_examples 'shows mirror settings'
end
context 'when project mirroring is disabled' do
let(:mirror_available) { false }
include_examples 'shows mirror settings'
end
end
end end
...@@ -108,5 +108,23 @@ describe('Frequent Items utils spec', () => { ...@@ -108,5 +108,23 @@ describe('Frequent Items utils spec', () => {
expect(sanitizeItem(input)).toEqual({ name: 'test', namespace: 'test', id: 1 }); expect(sanitizeItem(input)).toEqual({ name: 'test', namespace: 'test', id: 1 });
}); });
it("skips `name` key if it doesn't exist on the item", () => {
const input = {
namespace: '<br>test',
id: 1,
};
expect(sanitizeItem(input)).toEqual({ namespace: 'test', id: 1 });
});
it("skips `namespace` key if it doesn't exist on the item", () => {
const input = {
name: '<br><b>test</b>',
id: 1,
};
expect(sanitizeItem(input)).toEqual({ name: 'test', id: 1 });
});
}); });
}); });
...@@ -523,7 +523,12 @@ describe Banzai::Filter::LabelReferenceFilter do ...@@ -523,7 +523,12 @@ describe Banzai::Filter::LabelReferenceFilter do
end end
context 'when group name has HTML entities' do context 'when group name has HTML entities' do
let(:another_group) { create(:group, name: '<img src=x onerror=alert(1)>', path: 'another_group') } let(:another_group) { create(:group, name: 'random', path: 'another_group') }
before do
another_group.name = "<img src=x onerror=alert(1)>"
another_group.save!(validate: false)
end
it 'escapes the HTML entities' do it 'escapes the HTML entities' do
expect(result.text) expect(result.text)
......
...@@ -20,8 +20,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do ...@@ -20,8 +20,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
it 'skips when the skip_redaction flag is set' do it 'skips when the skip_redaction flag is set' do
user = create(:user) user = create(:user)
project = create(:project) project = create(:project)
link = reference_link(project: project.id, reference_type: 'test') link = reference_link(project: project.id, reference_type: 'test')
doc = filter(link, current_user: user, skip_redaction: true) doc = filter(link, current_user: user, skip_redaction: true)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
...@@ -51,8 +51,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do ...@@ -51,8 +51,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
user = create(:user) user = create(:user)
project = create(:project) project = create(:project)
project.add_maintainer(user) project.add_maintainer(user)
link = reference_link(project: project.id, reference_type: 'test') link = reference_link(project: project.id, reference_type: 'test')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
...@@ -69,8 +69,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do ...@@ -69,8 +69,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
it 'removes unpermitted references' do it 'removes unpermitted references' do
user = create(:user) user = create(:user)
project = create(:project) project = create(:project)
link = reference_link(project: project.id, reference_type: 'test') link = reference_link(project: project.id, reference_type: 'test')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 0 expect(doc.css('a').length).to eq 0
...@@ -90,8 +90,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do ...@@ -90,8 +90,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
non_member = create(:user) non_member = create(:user)
project = create(:project, :public) project = create(:project, :public)
issue = create(:issue, :confidential, project: project) issue = create(:issue, :confidential, project: project)
link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: non_member) doc = filter(link, current_user: non_member)
expect(doc.css('a').length).to eq 0 expect(doc.css('a').length).to eq 0
...@@ -124,8 +124,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do ...@@ -124,8 +124,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
assignee = create(:user) assignee = create(:user)
project = create(:project, :public) project = create(:project, :public)
issue = create(:issue, :confidential, project: project, assignees: [assignee]) issue = create(:issue, :confidential, project: project, assignees: [assignee])
link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: assignee) doc = filter(link, current_user: assignee)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
...@@ -136,8 +136,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do ...@@ -136,8 +136,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
project = create(:project, :public) project = create(:project, :public)
project.add_developer(member) project.add_developer(member)
issue = create(:issue, :confidential, project: project) issue = create(:issue, :confidential, project: project)
link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: member) doc = filter(link, current_user: member)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
...@@ -147,20 +147,62 @@ describe Banzai::Filter::ReferenceRedactorFilter do ...@@ -147,20 +147,62 @@ describe Banzai::Filter::ReferenceRedactorFilter do
admin = create(:admin) admin = create(:admin)
project = create(:project, :public) project = create(:project, :public)
issue = create(:issue, :confidential, project: project) issue = create(:issue, :confidential, project: project)
link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: admin) doc = filter(link, current_user: admin)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
end end
context "when a confidential issue is moved from a public project to a private one" do
let(:public_project) { create(:project, :public) }
let(:private_project) { create(:project, :private) }
it 'removes references for author' do
author = create(:user)
issue = create(:issue, :confidential, project: public_project, author: author)
issue.update!(project: private_project) # move issue to private project
link = reference_link(project: private_project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: author)
expect(doc.css('a').length).to eq 0
end
it 'removes references for assignee' do
assignee = create(:user)
issue = create(:issue, :confidential, project: public_project, assignees: [assignee])
issue.update!(project: private_project) # move issue to private project
link = reference_link(project: private_project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: assignee)
expect(doc.css('a').length).to eq 0
end
it 'allows references for project members' do
member = create(:user)
project = create(:project, :public)
project_2 = create(:project, :private)
project.add_developer(member)
project_2.add_developer(member)
issue = create(:issue, :confidential, project: project)
issue.update!(project: project_2) # move issue to private project
link = reference_link(project: project_2.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: member)
expect(doc.css('a').length).to eq 1
end
end
end end
it 'allows references for non confidential issues' do it 'allows references for non confidential issues' do
user = create(:user) user = create(:user)
project = create(:project, :public) project = create(:project, :public)
issue = create(:issue, project: project) issue = create(:issue, project: project)
link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue') link = reference_link(project: project.id, issue: issue.id, reference_type: 'issue')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
...@@ -172,8 +214,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do ...@@ -172,8 +214,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
it 'removes unpermitted Group references' do it 'removes unpermitted Group references' do
user = create(:user) user = create(:user)
group = create(:group, :private) group = create(:group, :private)
link = reference_link(group: group.id, reference_type: 'user') link = reference_link(group: group.id, reference_type: 'user')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 0 expect(doc.css('a').length).to eq 0
...@@ -183,8 +225,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do ...@@ -183,8 +225,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
user = create(:user) user = create(:user)
group = create(:group, :private) group = create(:group, :private)
group.add_developer(user) group.add_developer(user)
link = reference_link(group: group.id, reference_type: 'user') link = reference_link(group: group.id, reference_type: 'user')
doc = filter(link, current_user: user) doc = filter(link, current_user: user)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
...@@ -200,8 +242,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do ...@@ -200,8 +242,8 @@ describe Banzai::Filter::ReferenceRedactorFilter do
context 'with data-user' do context 'with data-user' do
it 'allows any User reference' do it 'allows any User reference' do
user = create(:user) user = create(:user)
link = reference_link(user: user.id, reference_type: 'user') link = reference_link(user: user.id, reference_type: 'user')
doc = filter(link) doc = filter(link)
expect(doc.css('a').length).to eq 1 expect(doc.css('a').length).to eq 1
......
...@@ -164,6 +164,12 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do ...@@ -164,6 +164,12 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, described_class.build_authentication_abilities)) expect(subject).to eq(Gitlab::Auth::Result.new(build.user, build.project, :build, described_class.build_authentication_abilities))
end end
it 'fails with blocked user token' do
build.update(user: create(:user, :blocked))
expect(subject).to eq(Gitlab::Auth::Result.new(nil, nil, nil, nil))
end
end end
(HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status| (HasStatus::AVAILABLE_STATUSES - ['running']).each do |build_status|
...@@ -259,6 +265,15 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do ...@@ -259,6 +265,15 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip') gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip')
end end
context 'blocked user' do
let(:user) { create(:user, :blocked) }
it 'fails' do
expect(gl_auth.find_for_git_client("oauth2", token_w_api_scope.token, project: nil, ip: 'ip'))
.to eq(Gitlab::Auth::Result.new(nil, nil, nil, nil))
end
end
end end
context 'while using personal access tokens as passwords' do context 'while using personal access tokens as passwords' do
...@@ -307,9 +322,35 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do ...@@ -307,9 +322,35 @@ describe Gitlab::Auth, :use_clean_rails_memory_store_caching do
it 'fails if password is nil' do it 'fails if password is nil' do
expect_results_with_abilities(nil, nil, false) expect_results_with_abilities(nil, nil, false)
end end
context 'when user is blocked' do
let(:user) { create(:user, :blocked) }
let(:personal_access_token) { create(:personal_access_token, scopes: ['read_registry'], user: user) }
before do
stub_container_registry_config(enabled: true)
end
it 'fails if user is blocked' do
expect(gl_auth.find_for_git_client('', personal_access_token.token, project: nil, ip: 'ip'))
.to eq(Gitlab::Auth::Result.new(nil, nil, nil, nil))
end
end
end end
context 'while using regular user and password' do context 'while using regular user and password' do
it 'fails for a blocked user' do
user = create(
:user,
:blocked,
username: 'normal_user',
password: 'my-secret'
)
expect(gl_auth.find_for_git_client(user.username, user.password, project: nil, ip: 'ip'))
.to eq(Gitlab::Auth::Result.new(nil, nil, nil, nil))
end
it 'goes through lfs authentication' do it 'goes through lfs authentication' do
user = create( user = create(
:user, :user,
......
...@@ -68,6 +68,16 @@ describe Gitlab::Gfm::UploadsRewriter do ...@@ -68,6 +68,16 @@ describe Gitlab::Gfm::UploadsRewriter do
expect(moved_text.scan(/\A\[.*?\]/).count).to eq(1) expect(moved_text.scan(/\A\[.*?\]/).count).to eq(1)
end end
context 'path traversal in file name' do
let(:text) do
"![a](/uploads/11111111111111111111111111111111/../../../../../../../../../../../../../../etc/passwd)"
end
it 'throw an error' do
expect { rewriter.rewrite(new_project) }.to raise_error(an_instance_of(StandardError).and having_attributes(message: "Invalid path"))
end
end
context "file are stored locally" do context "file are stored locally" do
include_examples "files are accessible" include_examples "files are accessible"
end end
......
# frozen_string_literal: true
require "spec_helper"
RSpec.describe Gitlab::HotlinkingDetector do
describe ".intercept_hotlinking?" do
using RSpec::Parameterized::TableSyntax
subject { described_class.intercept_hotlinking?(request) }
let(:request) { double("request", headers: headers) }
let(:headers) { {} }
context "hotlinked as media" do
where(:return_value, :accept_header) do
# These are default formats in modern browsers, and IE
false | "*/*"
false | "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
false | "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
false | "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
false | "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
false | "image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/x-shockwave-flash, application/msword, */*"
false | "text/html, application/xhtml+xml, image/jxr, */*"
false | "text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1"
# These are image request formats
true | "image/webp,*/*"
true | "image/png,image/*;q=0.8,*/*;q=0.5"
true | "image/webp,image/apng,image/*,*/*;q=0.8"
true | "image/png,image/svg+xml,image/*;q=0.8, */*;q=0.5"
# Video request formats
true | "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5"
# Audio request formats
true | "audio/webm,audio/ogg,audio/wav,audio/*;q=0.9,application/ogg;q=0.7,video/*;q=0.6,*/*;q=0.5"
# CSS request formats
true | "text/css,*/*;q=0.1"
true | "text/css"
true | "text/css,*/*;q=0.1"
end
with_them do
let(:headers) do
{ "Accept" => accept_header }
end
it { is_expected.to be(return_value) }
end
end
context "hotlinked as a script" do
where(:return_value, :fetch_mode) do
# Standard navigation fetch modes
false | "navigate"
false | "nested-navigate"
false | "same-origin"
# Fetch modes when linking as JS
true | "cors"
true | "no-cors"
true | "websocket"
end
with_them do
let(:headers) do
{ "Sec-Fetch-Mode" => fetch_mode }
end
it { is_expected.to be(return_value) }
end
end
end
end
...@@ -32,6 +32,9 @@ describe Gitlab::ImportExport::AttributeCleaner do ...@@ -32,6 +32,9 @@ describe Gitlab::ImportExport::AttributeCleaner do
'issue_ids' => [1, 2, 3], 'issue_ids' => [1, 2, 3],
'merge_request_ids' => [1, 2, 3], 'merge_request_ids' => [1, 2, 3],
'note_ids' => [1, 2, 3], 'note_ids' => [1, 2, 3],
'remote_attachment_url' => 'http://something.dodgy',
'remote_attachment_request_header' => 'bad value',
'remote_attachment_urls' => %w(http://something.dodgy http://something.okay),
'attributes' => { 'attributes' => {
'issue_ids' => [1, 2, 3], 'issue_ids' => [1, 2, 3],
'merge_request_ids' => [1, 2, 3], 'merge_request_ids' => [1, 2, 3],
......
...@@ -3,9 +3,7 @@ ...@@ -3,9 +3,7 @@
require 'spec_helper' require 'spec_helper'
describe Gitlab::Regex do describe Gitlab::Regex do
describe '.project_name_regex' do shared_examples_for 'project/group name regex' do
subject { described_class.project_name_regex }
it { is_expected.to match('gitlab-ce') } it { is_expected.to match('gitlab-ce') }
it { is_expected.to match('GitLab CE') } it { is_expected.to match('GitLab CE') }
it { is_expected.to match('100 lines') } it { is_expected.to match('100 lines') }
...@@ -15,6 +13,34 @@ describe Gitlab::Regex do ...@@ -15,6 +13,34 @@ describe Gitlab::Regex do
it { is_expected.not_to match('?gitlab') } it { is_expected.not_to match('?gitlab') }
end end
shared_examples_for 'project/group name error message' do
it { is_expected.to eq("can contain only letters, digits, emojis, '_', '.', dash, space. It must start with letter, digit, emoji or '_'.") }
end
describe '.project_name_regex' do
subject { described_class.project_name_regex }
it_behaves_like 'project/group name regex'
end
describe '.group_name_regex' do
subject { described_class.group_name_regex }
it_behaves_like 'project/group name regex'
end
describe '.project_name_regex_message' do
subject { described_class.project_name_regex_message }
it_behaves_like 'project/group name error message'
end
describe '.group_name_regex_message' do
subject { described_class.group_name_regex_message }
it_behaves_like 'project/group name error message'
end
describe '.environment_name_regex' do describe '.environment_name_regex' do
subject { described_class.environment_name_regex } subject { described_class.environment_name_regex }
......
...@@ -48,6 +48,9 @@ describe Group do ...@@ -48,6 +48,9 @@ describe Group do
describe 'validations' do describe 'validations' do
it { is_expected.to validate_presence_of :name } it { is_expected.to validate_presence_of :name }
it { is_expected.to allow_value('group test_4').for(:name) }
it { is_expected.not_to allow_value('test/../foo').for(:name) }
it { is_expected.not_to allow_value('<script>alert("Attack!")</script>').for(:name) }
it { is_expected.to validate_presence_of :path } it { is_expected.to validate_presence_of :path }
it { is_expected.not_to validate_presence_of :owner } it { is_expected.not_to validate_presence_of :owner }
it { is_expected.to validate_presence_of :two_factor_grace_period } it { is_expected.to validate_presence_of :two_factor_grace_period }
......
This diff is collapsed.
...@@ -103,12 +103,24 @@ describe IssuePolicy do ...@@ -103,12 +103,24 @@ describe IssuePolicy do
expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue) expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
end end
it 'does not allow issue author to read or update confidential issue moved to an private project' do
confidential_issue.project = build(:project, :private)
expect(permissions(author, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue)
end
it 'allows issue assignees to read and update their confidential issues' do it 'allows issue assignees to read and update their confidential issues' do
expect(permissions(assignee, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue) expect(permissions(assignee, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
expect(permissions(assignee, confidential_issue)).to be_disallowed(:admin_issue) expect(permissions(assignee, confidential_issue)).to be_disallowed(:admin_issue)
expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue) expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue)
end end
it 'does not allow issue assignees to read or update confidential issue moved to an private project' do
confidential_issue.project = build(:project, :private)
expect(permissions(assignee, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue)
end
end end
end end
......
...@@ -642,6 +642,20 @@ describe API::Groups do ...@@ -642,6 +642,20 @@ describe API::Groups do
expect(json_response['default_branch_protection']).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS) expect(json_response['default_branch_protection']).to eq(::Gitlab::Access::MAINTAINER_PROJECT_ACCESS)
end end
context 'malicious group name' do
subject { put api("/groups/#{group1.id}", user1), params: { name: "<SCRIPT>alert('DOUBLE-ATTACK!')</SCRIPT>" } }
it 'returns bad request' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
end
it 'does not update group name' do
expect { subject }.not_to change { group1.reload.name }
end
end
it 'returns 404 for a non existing group' do it 'returns 404 for a non existing group' do
put api('/groups/1328', user1), params: { name: new_group_name } put api('/groups/1328', user1), params: { name: new_group_name }
...@@ -1083,6 +1097,20 @@ describe API::Groups do ...@@ -1083,6 +1097,20 @@ describe API::Groups do
expect(json_response["parent_id"]).to eq(parent.id) expect(json_response["parent_id"]).to eq(parent.id)
end end
context 'malicious group name' do
subject { post api("/groups", user3), params: group_params }
let(:group_params) { attributes_for_group_api name: "<SCRIPT>alert('ATTACKED!')</SCRIPT>", path: "unique-url" }
it 'returns bad request' do
subject
expect(response).to have_gitlab_http_status(:bad_request)
end
it { expect { subject }.not_to change { Group.count } }
end
it "does not create group, duplicate" do it "does not create group, duplicate" do
post api("/groups", user3), params: { name: 'Duplicate Test', path: group2.path } post api("/groups", user3), params: { name: 'Duplicate Test', path: group2.path }
......
...@@ -164,6 +164,30 @@ describe API::ProjectSnippets do ...@@ -164,6 +164,30 @@ describe API::ProjectSnippets do
end end
end end
context 'with an external user' do
let(:user) { create(:user, :external) }
context 'that belongs to the project' do
before do
project.add_developer(user)
end
it 'creates a new snippet' do
post api("/projects/#{project.id}/snippets/", user), params: params
expect(response).to have_gitlab_http_status(:created)
end
end
context 'that does not belong to the project' do
it 'does not create a new snippet' do
post api("/projects/#{project.id}/snippets/", user), params: params
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
context 'with a regular user' do context 'with a regular user' do
let(:user) { create(:user) } let(:user) { create(:user) }
......
...@@ -275,6 +275,18 @@ describe API::Repositories do ...@@ -275,6 +275,18 @@ describe API::Repositories do
expect(response).to have_gitlab_http_status(:too_many_requests) expect(response).to have_gitlab_http_status(:too_many_requests)
end end
context "when hotlinking detection is enabled" do
before do
Feature.enable(:repository_archive_hotlinking_interception)
end
it_behaves_like "hotlink interceptor" do
let(:http_request) do
get api(route, current_user), headers: headers
end
end
end
end end
context 'when unauthenticated', 'and project is public' do context 'when unauthenticated', 'and project is public' do
......
...@@ -266,6 +266,16 @@ describe API::Snippets do ...@@ -266,6 +266,16 @@ describe API::Snippets do
it_behaves_like 'snippet creation' it_behaves_like 'snippet creation'
context 'with an external user' do
let(:user) { create(:user, :external) }
it 'does not create a new snippet' do
post api("/snippets/", user), params: params
expect(response).to have_gitlab_http_status(:forbidden)
end
end
it 'returns 400 for missing parameters' do it 'returns 400 for missing parameters' do
params.delete(:title) params.delete(:title)
......
...@@ -238,7 +238,8 @@ describe API::Triggers do ...@@ -238,7 +238,8 @@ describe API::Triggers do
end end
describe 'PUT /projects/:id/triggers/:trigger_id' do describe 'PUT /projects/:id/triggers/:trigger_id' do
context 'authenticated user with valid permissions' do context 'user is maintainer of the project' do
context 'the trigger belongs to user' do
let(:new_description) { 'new description' } let(:new_description) { 'new description' }
it 'updates description' do it 'updates description' do
...@@ -251,13 +252,32 @@ describe API::Triggers do ...@@ -251,13 +252,32 @@ describe API::Triggers do
end end
end end
context 'authenticated user with invalid permissions' do context 'the trigger does not belong to user' do
it 'does not update trigger' do
put api("/projects/#{project.id}/triggers/#{trigger2.id}", user)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
end
context 'user is developer of the project' do
context 'the trigger belongs to user' do
it 'does not update trigger' do
put api("/projects/#{project.id}/triggers/#{trigger2.id}", user2)
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'the trigger does not belong to user' do
it 'does not update trigger' do it 'does not update trigger' do
put api("/projects/#{project.id}/triggers/#{trigger.id}", user2) put api("/projects/#{project.id}/triggers/#{trigger.id}", user2)
expect(response).to have_gitlab_http_status(:forbidden) expect(response).to have_gitlab_http_status(:forbidden)
end end
end end
end
context 'unauthenticated user' do context 'unauthenticated user' do
it 'does not update trigger' do it 'does not update trigger' do
......
...@@ -25,6 +25,17 @@ describe JwtController do ...@@ -25,6 +25,17 @@ describe JwtController do
end end
context 'when using authenticated request' do context 'when using authenticated request' do
shared_examples 'rejecting a blocked user' do
context 'with blocked user' do
let(:user) { create(:user, :blocked) }
it 'rejects the request as unauthorized' do
expect(response).to have_gitlab_http_status(:unauthorized)
expect(response.body).to include('HTTP Basic: Access denied')
end
end
end
context 'using CI token' do context 'using CI token' do
let(:build) { create(:ci_build, :running) } let(:build) { create(:ci_build, :running) }
let(:project) { build.project } let(:project) { build.project }
...@@ -61,6 +72,8 @@ describe JwtController do ...@@ -61,6 +72,8 @@ describe JwtController do
expect(response).to have_gitlab_http_status(:ok) expect(response).to have_gitlab_http_status(:ok)
expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!) expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!)
end end
it_behaves_like 'rejecting a blocked user'
end end
end end
...@@ -72,6 +85,8 @@ describe JwtController do ...@@ -72,6 +85,8 @@ describe JwtController do
it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!) } it { expect(service_class).to have_received(:new).with(nil, user, ActionController::Parameters.new(parameters).permit!) }
it_behaves_like 'rejecting a blocked user'
context 'when passing a flat array of scopes' do context 'when passing a flat array of scopes' do
# We use this trick to make rails to generate a query_string: # We use this trick to make rails to generate a query_string:
# scope=scope1&scope=scope2 # scope=scope1&scope=scope2
......
...@@ -138,7 +138,7 @@ describe MergeRequestPollWidgetEntity do ...@@ -138,7 +138,7 @@ describe MergeRequestPollWidgetEntity do
end end
describe 'pipeline' do describe 'pipeline' do
let(:pipeline) { create(:ci_empty_pipeline, project: project, ref: resource.source_branch, sha: resource.source_branch_sha, head_pipeline_of: resource) } let!(:pipeline) { create(:ci_empty_pipeline, project: project, ref: resource.source_branch, sha: resource.source_branch_sha, head_pipeline_of: resource) }
before do before do
allow_any_instance_of(MergeRequestPresenter).to receive(:can?).and_call_original allow_any_instance_of(MergeRequestPresenter).to receive(:can?).and_call_original
...@@ -158,6 +158,10 @@ describe MergeRequestPollWidgetEntity do ...@@ -158,6 +158,10 @@ describe MergeRequestPollWidgetEntity do
expect(subject[:pipeline]).to eq(pipeline_payload) expect(subject[:pipeline]).to eq(pipeline_payload)
end end
it 'returns ci_status' do
expect(subject[:ci_status]).to eq('pending')
end
end end
context 'when is not up to date' do context 'when is not up to date' do
...@@ -171,10 +175,15 @@ describe MergeRequestPollWidgetEntity do ...@@ -171,10 +175,15 @@ describe MergeRequestPollWidgetEntity do
context 'when user does not have access to pipelines' do context 'when user does not have access to pipelines' do
let(:result) { false } let(:result) { false }
let(:req) { double('request', current_user: user, project: project) }
it 'does not have pipeline' do it 'does not have pipeline' do
expect(subject[:pipeline]).to eq(nil) expect(subject[:pipeline]).to eq(nil)
end end
it 'does not return ci_status' do
expect(subject[:ci_status]).to eq(nil)
end
end end
end end
end end
...@@ -76,7 +76,7 @@ module WorkhorseHelpers ...@@ -76,7 +76,7 @@ module WorkhorseHelpers
"#{key}.size" => file.size "#{key}.size" => file.size
}.tap do |params| }.tap do |params|
params["#{key}.path"] = file.path if file.path params["#{key}.path"] = file.path if file.path
params["#{key}.remote_id"] = file.remote_id if file.respond_to?(:remote_id) && file.remote_id params["#{key}.remote_id"] = file.remote_id if file.respond_to?(:remote_id) && file.remote_id.present?
end end
end end
......
# frozen_string_literal: true
RSpec.shared_examples "hotlink interceptor" do
let(:http_request) { nil }
let(:headers) { nil }
describe "DDOS prevention" do
using RSpec::Parameterized::TableSyntax
context "hotlinked as media" do
where(:response_status, :accept_header) do
# These are default formats in modern browsers, and IE
:ok | "*/*"
:ok | "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
:ok | "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8"
:ok | "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"
:ok | "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8"
:ok | "image/jpeg, application/x-ms-application, image/gif, application/xaml+xml, image/pjpeg, application/x-ms-xbap, application/x-shockwave-flash, application/msword, */*"
:ok | "text/html, application/xhtml+xml, image/jxr, */*"
:ok | "text/html, application/xml;q=0.9, application/xhtml+xml, image/png, image/webp, image/jpeg, image/gif, image/x-xbitmap, */*;q=0.1"
# These are image request formats
:not_acceptable | "image/webp,*/*"
:not_acceptable | "image/png,image/*;q=0.8,*/*;q=0.5"
:not_acceptable | "image/webp,image/apng,image/*,*/*;q=0.8"
:not_acceptable | "image/png,image/svg+xml,image/*;q=0.8, */*;q=0.5"
# Video request formats
:not_acceptable | "video/webm,video/ogg,video/*;q=0.9,application/ogg;q=0.7,audio/*;q=0.6,*/*;q=0.5"
# Audio request formats
:not_acceptable | "audio/webm,audio/ogg,audio/wav,audio/*;q=0.9,application/ogg;q=0.7,video/*;q=0.6,*/*;q=0.5"
# CSS request formats
:not_acceptable | "text/css,*/*;q=0.1"
:not_acceptable | "text/css"
:not_acceptable | "text/css,*/*;q=0.1"
end
with_them do
let(:headers) do
{ "Accept" => accept_header }
end
before do
request.headers.merge!(headers) if request.present?
end
it "renders the response" do
http_request
expect(response).to have_gitlab_http_status(response_status)
end
end
end
context "hotlinked as a script" do
where(:response_status, :fetch_mode) do
# Standard navigation fetch modes
:ok | "navigate"
:ok | "nested-navigate"
:ok | "same-origin"
# Fetch modes when linking as JS
:not_acceptable | "cors"
:not_acceptable | "no-cors"
:not_acceptable | "websocket"
end
with_them do
let(:headers) do
{ "Sec-Fetch-Mode" => fetch_mode }
end
before do
request.headers.merge!(headers) if request.present?
end
it "renders the response" do
http_request
expect(response).to have_gitlab_http_status(response_status)
end
end
end
end
end
...@@ -714,6 +714,19 @@ describe ObjectStorage do ...@@ -714,6 +714,19 @@ describe ObjectStorage do
end end
end end
context 'when empty remote_id is specified' do
let(:uploaded_file) do
UploadedFile.new(temp_file.path, remote_id: '')
end
it 'uses local storage' do
subject
expect(uploader).to be_file_storage
expect(uploader.object_store).to eq(described_class::Store::LOCAL)
end
end
context 'when valid file is specified' do context 'when valid file is specified' do
let(:uploaded_file) do let(:uploaded_file) do
UploadedFile.new(temp_file.path, filename: "my_file.txt", remote_id: "test/123123") UploadedFile.new(temp_file.path, filename: "my_file.txt", remote_id: "test/123123")
......
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