Commit e7f76734 authored by Douwe Maan's avatar Douwe Maan

Merge branch 'mk-fix-git-over-http-rejections-ee' into 'master'

Port of mk-fix-git-over-http-rejections to EE

See merge request !2062
parents 9100ee60 51655fdb
...@@ -108,6 +108,10 @@ module LfsRequest ...@@ -108,6 +108,10 @@ module LfsRequest
@objects ||= (params[:objects] || []).to_a @objects ||= (params[:objects] || []).to_a
end end
def has_authentication_ability?(capability)
(authentication_abilities || []).include?(capability)
end
module EE module EE
def lfs_forbidden! def lfs_forbidden!
raise NotImplementedError unless defined?(super) raise NotImplementedError unless defined?(super)
......
...@@ -128,32 +128,10 @@ class Projects::GitHttpClientController < Projects::ApplicationController ...@@ -128,32 +128,10 @@ class Projects::GitHttpClientController < Projects::ApplicationController
@authentication_result = Gitlab::Auth.find_for_git_client( @authentication_result = Gitlab::Auth.find_for_git_client(
login, password, project: project, ip: request.ip) login, password, project: project, ip: request.ip)
return false unless @authentication_result.success? @authentication_result.success?
if download_request?
authentication_has_download_access?
else
authentication_has_upload_access?
end
end end
def ci? def ci?
authentication_result.ci?(project) authentication_result.ci?(project)
end end
def authentication_has_download_access?
has_authentication_ability?(:download_code) || has_authentication_ability?(:build_download_code)
end
def authentication_has_upload_access?
has_authentication_ability?(:push_code)
end
def has_authentication_ability?(capability)
(authentication_abilities || []).include?(capability)
end
def authentication_project
authentication_result.project
end
end end
class Projects::GitHttpController < Projects::GitHttpClientController class Projects::GitHttpController < Projects::GitHttpClientController
include WorkhorseRequest include WorkhorseRequest
before_action :access_check
rescue_from Gitlab::GitAccess::UnauthorizedError, with: :render_403
rescue_from Gitlab::GitAccess::NotFoundError, with: :render_404
# GET /foo/bar.git/info/refs?service=git-upload-pack (git pull) # GET /foo/bar.git/info/refs?service=git-upload-pack (git pull)
# GET /foo/bar.git/info/refs?service=git-receive-pack (git push) # GET /foo/bar.git/info/refs?service=git-receive-pack (git push)
def info_refs def info_refs
if upload_pack? && upload_pack_allowed? log_user_activity if upload_pack?
log_user_activity
render_ok render_ok
elsif receive_pack? && receive_pack_allowed?
render_ok
elsif http_blocked?
render_http_not_allowed
else
render_denied
end
end end
# POST /foo/bar.git/git-upload-pack (git pull) # POST /foo/bar.git/git-upload-pack (git pull)
def git_upload_pack def git_upload_pack
if upload_pack? && upload_pack_allowed?
render_ok render_ok
else
render_denied
end
end end
# POST /foo/bar.git/git-receive-pack" (git push) # POST /foo/bar.git/git-receive-pack" (git push)
def git_receive_pack def git_receive_pack
if receive_pack? && receive_pack_allowed?
render_ok render_ok
else
render_denied
end
end end
private private
...@@ -45,10 +34,6 @@ class Projects::GitHttpController < Projects::GitHttpClientController ...@@ -45,10 +34,6 @@ class Projects::GitHttpController < Projects::GitHttpClientController
git_command == 'git-upload-pack' git_command == 'git-upload-pack'
end end
def receive_pack?
git_command == 'git-receive-pack'
end
def git_command def git_command
if action_name == 'info_refs' if action_name == 'info_refs'
params[:service] params[:service]
...@@ -62,47 +47,27 @@ class Projects::GitHttpController < Projects::GitHttpClientController ...@@ -62,47 +47,27 @@ class Projects::GitHttpController < Projects::GitHttpClientController
render json: Gitlab::Workhorse.git_http_ok(repository, wiki?, user, action_name) render json: Gitlab::Workhorse.git_http_ok(repository, wiki?, user, action_name)
end end
def render_http_not_allowed def render_403(exception)
render plain: access_check.message, status: :forbidden render plain: exception.message, status: :forbidden
end end
def render_denied def render_404(exception)
if user && can?(user, :read_project, project) render plain: exception.message, status: :not_found
render plain: access_denied_message, status: :forbidden
else
# Do not leak information about project existence
render_not_found
end
end
def access_denied_message
access_check.message || 'Access denied'
end end
def upload_pack_allowed? def access
return false unless Gitlab.config.gitlab_shell.upload_pack @access ||= access_klass.new(access_actor, project, 'http', authentication_abilities: authentication_abilities)
access_check.allowed? || ci?
end end
def access def access_actor
@access ||= access_klass.new(user, project, 'http', authentication_abilities: authentication_abilities) return user if user
return :ci if ci?
end end
def access_check def access_check
# Use the magic string '_any' to indicate we do not know what the # Use the magic string '_any' to indicate we do not know what the
# changes are. This is also what gitlab-shell does. # changes are. This is also what gitlab-shell does.
@access_check ||= access.check(git_command, '_any') access.check(git_command, '_any')
end
def http_blocked?
!access.protocol_allowed?
end
def receive_pack_allowed?
return false unless Gitlab.config.gitlab_shell.receive_pack
access_check.allowed?
end end
def access_klass def access_klass
......
---
title: Fix Git-over-HTTP error statuses and improve error messages
merge_request: 11398
author:
...@@ -42,6 +42,22 @@ module API ...@@ -42,6 +42,22 @@ module API
@project, @wiki = Gitlab::RepoPath.parse(params[:project]) @project, @wiki = Gitlab::RepoPath.parse(params[:project])
end end
end end
# Project id to pass between components that don't share/don't have
# access to the same filesystem mounts
def gl_repository
Gitlab::GlRepository.gl_repository(project, wiki?)
end
# Return the repository full path so that gitlab-shell has it when
# handling ssh commands
def repository_path
if wiki?
project.wiki.repository.path_to_repo
else
project.repository.path_to_repo
end
end
end end
end end
end end
...@@ -32,31 +32,23 @@ module API ...@@ -32,31 +32,23 @@ module API
actor.update_last_used_at if actor.is_a?(Key) actor.update_last_used_at if actor.is_a?(Key)
access_checker = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess access_checker_klass = wiki? ? Gitlab::GitAccessWiki : Gitlab::GitAccess
access_status = access_checker access_checker = access_checker_klass
.new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities) .new(actor, project, protocol, authentication_abilities: ssh_authentication_abilities)
.check(params[:action], params[:changes])
response = { status: access_status.status, message: access_status.message } begin
access_checker.check(params[:action], params[:changes])
rescue Gitlab::GitAccess::UnauthorizedError, Gitlab::GitAccess::NotFoundError => e
return { status: false, message: e.message }
end
if access_status.status
log_user_activity(actor) log_user_activity(actor)
# Project id to pass between components that don't share/don't have {
# access to the same filesystem mounts status: true,
response[:gl_repository] = Gitlab::GlRepository.gl_repository(project, wiki?) gl_repository: gl_repository,
repository_path: repository_path
# Return the repository full path so that gitlab-shell has it when }
# handling ssh commands
response[:repository_path] =
if wiki?
project.wiki.repository.path_to_repo
else
project.repository.path_to_repo
end
end
response
end end
post "/lfs_authenticate" do post "/lfs_authenticate" do
......
...@@ -3,6 +3,20 @@ module Gitlab ...@@ -3,6 +3,20 @@ module Gitlab
class ChangeAccess class ChangeAccess
include PathLocksHelper include PathLocksHelper
ERROR_MESSAGES = {
push_code: 'You are not allowed to push code to this project.',
delete_default_branch: 'The default branch of a project cannot be deleted.',
force_push_protected_branch: 'You are not allowed to force push code to a protected branch on this project.',
non_master_delete_protected_branch: 'You are not allowed to delete protected branches from this project. Only a project master or owner can delete a protected branch.',
non_web_delete_protected_branch: 'You can only delete protected branches using the web interface.',
merge_protected_branch: 'You are not allowed to merge code into protected branches on this project.',
push_protected_branch: 'You are not allowed to push code to protected branches on this project.',
change_existing_tags: 'You are not allowed to change existing tags on this project.',
update_protected_tag: 'Protected tags cannot be updated.',
delete_protected_tag: 'Protected tags cannot be deleted.',
create_protected_tag: 'You are not allowed to create this tag as it is protected.'
}.freeze
# protocol is currently used only in EE # protocol is currently used only in EE
attr_reader :user_access, :project, :skip_authorization, :protocol attr_reader :user_access, :project, :skip_authorization, :protocol
...@@ -20,22 +34,21 @@ module Gitlab ...@@ -20,22 +34,21 @@ module Gitlab
end end
def exec def exec
return GitAccessStatus.new(true) if skip_authorization return true if skip_authorization
error = push_checks || branch_checks || tag_checks || push_rule_check push_checks
branch_checks
tag_checks
push_rule_check
if error true
GitAccessStatus.new(false, error)
else
GitAccessStatus.new(true)
end
end end
protected protected
def push_checks def push_checks
if user_access.cannot_do_action?(:push_code) if user_access.cannot_do_action?(:push_code)
"You are not allowed to push code to this project." raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_code]
end end
end end
...@@ -43,7 +56,7 @@ module Gitlab ...@@ -43,7 +56,7 @@ module Gitlab
return unless @branch_name return unless @branch_name
if deletion? && @branch_name == project.default_branch if deletion? && @branch_name == project.default_branch
return "The default branch of a project cannot be deleted." raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_default_branch]
end end
protected_branch_checks protected_branch_checks
...@@ -53,7 +66,7 @@ module Gitlab ...@@ -53,7 +66,7 @@ module Gitlab
return unless ProtectedBranch.protected?(project, @branch_name) return unless ProtectedBranch.protected?(project, @branch_name)
if forced_push? if forced_push?
return "You are not allowed to force push code to a protected branch on this project." raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:force_push_protected_branch]
end end
if deletion? if deletion?
...@@ -65,22 +78,22 @@ module Gitlab ...@@ -65,22 +78,22 @@ module Gitlab
def protected_branch_deletion_checks def protected_branch_deletion_checks
unless user_access.can_delete_branch?(@branch_name) unless user_access.can_delete_branch?(@branch_name)
return 'You are not allowed to delete protected branches from this project. Only a project master or owner can delete a protected branch.' raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_master_delete_protected_branch]
end end
unless protocol == 'web' unless protocol == 'web'
'You can only delete protected branches using the web interface.' raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:non_web_delete_protected_branch]
end end
end end
def protected_branch_push_checks def protected_branch_push_checks
if matching_merge_request? if matching_merge_request?
unless user_access.can_merge_to_branch?(@branch_name) || user_access.can_push_to_branch?(@branch_name) unless user_access.can_merge_to_branch?(@branch_name) || user_access.can_push_to_branch?(@branch_name)
"You are not allowed to merge code into protected branches on this project." raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:merge_protected_branch]
end end
else else
unless user_access.can_push_to_branch?(@branch_name) unless user_access.can_push_to_branch?(@branch_name)
"You are not allowed to push code to protected branches on this project." raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:push_protected_branch]
end end
end end
end end
...@@ -89,7 +102,7 @@ module Gitlab ...@@ -89,7 +102,7 @@ module Gitlab
return unless @tag_name return unless @tag_name
if tag_exists? && user_access.cannot_do_action?(:admin_project) if tag_exists? && user_access.cannot_do_action?(:admin_project)
return "You are not allowed to change existing tags on this project." raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:change_existing_tags]
end end
protected_tag_checks protected_tag_checks
...@@ -98,11 +111,11 @@ module Gitlab ...@@ -98,11 +111,11 @@ module Gitlab
def protected_tag_checks def protected_tag_checks
return unless ProtectedTag.protected?(project, @tag_name) return unless ProtectedTag.protected?(project, @tag_name)
return "Protected tags cannot be updated." if update? raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:update_protected_tag]) if update?
return "Protected tags cannot be deleted." if deletion? raise(GitAccess::UnauthorizedError, ERROR_MESSAGES[:delete_protected_tag]) if deletion?
unless user_access.can_create_tag?(@tag_name) unless user_access.can_create_tag?(@tag_name)
return "You are not allowed to create this tag as it is protected." raise GitAccess::UnauthorizedError, ERROR_MESSAGES[:create_protected_tag]
end end
end end
...@@ -136,7 +149,7 @@ module Gitlab ...@@ -136,7 +149,7 @@ module Gitlab
# Prevent tag removal # Prevent tag removal
if @tag_name if @tag_name
if tag_deletion_denied_by_push_rule?(push_rule) if tag_deletion_denied_by_push_rule?(push_rule)
return 'You cannot delete a tag' raise GitAccess::UnauthorizedError, 'You cannot delete a tag'
end end
else else
commit_validation = push_rule.try(:commit_validation?) commit_validation = push_rule.try(:commit_validation?)
...@@ -147,16 +160,14 @@ module Gitlab ...@@ -147,16 +160,14 @@ module Gitlab
commits.each do |commit| commits.each do |commit|
if commit_validation if commit_validation
error = check_commit(commit, push_rule) error = check_commit(commit, push_rule)
return error if error raise GitAccess::UnauthorizedError, error if error
end end
if error = check_commit_diff(commit, push_rule) if error = check_commit_diff(commit, push_rule)
return error raise GitAccess::UnauthorizedError, error
end end
end end
end end
nil
end end
def tag_deletion_denied_by_push_rule?(push_rule) def tag_deletion_denied_by_push_rule?(push_rule)
...@@ -167,7 +178,7 @@ module Gitlab ...@@ -167,7 +178,7 @@ module Gitlab
end end
# If commit does not pass push rule validation the whole push should be rejected. # If commit does not pass push rule validation the whole push should be rejected.
# This method should return nil if no error found or status object if there are some errors. # This method should return nil if no error found or a string if error.
# In case of errors - all other checks will be canceled and push will be rejected. # In case of errors - all other checks will be canceled and push will be rejected.
def check_commit(commit, push_rule) def check_commit(commit, push_rule)
unless push_rule.commit_message_allowed?(commit.safe_message) unless push_rule.commit_message_allowed?(commit.safe_message)
......
module Gitlab
# For backwards compatibility, generic CI (which is a build without a user) is
# allowed to :build_download_code without any other checks.
class CiAccess
def can_do_action?(action)
action == :build_download_code
end
end
end
...@@ -5,33 +5,40 @@ module Gitlab ...@@ -5,33 +5,40 @@ module Gitlab
include ActionView::Helpers::SanitizeHelper include ActionView::Helpers::SanitizeHelper
include PathLocksHelper include PathLocksHelper
UnauthorizedError = Class.new(StandardError) UnauthorizedError = Class.new(StandardError)
NotFoundError = Class.new(StandardError)
ERROR_MESSAGES = { ERROR_MESSAGES = {
upload: 'You are not allowed to upload code for this project.', upload: 'You are not allowed to upload code for this project.',
download: 'You are not allowed to download code from this project.', download: 'You are not allowed to download code from this project.',
deploy_key_upload: deploy_key_upload:
'This deploy key does not have write access to this project.', 'This deploy key does not have write access to this project.',
no_repo: 'A repository for this project does not exist yet.' no_repo: 'A repository for this project does not exist yet.',
project_not_found: 'The project you were looking for could not be found.',
account_blocked: 'Your account has been blocked.',
command_not_allowed: "The command you're trying to execute is not allowed.",
upload_pack_disabled_over_http: 'Pulling over HTTP is not allowed.',
receive_pack_disabled_over_http: 'Pushing over HTTP is not allowed.',
cannot_push_to_secondary_geo: "You can't push code to a secondary GitLab Geo node."
}.freeze }.freeze
DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }.freeze DOWNLOAD_COMMANDS = %w{ git-upload-pack git-upload-archive }.freeze
PUSH_COMMANDS = %w{ git-receive-pack }.freeze PUSH_COMMANDS = %w{ git-receive-pack }.freeze
ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS ALL_COMMANDS = DOWNLOAD_COMMANDS + PUSH_COMMANDS
attr_reader :actor, :project, :protocol, :user_access, :authentication_abilities attr_reader :actor, :project, :protocol, :authentication_abilities
def initialize(actor, project, protocol, authentication_abilities:) def initialize(actor, project, protocol, authentication_abilities:)
@actor = actor @actor = actor
@project = project @project = project
@protocol = protocol @protocol = protocol
@authentication_abilities = authentication_abilities @authentication_abilities = authentication_abilities
@user_access = UserAccess.new(user, project: project)
end end
def check(cmd, changes) def check(cmd, changes)
check_protocol! check_protocol!
check_active_user! check_active_user!
check_project_accessibility! check_project_accessibility!
check_command_disabled!(cmd)
check_command_existence!(cmd) check_command_existence!(cmd)
check_repository_existence! check_repository_existence!
...@@ -44,9 +51,7 @@ module Gitlab ...@@ -44,9 +51,7 @@ module Gitlab
check_push_access!(changes) check_push_access!(changes)
end end
build_status_object(true) true
rescue UnauthorizedError => ex
build_status_object(false, ex.message)
end end
def guest_can_download_code? def guest_can_download_code?
...@@ -77,19 +82,39 @@ module Gitlab ...@@ -77,19 +82,39 @@ module Gitlab
return if deploy_key? || geo_node_key? return if deploy_key? || geo_node_key?
if user && !user_access.allowed? if user && !user_access.allowed?
raise UnauthorizedError, "Your account has been blocked." raise UnauthorizedError, ERROR_MESSAGES[:account_blocked]
end end
end end
def check_project_accessibility! def check_project_accessibility!
if project.blank? || !can_read_project? if project.blank? || !can_read_project?
raise UnauthorizedError, 'The project you were looking for could not be found.' raise NotFoundError, ERROR_MESSAGES[:project_not_found]
end
end
def check_command_disabled!(cmd)
if upload_pack?(cmd)
check_upload_pack_disabled!
elsif receive_pack?(cmd)
check_receive_pack_disabled!
end
end
def check_upload_pack_disabled!
if http? && upload_pack_disabled_over_http?
raise UnauthorizedError, ERROR_MESSAGES[:upload_pack_disabled_over_http]
end
end
def check_receive_pack_disabled!
if http? && receive_pack_disabled_over_http?
raise UnauthorizedError, ERROR_MESSAGES[:receive_pack_disabled_over_http]
end end
end end
def check_command_existence!(cmd) def check_command_existence!(cmd)
unless ALL_COMMANDS.include?(cmd) unless ALL_COMMANDS.include?(cmd)
raise UnauthorizedError, "The command you're trying to execute is not allowed." raise UnauthorizedError, ERROR_MESSAGES[:command_not_allowed]
end end
end end
...@@ -124,7 +149,7 @@ module Gitlab ...@@ -124,7 +149,7 @@ module Gitlab
end end
if Gitlab::Geo.secondary? if Gitlab::Geo.secondary?
raise UnauthorizedError, "You can't push code on a secondary GitLab Geo node." raise UnauthorizedError, ERROR_MESSAGES[:cannot_push_to_secondary_geo]
end end
if deploy_key if deploy_key
...@@ -168,12 +193,9 @@ module Gitlab ...@@ -168,12 +193,9 @@ module Gitlab
# Iterate over all changes to find if user allowed all of them to be applied # Iterate over all changes to find if user allowed all of them to be applied
changes_list.each do |change| changes_list.each do |change|
status = check_single_change_access(change) # If user does not have access to make at least one change, cancel all
# push by allowing the exception to bubble up
unless status.allowed? check_single_change_access(change)
# If user does not have access to make at least one change - cancel all push
raise UnauthorizedError, status.message
end
if project.size_limit_enabled? if project.size_limit_enabled?
push_size_in_bytes += EE::Gitlab::Deltas.delta_size_check(change, project.repository) push_size_in_bytes += EE::Gitlab::Deltas.delta_size_check(change, project.repository)
...@@ -211,6 +233,10 @@ module Gitlab ...@@ -211,6 +233,10 @@ module Gitlab
actor.is_a?(GeoNodeKey) actor.is_a?(GeoNodeKey)
end end
def ci?
actor == :ci
end
def can_read_project? def can_read_project?
if deploy_key? if deploy_key?
deploy_key.has_access_to?(project) deploy_key.has_access_to?(project)
...@@ -218,9 +244,31 @@ module Gitlab ...@@ -218,9 +244,31 @@ module Gitlab
true true
elsif user elsif user
user.can?(:read_project, project) user.can?(:read_project, project)
elsif ci?
true # allow CI (build without a user) for backwards compatibility
end || Guest.can?(:read_project, project) end || Guest.can?(:read_project, project)
end end
def http?
protocol == 'http'
end
def upload_pack?(command)
command == 'git-upload-pack'
end
def receive_pack?(command)
command == 'git-receive-pack'
end
def upload_pack_disabled_over_http?
!Gitlab.config.gitlab_shell.upload_pack
end
def receive_pack_disabled_over_http?
!Gitlab.config.gitlab_shell.receive_pack
end
protected protected
def user def user
...@@ -230,17 +278,21 @@ module Gitlab ...@@ -230,17 +278,21 @@ module Gitlab
case actor case actor
when User when User
actor actor
when DeployKey
nil
when GeoNodeKey when GeoNodeKey
nil nil
when Key when Key
actor.user actor.user unless actor.is_a?(DeployKey)
when :ci
nil
end end
end end
def build_status_object(status, message = '') def user_access
Gitlab::GitAccessStatus.new(status, message) @user_access ||= if ci?
CiAccess.new
else
UserAccess.new(user, project: project)
end
end end
end end
end end
module Gitlab
class GitAccessStatus
attr_accessor :status, :message
alias_method :allowed?, :status
def initialize(status, message = '')
@status = status
@message = message
end
def to_json(opts = nil)
{ status: @status, message: @message }.to_json(opts)
end
end
end
module Gitlab module Gitlab
class GitAccessWiki < GitAccess class GitAccessWiki < GitAccess
ERROR_MESSAGES = {
write_to_wiki: "You are not allowed to write to this project's wiki."
}.freeze
def guest_can_download_code? def guest_can_download_code?
Guest.can?(:download_wiki_code, project) Guest.can?(:download_wiki_code, project)
end end
...@@ -9,13 +13,11 @@ module Gitlab ...@@ -9,13 +13,11 @@ module Gitlab
end end
def check_single_change_access(change) def check_single_change_access(change)
if Gitlab::Geo.enabled? && Gitlab::Geo.secondary? unless user_access.can_do_action?(:create_wiki)
build_status_object(false, "You can't push code to a secondary GitLab Geo node.") raise UnauthorizedError, ERROR_MESSAGES[:write_to_wiki]
elsif user_access.can_do_action?(:create_wiki)
build_status_object(true)
else
build_status_object(false, "You are not allowed to write to this project's wiki.")
end end
true
end end
end end
end end
...@@ -23,29 +23,27 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -23,29 +23,27 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
before { project.add_developer(user) } before { project.add_developer(user) }
context 'without failed checks' do context 'without failed checks' do
it "doesn't return any error" do it "doesn't raise an error" do
expect(subject.status).to be(true) expect { subject }.not_to raise_error
end end
end end
context 'when the user is not allowed to push code' do context 'when the user is not allowed to push code' do
it 'returns an error' do it 'raises an error' do
expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false) expect(user_access).to receive(:can_do_action?).with(:push_code).and_return(false)
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to this project.')
expect(subject.message).to eq('You are not allowed to push code to this project.')
end end
end end
context 'tags check' do context 'tags check' do
let(:ref) { 'refs/tags/v1.0.0' } let(:ref) { 'refs/tags/v1.0.0' }
it 'returns an error if the user is not allowed to update tags' do it 'raises an error if the user is not allowed to update tags' do
allow(user_access).to receive(:can_do_action?).with(:push_code).and_return(true) allow(user_access).to receive(:can_do_action?).with(:push_code).and_return(true)
expect(user_access).to receive(:can_do_action?).with(:admin_project).and_return(false) expect(user_access).to receive(:can_do_action?).with(:admin_project).and_return(false)
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to change existing tags on this project.')
expect(subject.message).to eq('You are not allowed to change existing tags on this project.')
end end
context 'with protected tag' do context 'with protected tag' do
...@@ -59,8 +57,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -59,8 +57,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
let(:newrev) { '0000000000000000000000000000000000000000' } let(:newrev) { '0000000000000000000000000000000000000000' }
it 'is prevented' do it 'is prevented' do
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be deleted/)
expect(subject.message).to include('cannot be deleted')
end end
end end
...@@ -69,8 +66,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -69,8 +66,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' } let(:newrev) { '54fcc214b94e78d7a41a9a8fe6d87a5e59500e51' }
it 'is prevented' do it 'is prevented' do
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /cannot be updated/)
expect(subject.message).to include('cannot be updated')
end end
end end
end end
...@@ -81,15 +77,14 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -81,15 +77,14 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
let(:ref) { 'refs/tags/v9.1.0' } let(:ref) { 'refs/tags/v9.1.0' }
it 'prevents creation below access level' do it 'prevents creation below access level' do
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /allowed to create this tag as it is protected/)
expect(subject.message).to include('allowed to create this tag as it is protected')
end end
context 'when user has access' do context 'when user has access' do
let!(:protected_tag) { create(:protected_tag, :developers_can_create, project: project, name: 'v*') } let!(:protected_tag) { create(:protected_tag, :developers_can_create, project: project, name: 'v*') }
it 'allows tag creation' do it 'allows tag creation' do
expect(subject.status).to be(true) expect { subject }.not_to raise_error
end end
end end
end end
...@@ -101,9 +96,8 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -101,9 +96,8 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
let(:newrev) { '0000000000000000000000000000000000000000' } let(:newrev) { '0000000000000000000000000000000000000000' }
let(:ref) { 'refs/heads/master' } let(:ref) { 'refs/heads/master' }
it 'returns an error' do it 'raises an error' do
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'The default branch of a project cannot be deleted.')
expect(subject.message).to eq('The default branch of a project cannot be deleted.')
end end
end end
...@@ -113,27 +107,24 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -113,27 +107,24 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
allow(ProtectedBranch).to receive(:protected?).with(project, 'feature').and_return(true) allow(ProtectedBranch).to receive(:protected?).with(project, 'feature').and_return(true)
end end
it 'returns an error if the user is not allowed to do forced pushes to protected branches' do it 'raises an error if the user is not allowed to do forced pushes to protected branches' do
expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true) expect(Gitlab::Checks::ForcePush).to receive(:force_push?).and_return(true)
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to force push code to a protected branch on this project.')
expect(subject.message).to eq('You are not allowed to force push code to a protected branch on this project.')
end end
it 'returns an error if the user is not allowed to merge to protected branches' do it 'raises an error if the user is not allowed to merge to protected branches' do
expect_any_instance_of(Gitlab::Checks::MatchingMergeRequest).to receive(:match?).and_return(true) expect_any_instance_of(Gitlab::Checks::MatchingMergeRequest).to receive(:match?).and_return(true)
expect(user_access).to receive(:can_merge_to_branch?).and_return(false) expect(user_access).to receive(:can_merge_to_branch?).and_return(false)
expect(user_access).to receive(:can_push_to_branch?).and_return(false) expect(user_access).to receive(:can_push_to_branch?).and_return(false)
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to merge code into protected branches on this project.')
expect(subject.message).to eq('You are not allowed to merge code into protected branches on this project.')
end end
it 'returns an error if the user is not allowed to push to protected branches' do it 'raises an error if the user is not allowed to push to protected branches' do
expect(user_access).to receive(:can_push_to_branch?).and_return(false) expect(user_access).to receive(:can_push_to_branch?).and_return(false)
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to push code to protected branches on this project.')
expect(subject.message).to eq('You are not allowed to push code to protected branches on this project.')
end end
context 'branch deletion' do context 'branch deletion' do
...@@ -141,9 +132,8 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -141,9 +132,8 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
let(:ref) { 'refs/heads/feature' } let(:ref) { 'refs/heads/feature' }
context 'if the user is not allowed to delete protected branches' do context 'if the user is not allowed to delete protected branches' do
it 'returns an error' do it 'raises an error' do
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to delete protected branches from this project. Only a project master or owner can delete a protected branch.')
expect(subject.message).to eq('You are not allowed to delete protected branches from this project. Only a project master or owner can delete a protected branch.')
end end
end end
...@@ -156,14 +146,13 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -156,14 +146,13 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
let(:protocol) { 'web' } let(:protocol) { 'web' }
it 'allows branch deletion' do it 'allows branch deletion' do
expect(subject.status).to be(true) expect { subject }.not_to raise_error
end end
end end
context 'over SSH or HTTP' do context 'over SSH or HTTP' do
it 'returns an error' do it 'raises an error' do
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You can only delete protected branches using the web interface.')
expect(subject.message).to eq('You can only delete protected branches using the web interface.')
end end
end end
end end
...@@ -189,15 +178,14 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -189,15 +178,14 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
before { project.add_master(user) } before { project.add_master(user) }
it 'returns an error if the rule denies tag deletion' do it 'returns an error if the rule denies tag deletion' do
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You cannot delete a tag')
expect(subject.message).to eq('You cannot delete a tag')
end end
context 'when tag is deleted in web UI' do context 'when tag is deleted in web UI' do
let(:protocol) { 'web' } let(:protocol) { 'web' }
it 'ignores the push rule' do it 'ignores the push rule' do
expect(subject.status).to be(true) expect(subject).to be_truthy
end end
end end
end end
...@@ -206,8 +194,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -206,8 +194,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
let(:push_rule) { create(:push_rule, :commit_message) } let(:push_rule) { create(:push_rule, :commit_message) }
it 'returns an error if the rule fails' do it 'returns an error if the rule fails' do
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "Commit message does not follow the pattern '#{push_rule.commit_message_regex}'")
expect(subject.message).to eq("Commit message does not follow the pattern '#{push_rule.commit_message_regex}'")
end end
end end
...@@ -222,15 +209,13 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -222,15 +209,13 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
it 'returns an error if the rule fails for the committer' do it 'returns an error if the rule fails for the committer' do
allow_any_instance_of(Commit).to receive(:committer_email).and_return('ana@invalid.com') allow_any_instance_of(Commit).to receive(:committer_email).and_return('ana@invalid.com')
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "Committer's email 'ana@invalid.com' does not follow the pattern '.*@valid.com'")
expect(subject.message).to eq("Committer's email 'ana@invalid.com' does not follow the pattern '.*@valid.com'")
end end
it 'returns an error if the rule fails for the author' do it 'returns an error if the rule fails for the author' do
allow_any_instance_of(Commit).to receive(:author_email).and_return('joan@invalid.com') allow_any_instance_of(Commit).to receive(:author_email).and_return('joan@invalid.com')
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "Author's email 'joan@invalid.com' does not follow the pattern '.*@valid.com'")
expect(subject.message).to eq("Author's email 'joan@invalid.com' does not follow the pattern '.*@valid.com'")
end end
end end
...@@ -243,8 +228,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -243,8 +228,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
end end
it 'returns an error if the commit author is not a GitLab member' do it 'returns an error if the commit author is not a GitLab member' do
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "Author 'some@mail.com' is not a member of team")
expect(subject.message).to eq("Author 'some@mail.com' is not a member of team")
end end
end end
...@@ -254,23 +238,14 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -254,23 +238,14 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
let(:push_rule) { create(:push_rule, file_name_regex: 'READ*') } let(:push_rule) { create(:push_rule, file_name_regex: 'READ*') }
it "returns an error if a new or renamed filed doesn't match the file name regex" do it "returns an error if a new or renamed filed doesn't match the file name regex" do
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "File name README was blacklisted by the pattern READ*.")
expect(subject.message).to eq("File name README was blacklisted by the pattern READ*.")
end end
end end
context 'blacklisted files check' do context 'blacklisted files check' do
let(:push_rule) { create(:push_rule, prevent_secrets: true) } let(:push_rule) { create(:push_rule, prevent_secrets: true) }
let(:checker) do
described_class.new(
changes,
project: project,
user_access: user_access,
protocol: protocol
)
end
it "returns status true if there is no blacklisted files" do it "returns true if there is no blacklisted files" do
new_rev = nil new_rev = nil
white_listed = white_listed =
...@@ -288,7 +263,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -288,7 +263,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
project.repository.commits_between(old_rev, new_rev) project.repository.commits_between(old_rev, new_rev)
) )
expect(checker.exec.status).to be(true) expect(subject).to be_truthy
end end
end end
...@@ -311,10 +286,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -311,10 +286,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
project.repository.commits_between(old_rev, new_rev) project.repository.commits_between(old_rev, new_rev)
) )
result = checker.exec expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, /File name #{file_path} was blacklisted by the pattern/)
expect(result.status).to be(false)
expect(result.message).to include("File name #{file_path} was blacklisted by the pattern")
end end
end end
end end
...@@ -326,8 +298,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -326,8 +298,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
before { allow_any_instance_of(Blob).to receive(:size).and_return(2.megabytes) } before { allow_any_instance_of(Blob).to receive(:size).and_return(2.megabytes) }
it 'returns an error if file exceeds the maximum file size' do it 'returns an error if file exceeds the maximum file size' do
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "File \"README\" is larger than the allowed size of 1 MB")
expect(subject.message).to eq("File \"README\" is larger than the allowed size of 1 MB")
end end
end end
end end
...@@ -342,8 +313,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do ...@@ -342,8 +313,7 @@ describe Gitlab::Checks::ChangeAccess, lib: true do
end end
it 'returns an error if the changes update a path locked by another user' do it 'returns an error if the changes update a path locked by another user' do
expect(subject.status).to be(false) expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "The path 'README' is locked by #{path_lock.user.name}")
expect(subject.message).to eq("The path 'README' is locked by #{path_lock.user.name}")
end end
end end
end end
......
require 'spec_helper'
describe Gitlab::CiAccess, lib: true do
let(:access) { Gitlab::CiAccess.new }
describe '#can_do_action?' do
context 'when action is :build_download_code' do
it { expect(access.can_do_action?(:build_download_code)).to be_truthy }
end
context 'when action is not :build_download_code' do
it { expect(access.can_do_action?(:download_code)).to be_falsey }
end
end
end
require 'spec_helper' require 'spec_helper'
describe Gitlab::GitAccess, lib: true do describe Gitlab::GitAccess, lib: true do
let(:access) { Gitlab::GitAccess.new(actor, project, 'ssh', authentication_abilities: authentication_abilities) } let(:pull_access_check) { access.check('git-upload-pack', '_any') }
let(:push_access_check) { access.check('git-receive-pack', '_any') }
let(:access) { Gitlab::GitAccess.new(actor, project, protocol, authentication_abilities: authentication_abilities) }
let(:project) { create(:project, :repository) } let(:project) { create(:project, :repository) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:actor) { user } let(:actor) { user }
let(:protocol) { 'ssh' }
let(:authentication_abilities) do let(:authentication_abilities) do
[ [
...@@ -16,49 +19,188 @@ describe Gitlab::GitAccess, lib: true do ...@@ -16,49 +19,188 @@ describe Gitlab::GitAccess, lib: true do
describe '#check with single protocols allowed' do describe '#check with single protocols allowed' do
def disable_protocol(protocol) def disable_protocol(protocol)
settings = ::ApplicationSetting.create_from_defaults allow(Gitlab::ProtocolAccess).to receive(:allowed?).with(protocol).and_return(false)
settings.update_attribute(:enabled_git_access_protocol, protocol)
end end
context 'ssh disabled' do context 'ssh disabled' do
before do before do
disable_protocol('ssh') disable_protocol('ssh')
@acc = Gitlab::GitAccess.new(actor, project, 'ssh', authentication_abilities: authentication_abilities)
end end
it 'blocks ssh git push' do it 'blocks ssh git push' do
expect(@acc.check('git-receive-pack', '_any').allowed?).to be_falsey expect { push_access_check }.to raise_unauthorized('Git access over SSH is not allowed')
end end
it 'blocks ssh git pull' do it 'blocks ssh git pull' do
expect(@acc.check('git-upload-pack', '_any').allowed?).to be_falsey expect { pull_access_check }.to raise_unauthorized('Git access over SSH is not allowed')
end end
end end
context 'http disabled' do context 'http disabled' do
let(:protocol) { 'http' }
before do before do
disable_protocol('http') disable_protocol('http')
@acc = Gitlab::GitAccess.new(actor, project, 'http', authentication_abilities: authentication_abilities)
end end
it 'blocks http push' do it 'blocks http push' do
expect(@acc.check('git-receive-pack', '_any').allowed?).to be_falsey expect { push_access_check }.to raise_unauthorized('Git access over HTTP is not allowed')
end end
it 'blocks http git pull' do it 'blocks http git pull' do
expect(@acc.check('git-upload-pack', '_any').allowed?).to be_falsey expect { pull_access_check }.to raise_unauthorized('Git access over HTTP is not allowed')
end end
end end
end end
describe '#check_download_access!' do describe '#check_project_accessibility!' do
subject { access.check('git-upload-pack', '_any') } context 'when the project exists' do
context 'when actor exists' do
context 'when actor is a DeployKey' do
let(:deploy_key) { create(:deploy_key, user: user, can_push: true) }
let(:actor) { deploy_key }
context 'when the DeployKey has access to the project' do
before { deploy_key.projects << project }
it 'allows pull access' do
expect { pull_access_check }.not_to raise_error
end
it 'allows push access' do
expect { push_access_check }.not_to raise_error
end
end
context 'when the Deploykey does not have access to the project' do
it 'blocks pulls with "not found"' do
expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.')
end
it 'blocks pushes with "not found"' do
expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.')
end
end
end
context 'when actor is a User' do
context 'when the User can read the project' do
before { project.team << [user, :master] }
it 'allows pull access' do
expect { pull_access_check }.not_to raise_error
end
it 'allows push access' do
expect { push_access_check }.not_to raise_error
end
end
context 'when the User cannot read the project' do
it 'blocks pulls with "not found"' do
expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.')
end
it 'blocks pushes with "not found"' do
expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.')
end
end
end
# For backwards compatibility
context 'when actor is :ci' do
let(:actor) { :ci }
let(:authentication_abilities) { build_authentication_abilities }
it 'allows pull access' do
expect { pull_access_check }.not_to raise_error
end
it 'does not block pushes with "not found"' do
expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.')
end
end
end
context 'when actor is nil' do
let(:actor) { nil }
context 'when guests can read the project' do
let(:project) { create(:project, :repository, :public) }
it 'allows pull access' do
expect { pull_access_check }.not_to raise_error
end
it 'does not block pushes with "not found"' do
expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.')
end
end
context 'when guests cannot read the project' do
it 'blocks pulls with "not found"' do
expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.')
end
it 'blocks pushes with "not found"' do
expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.')
end
end
end
end
context 'when the project is nil' do
let(:project) { nil }
it 'blocks any command with "not found"' do
expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.')
expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.')
end
end
end
describe '#check_command_disabled!' do
before { project.team << [user, :master] }
context 'over http' do
let(:protocol) { 'http' }
context 'when the git-upload-pack command is disabled in config' do
before do
allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false)
end
context 'when calling git-upload-pack' do
it { expect { pull_access_check }.to raise_unauthorized('Pulling over HTTP is not allowed.') }
end
context 'when calling git-receive-pack' do
it { expect { push_access_check }.not_to raise_error }
end
end
context 'when the git-receive-pack command is disabled in config' do
before do
allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
end
context 'when calling git-receive-pack' do
it { expect { push_access_check }.to raise_unauthorized('Pushing over HTTP is not allowed.') }
end
context 'when calling git-upload-pack' do
it { expect { pull_access_check }.not_to raise_error }
end
end
end
end
describe '#check_download_access!' do
describe 'master permissions' do describe 'master permissions' do
before { project.team << [user, :master] } before { project.team << [user, :master] }
context 'pull code' do context 'pull code' do
it { expect(subject.allowed?).to be_truthy } it { expect { pull_access_check }.not_to raise_error }
end end
end end
...@@ -66,8 +208,7 @@ describe Gitlab::GitAccess, lib: true do ...@@ -66,8 +208,7 @@ describe Gitlab::GitAccess, lib: true do
before { project.team << [user, :guest] } before { project.team << [user, :guest] }
context 'pull code' do context 'pull code' do
it { expect(subject.allowed?).to be_falsey } it { expect { pull_access_check }.to raise_unauthorized('You are not allowed to download code from this project.') }
it { expect(subject.message).to match(/You are not allowed to download code/) }
end end
end end
...@@ -78,24 +219,22 @@ describe Gitlab::GitAccess, lib: true do ...@@ -78,24 +219,22 @@ describe Gitlab::GitAccess, lib: true do
end end
context 'pull code' do context 'pull code' do
it { expect(subject.allowed?).to be_falsey } it { expect { pull_access_check }.to raise_unauthorized('Your account has been blocked.') }
it { expect(subject.message).to match(/Your account has been blocked/) }
end end
end end
describe 'without access to project' do describe 'without access to project' do
context 'pull code' do context 'pull code' do
it { expect(subject.allowed?).to be_falsey } it { expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.') }
end end
context 'when project is public' do context 'when project is public' do
let(:public_project) { create(:project, :public, :repository) } let(:public_project) { create(:project, :public, :repository) }
let(:guest_access) { Gitlab::GitAccess.new(nil, public_project, 'web', authentication_abilities: []) } let(:access) { Gitlab::GitAccess.new(nil, public_project, 'web', authentication_abilities: []) }
subject { guest_access.check('git-upload-pack', '_any') }
context 'when repository is enabled' do context 'when repository is enabled' do
it 'give access to download code' do it 'give access to download code' do
expect(subject.allowed?).to be_truthy expect { pull_access_check }.not_to raise_error
end end
end end
...@@ -103,8 +242,7 @@ describe Gitlab::GitAccess, lib: true do ...@@ -103,8 +242,7 @@ describe Gitlab::GitAccess, lib: true do
it 'does not give access to download code' do it 'does not give access to download code' do
public_project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED) public_project.project_feature.update_attribute(:repository_access_level, ProjectFeature::DISABLED)
expect(subject.allowed?).to be_falsey expect { pull_access_check }.to raise_unauthorized('You are not allowed to download code from this project.')
expect(subject.message).to match(/You are not allowed to download code/)
end end
end end
end end
...@@ -118,26 +256,26 @@ describe Gitlab::GitAccess, lib: true do ...@@ -118,26 +256,26 @@ describe Gitlab::GitAccess, lib: true do
context 'when project is authorized' do context 'when project is authorized' do
before { key.projects << project } before { key.projects << project }
it { expect(subject).to be_allowed } it { expect { pull_access_check }.not_to raise_error }
end end
context 'when unauthorized' do context 'when unauthorized' do
context 'from public project' do context 'from public project' do
let(:project) { create(:project, :public, :repository) } let(:project) { create(:project, :public, :repository) }
it { expect(subject).to be_allowed } it { expect { pull_access_check }.not_to raise_error }
end end
context 'from internal project' do context 'from internal project' do
let(:project) { create(:project, :internal, :repository) } let(:project) { create(:project, :internal, :repository) }
it { expect(subject).not_to be_allowed } it { expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.') }
end end
context 'from private project' do context 'from private project' do
let(:project) { create(:project, :private, :repository) } let(:project) { create(:project, :private, :repository) }
it { expect(subject).not_to be_allowed } it { expect { pull_access_check }.to raise_not_found('The project you were looking for could not be found.') }
end end
end end
end end
...@@ -161,7 +299,7 @@ describe Gitlab::GitAccess, lib: true do ...@@ -161,7 +299,7 @@ describe Gitlab::GitAccess, lib: true do
let(:project) { create(:project, :repository, namespace: user.namespace) } let(:project) { create(:project, :repository, namespace: user.namespace) }
context 'pull code' do context 'pull code' do
it { expect { subject }.not_to raise_error } it { expect { pull_access_check }.not_to raise_error }
end end
end end
...@@ -169,7 +307,7 @@ describe Gitlab::GitAccess, lib: true do ...@@ -169,7 +307,7 @@ describe Gitlab::GitAccess, lib: true do
before { project.team << [user, :reporter] } before { project.team << [user, :reporter] }
context 'pull code' do context 'pull code' do
it { expect { subject }.not_to raise_error } it { expect { pull_access_check }.not_to raise_error }
end end
end end
...@@ -180,15 +318,23 @@ describe Gitlab::GitAccess, lib: true do ...@@ -180,15 +318,23 @@ describe Gitlab::GitAccess, lib: true do
before { project.team << [user, :reporter] } before { project.team << [user, :reporter] }
context 'pull code' do context 'pull code' do
it { expect { subject }.not_to raise_error } it { expect { pull_access_check }.not_to raise_error }
end end
end end
context 'when is not member of the project' do context 'when is not member of the project' do
context 'pull code' do context 'pull code' do
it { expect { subject }.not_to raise_error } it { expect { pull_access_check }.to raise_unauthorized('You are not allowed to download code from this project.') }
end
end end
end end
describe 'generic CI (build without a user)' do
let(:actor) { :ci }
context 'pull code' do
it { expect { pull_access_check }.not_to raise_error }
end
end end
end end
end end
...@@ -680,54 +826,43 @@ describe Gitlab::GitAccess, lib: true do ...@@ -680,54 +826,43 @@ describe Gitlab::GitAccess, lib: true do
end end
end end
shared_examples 'pushing code' do |can| describe 'build authentication abilities' do
subject { access.check('git-receive-pack', '_any') } let(:authentication_abilities) { build_authentication_abilities }
context 'when project is authorized' do context 'when project is authorized' do
before { authorize } before { project.team << [user, :reporter] }
it { expect(subject).public_send(can, be_allowed) } it { expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.') }
end end
context 'when unauthorized' do context 'when unauthorized' do
context 'to public project' do context 'to public project' do
let(:project) { create(:project, :public, :repository) } let(:project) { create(:project, :public, :repository) }
it { expect(subject).not_to be_allowed } it { expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.') }
end end
context 'to internal project' do context 'to internal project' do
let(:project) { create(:project, :internal, :repository) } let(:project) { create(:project, :internal, :repository) }
it { expect(subject).not_to be_allowed } it { expect { push_access_check }.to raise_unauthorized('You are not allowed to upload code for this project.') }
end end
context 'to private project' do context 'to private project' do
let(:project) { create(:project, :private, :repository) } let(:project) { create(:project, :private, :repository) }
it { expect(subject).not_to be_allowed } it { expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') }
end
end
end
describe 'build authentication abilities' do
let(:authentication_abilities) { build_authentication_abilities }
it_behaves_like 'pushing code', :not_to do
def authorize
project.team << [user, :reporter]
end end
end end
end end
context 'when the repository is read only' do context 'when the repository is read only' do
let(:project) { create(:project, :read_only_repository) }
it 'denies push access' do it 'denies push access' do
project = create(:project, :read_only_repository)
project.team << [user, :master] project.team << [user, :master]
check = access.check('git-receive-pack', '_any') expect { push_access_check }.to raise_unauthorized('The repository is temporarily read-only. Please try again later.')
expect(check).not_to be_allowed
end end
end end
...@@ -738,9 +873,29 @@ describe Gitlab::GitAccess, lib: true do ...@@ -738,9 +873,29 @@ describe Gitlab::GitAccess, lib: true do
context 'when deploy_key can push' do context 'when deploy_key can push' do
let(:can_push) { true } let(:can_push) { true }
it_behaves_like 'pushing code', :to do context 'when project is authorized' do
def authorize before { key.projects << project }
key.projects << project
it { expect { push_access_check }.not_to raise_error }
end
context 'when unauthorized' do
context 'to public project' do
let(:project) { create(:project, :public, :repository) }
it { expect { push_access_check }.to raise_unauthorized('This deploy key does not have write access to this project.') }
end
context 'to internal project' do
let(:project) { create(:project, :internal, :repository) }
it { expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') }
end
context 'to private project' do
let(:project) { create(:project, :private, :repository) }
it { expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') }
end end
end end
end end
...@@ -748,9 +903,29 @@ describe Gitlab::GitAccess, lib: true do ...@@ -748,9 +903,29 @@ describe Gitlab::GitAccess, lib: true do
context 'when deploy_key cannot push' do context 'when deploy_key cannot push' do
let(:can_push) { false } let(:can_push) { false }
it_behaves_like 'pushing code', :not_to do context 'when project is authorized' do
def authorize before { key.projects << project }
key.projects << project
it { expect { push_access_check }.to raise_unauthorized('This deploy key does not have write access to this project.') }
end
context 'when unauthorized' do
context 'to public project' do
let(:project) { create(:project, :public, :repository) }
it { expect { push_access_check }.to raise_unauthorized('This deploy key does not have write access to this project.') }
end
context 'to internal project' do
let(:project) { create(:project, :internal, :repository) }
it { expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') }
end
context 'to private project' do
let(:project) { create(:project, :private, :repository) }
it { expect { push_access_check }.to raise_not_found('The project you were looking for could not be found.') }
end end
end end
end end
...@@ -758,6 +933,14 @@ describe Gitlab::GitAccess, lib: true do ...@@ -758,6 +933,14 @@ describe Gitlab::GitAccess, lib: true do
private private
def raise_unauthorized(message)
raise_error(Gitlab::GitAccess::UnauthorizedError, message)
end
def raise_not_found(message)
raise_error(Gitlab::GitAccess::NotFoundError, message)
end
def build_authentication_abilities def build_authentication_abilities
[ [
:read_project, :read_project,
......
...@@ -2,7 +2,7 @@ require 'spec_helper' ...@@ -2,7 +2,7 @@ require 'spec_helper'
describe Gitlab::GitAccessWiki, lib: true do describe Gitlab::GitAccessWiki, lib: true do
let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities) } let(:access) { Gitlab::GitAccessWiki.new(user, project, 'web', authentication_abilities: authentication_abilities) }
let(:project) { create(:project, :repository) } let!(:project) { create(:project, :repository) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:changes) { ['6f6d7e7ed 570e7b2ab refs/heads/master'] } let(:changes) { ['6f6d7e7ed 570e7b2ab refs/heads/master'] }
let(:authentication_abilities) do let(:authentication_abilities) do
...@@ -22,15 +22,18 @@ describe Gitlab::GitAccessWiki, lib: true do ...@@ -22,15 +22,18 @@ describe Gitlab::GitAccessWiki, lib: true do
subject { access.check('git-receive-pack', changes) } subject { access.check('git-receive-pack', changes) }
it { expect(subject.allowed?).to be_truthy } it { expect { subject }.not_to raise_error }
context 'when in a secondary gitlab geo node' do context 'when in a secondary gitlab geo node' do
before do before do
allow(Gitlab::Geo).to receive(:enabled?) { true } allow(Gitlab::Geo).to receive(:enabled?) { true }
allow(Gitlab::Geo).to receive(:secondary?) { true } allow(Gitlab::Geo).to receive(:secondary?) { true }
allow(Gitlab::Geo).to receive(:license_allows?) { true }
end end
it { expect(subject.allowed?).to be_falsey } it 'does not give access to upload wiki code' do
expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, "You can't push code to a secondary GitLab Geo node.")
end
end end
end end
end end
...@@ -44,7 +47,7 @@ describe Gitlab::GitAccessWiki, lib: true do ...@@ -44,7 +47,7 @@ describe Gitlab::GitAccessWiki, lib: true do
context 'when wiki feature is enabled' do context 'when wiki feature is enabled' do
it 'give access to download wiki code' do it 'give access to download wiki code' do
expect(subject.allowed?).to be_truthy expect { subject }.not_to raise_error
end end
end end
...@@ -52,8 +55,7 @@ describe Gitlab::GitAccessWiki, lib: true do ...@@ -52,8 +55,7 @@ describe Gitlab::GitAccessWiki, lib: true do
it 'does not give access to download wiki code' do it 'does not give access to download wiki code' do
project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED) project.project_feature.update_attribute(:wiki_access_level, ProjectFeature::DISABLED)
expect(subject.allowed?).to be_falsey expect { subject }.to raise_error(Gitlab::GitAccess::UnauthorizedError, 'You are not allowed to download code from this project.')
expect(subject.message).to match(/You are not allowed to download code/)
end end
end end
end end
......
...@@ -5,330 +5,329 @@ describe 'Git HTTP requests', lib: true do ...@@ -5,330 +5,329 @@ describe 'Git HTTP requests', lib: true do
include WorkhorseHelpers include WorkhorseHelpers
include UserActivitiesHelpers include UserActivitiesHelpers
it "gives WWW-Authenticate hints" do shared_examples 'pulls require Basic HTTP Authentication' do
clone_get('doesnt/exist.git') context "when no credentials are provided" do
it "responds to downloads with status 401 Unauthorized (no project existence information leak)" do
download(path) do |response|
expect(response).to have_http_status(:unauthorized)
expect(response.header['WWW-Authenticate']).to start_with('Basic ') expect(response.header['WWW-Authenticate']).to start_with('Basic ')
end end
end
end
describe "User with no identities" do context "when only username is provided" do
let(:user) { create(:user) } it "responds to downloads with status 401 Unauthorized" do
let(:project) { create(:project, :repository, path: 'project.git-project') } download(path, user: user.username) do |response|
expect(response).to have_http_status(:unauthorized)
context "when the project doesn't exist" do expect(response.header['WWW-Authenticate']).to start_with('Basic ')
context "when no authentication is provided" do
it "responds with status 401 (no project existence information leak)" do
download('doesnt/exist.git') do |response|
expect(response).to have_http_status(401)
end end
end end
end end
context "when username and password are provided" do context "when username and password are provided" do
context "when authentication fails" do context "when authentication fails" do
it "responds with status 401" do it "responds to downloads with status 401 Unauthorized" do
download('doesnt/exist.git', user: user.username, password: "nope") do |response| download(path, user: user.username, password: "wrong-password") do |response|
expect(response).to have_http_status(401) expect(response).to have_http_status(:unauthorized)
expect(response.header['WWW-Authenticate']).to start_with('Basic ')
end end
end end
end end
context "when authentication succeeds" do context "when authentication succeeds" do
it "responds with status 404" do it "does not respond to downloads with status 401 Unauthorized" do
download('/doesnt/exist.git', user: user.username, password: user.password) do |response| download(path, user: user.username, password: user.password) do |response|
expect(response).to have_http_status(404) expect(response).not_to have_http_status(:unauthorized)
expect(response.header['WWW-Authenticate']).to be_nil
end end
end end
end end
end end
end end
context "when the Wiki for a project exists" do shared_examples 'pushes require Basic HTTP Authentication' do
it "responds with the right project" do context "when no credentials are provided" do
wiki = ProjectWiki.new(project) it "responds to uploads with status 401 Unauthorized (no project existence information leak)" do
project.update_attribute(:visibility_level, Project::PUBLIC) upload(path) do |response|
expect(response).to have_http_status(:unauthorized)
download("/#{wiki.repository.path_with_namespace}.git") do |response| expect(response.header['WWW-Authenticate']).to start_with('Basic ')
json_body = ActiveSupport::JSON.decode(response.body) end
expect(response).to have_http_status(200)
expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end end
end end
context 'but the repo is disabled' do context "when only username is provided" do
let(:project) { create(:project, :repository_disabled, :wiki_enabled) } it "responds to uploads with status 401 Unauthorized" do
let(:wiki) { ProjectWiki.new(project) } upload(path, user: user.username) do |response|
let(:path) { "/#{wiki.repository.path_with_namespace}.git" } expect(response).to have_http_status(:unauthorized)
expect(response.header['WWW-Authenticate']).to start_with('Basic ')
before do end
project.team << [user, :developer] end
end end
it 'allows clones' do context "when username and password are provided" do
download(path, user: user.username, password: user.password) do |response| context "when authentication fails" do
expect(response).to have_http_status(200) it "responds to uploads with status 401 Unauthorized" do
upload(path, user: user.username, password: "wrong-password") do |response|
expect(response).to have_http_status(:unauthorized)
expect(response.header['WWW-Authenticate']).to start_with('Basic ')
end
end end
end end
it 'allows pushes' do context "when authentication succeeds" do
it "does not respond to uploads with status 401 Unauthorized" do
upload(path, user: user.username, password: user.password) do |response| upload(path, user: user.username, password: user.password) do |response|
expect(response).to have_http_status(200) expect(response).not_to have_http_status(:unauthorized)
expect(response.header['WWW-Authenticate']).to be_nil
end end
end end
end end
end end
context "when the project exists" do
let(:path) { "#{project.path_with_namespace}.git" }
context "when the project is public" do
before do
project.update_attribute(:visibility_level, Project::PUBLIC)
end end
it "downloads get status 200" do shared_examples_for 'pulls are allowed' do
download(path, {}) do |response| it do
expect(response).to have_http_status(200) download(path, env) do |response|
expect(response).to have_http_status(:ok)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end end
end end
it "uploads get status 401" do
upload(path, {}) do |response|
expect(response).to have_http_status(401)
end
end end
context "with correct credentials" do shared_examples_for 'pushes are allowed' do
let(:env) { { user: user.username, password: user.password } } it do
it "uploads get status 403" do
upload(path, env) do |response| upload(path, env) do |response|
expect(response).to have_http_status(403) expect(response).to have_http_status(:ok)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
end end
end end
context 'but git-receive-pack is disabled' do describe "User with no identities" do
it "responds with status 404" do let(:user) { create(:user) }
allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
upload(path, env) do |response| context "when the project doesn't exist" do
expect(response).to have_http_status(403) let(:path) { 'doesnt/exist.git' }
end
end
end
end
context 'but git-upload-pack is disabled' do it_behaves_like 'pulls require Basic HTTP Authentication'
it "responds with status 404" do it_behaves_like 'pushes require Basic HTTP Authentication'
allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false)
download(path, {}) do |response| context 'when authenticated' do
expect(response).to have_http_status(404) it 'rejects downloads and uploads with 404 Not Found' do
end download_or_upload(path, user: user.username, password: user.password) do |response|
expect(response).to have_http_status(:not_found)
end end
end end
context 'when the request is not from gitlab-workhorse' do
it 'raises an exception' do
expect do
get("/#{project.path_with_namespace}.git/info/refs?service=git-upload-pack")
end.to raise_error(JWT::DecodeError)
end end
end end
context 'when the repo is public' do context "when requesting the Wiki" do
context 'but the repo is disabled' do let(:wiki) { ProjectWiki.new(project) }
it 'does not allow to clone the repo' do let(:path) { "/#{wiki.repository.path_with_namespace}.git" }
project = create(:project, :public, :repository_disabled)
download("#{project.path_with_namespace}.git", {}) do |response| context "when the project is public" do
expect(response).to have_http_status(:unauthorized) let(:project) { create(:project, :repository, :public, :wiki_enabled) }
end
end
end
context 'but the repo is enabled' do it_behaves_like 'pushes require Basic HTTP Authentication'
it 'allows to clone the repo' do
project = create(:project, :public, :repository_enabled)
download("#{project.path_with_namespace}.git", {}) do |response| context 'when unauthenticated' do
expect(response).to have_http_status(:ok) let(:env) { {} }
end
end
end
context 'but only project members are allowed' do it_behaves_like 'pulls are allowed'
it 'does not allow to clone the repo' do
project = create(:project, :public, :repository_private)
download("#{project.path_with_namespace}.git", {}) do |response| it "responds to pulls with the wiki's repo" do
expect(response).to have_http_status(:unauthorized) download(path) do |response|
end json_body = ActiveSupport::JSON.decode(response.body)
end
expect(json_body['RepoPath']).to include(wiki.repository.path_with_namespace)
end end
end end
end end
context "when Kerberos token is provided" do context 'when authenticated' do
let(:env) { { spnego_request_token: 'opaque_request_token' } } let(:env) { { user: user.username, password: user.password } }
context 'and as a developer on the team' do
before do before do
allow_any_instance_of(Projects::GitHttpController).to receive(:allow_kerberos_spnego_auth?).and_return(true) project.team << [user, :developer]
end end
context "when authentication fails because of invalid Kerberos token" do context 'but the repo is disabled' do
before do let(:project) { create(:project, :repository, :public, :repository_disabled, :wiki_enabled) }
allow_any_instance_of(Projects::GitHttpController).to receive(:spnego_credentials!).and_return(nil)
end
it "responds with status 401" do it_behaves_like 'pulls are allowed'
download(path, env) do |response| it_behaves_like 'pushes are allowed'
expect(response.status).to eq(401)
end
end end
end end
context "when authentication fails because of unknown Kerberos identity" do context 'and not on the team' do
before do it_behaves_like 'pulls are allowed'
allow_any_instance_of(Projects::GitHttpController).to receive(:spnego_credentials!).and_return("mylogin@FOO.COM")
end
it "responds with status 401" do it 'rejects pushes with 403 Forbidden' do
download(path, env) do |response| upload(path, env) do |response|
expect(response.status).to eq(401) expect(response).to have_http_status(:forbidden)
expect(response.body).to eq(git_access_wiki_error(:write_to_wiki))
end end
end end
end end
context "when authentication succeeds" do
before do
allow_any_instance_of(Projects::GitHttpController).to receive(:spnego_credentials!).and_return("mylogin@FOO.COM")
user.identities.create!(provider: "kerberos", extern_uid: "mylogin@FOO.COM")
end end
context "when the user has access to the project" do
before do
project.team << [user, :master]
end end
context "when the user is blocked" do context "when the project is private" do
let(:project) { create(:project, :repository, :private, :wiki_enabled) }
it_behaves_like 'pulls require Basic HTTP Authentication'
it_behaves_like 'pushes require Basic HTTP Authentication'
context 'when authenticated' do
context 'and as a developer on the team' do
before do before do
user.block project.team << [user, :developer]
project.team << [user, :master]
end end
it "responds with status 404" do context 'but the repo is disabled' do
download(path, env) do |response| let(:project) { create(:project, :repository, :private, :repository_disabled, :wiki_enabled) }
expect(response.status).to eq(404)
end
end
end
context "when the user isn't blocked", :redis do it 'allows clones' do
it "responds with status 200" do download(path, user: user.username, password: user.password) do |response|
download(path, env) do |response| expect(response).to have_http_status(:ok)
expect(response.status).to eq(200)
end end
end end
it 'updates the user last activity' do it 'pushes are allowed' do
download(path, env) do |_response| upload(path, user: user.username, password: user.password) do |response|
expect(user).to have_an_activity_record expect(response).to have_http_status(:ok)
end end
end end
end end
it "complies with RFC4559" do
allow_any_instance_of(Projects::GitHttpController).to receive(:spnego_response_token).and_return("opaque_response_token")
download(path, env) do |response|
expect(response.headers['WWW-Authenticate'].split("\n")).to include("Negotiate #{::Base64.strict_encode64('opaque_response_token')}")
end end
context 'and not on the team' do
it 'rejects clones with 404 Not Found' do
download(path, user: user.username, password: user.password) do |response|
expect(response).to have_http_status(:not_found)
expect(response.body).to eq(git_access_error(:project_not_found))
end end
end end
context "when the user doesn't have access to the project" do it 'rejects pushes with 404 Not Found' do
it "responds with status 404" do upload(path, user: user.username, password: user.password) do |response|
download(path, env) do |response| expect(response).to have_http_status(:not_found)
expect(response.status).to eq(404) expect(response.body).to eq(git_access_error(:project_not_found))
end end
end end
it "complies with RFC4559" do
allow_any_instance_of(Projects::GitHttpController).to receive(:spnego_response_token).and_return("opaque_response_token")
download(path, env) do |response|
expect(response.headers['WWW-Authenticate'].split("\n")).to include("Negotiate #{::Base64.strict_encode64('opaque_response_token')}")
end end
end end
end end
end end
context "when the project exists" do
let(:path) { "#{project.path_with_namespace}.git" }
context "when the project is public" do
let(:project) { create(:project, :repository, :public) }
it_behaves_like 'pushes require Basic HTTP Authentication'
context 'when not authenticated' do
let(:env) { {} }
it_behaves_like 'pulls are allowed'
end end
context "when repository is above size limit" do context "when authenticated" do
let(:env) { { user: user.username, password: user.password } } let(:env) { { user: user.username, password: user.password } }
context 'as a developer on the team' do
before do before do
project.team << [user, :master] project.team << [user, :developer]
end end
it 'responds with status 403' do it_behaves_like 'pulls are allowed'
allow_any_instance_of(Project).to receive(:above_size_limit?).and_return(true) it_behaves_like 'pushes are allowed'
context 'but git-receive-pack over HTTP is disabled in config' do
before do
allow(Gitlab.config.gitlab_shell).to receive(:receive_pack).and_return(false)
end
it 'rejects pushes with 403 Forbidden' do
upload(path, env) do |response| upload(path, env) do |response|
expect(response).to have_http_status(403) expect(response).to have_http_status(:forbidden)
expect(response.body).to eq(git_access_error(:receive_pack_disabled_over_http))
end end
end end
end end
context 'when license is not provided' do context 'but git-upload-pack over HTTP is disabled in config' do
let(:env) { { user: user.username, password: user.password } } it "rejects pushes with 403 Forbidden" do
allow(Gitlab.config.gitlab_shell).to receive(:upload_pack).and_return(false)
before do download(path, env) do |response|
project.team << [user, :master] expect(response).to have_http_status(:forbidden)
expect(response.body).to eq(git_access_error(:upload_pack_disabled_over_http))
end
end
end
end end
it 'responds with status 403' do context 'and not a member of the team' do
msg = 'No GitLab Enterprise Edition license has been provided yet. Pushing code and creation of issues and merge requests has been disabled. Ask an admin to upload a license to activate this functionality.' it_behaves_like 'pulls are allowed'
allow(License).to receive(:current).and_return(nil)
it 'rejects pushes with 403 Forbidden' do
upload(path, env) do |response| upload(path, env) do |response|
expect(response).to have_http_status(403) expect(response).to have_http_status(:forbidden)
expect(response.body).to eq(msg) expect(response.body).to eq(change_access_error(:push_code))
end
end end
end end
end end
context "when the project is private" do context 'when the request is not from gitlab-workhorse' do
before do it 'raises an exception' do
project.update_attribute(:visibility_level, Project::PRIVATE) expect do
get("/#{project.path_with_namespace}.git/info/refs?service=git-upload-pack")
end.to raise_error(JWT::DecodeError)
end
end end
context "when no authentication is provided" do context 'when the repo is public' do
it "responds with status 401 to downloads" do context 'but the repo is disabled' do
download(path, {}) do |response| let(:project) { create(:project, :public, :repository, :repository_disabled) }
expect(response).to have_http_status(401) let(:path) { "#{project.path_with_namespace}.git" }
let(:env) { {} }
it_behaves_like 'pulls require Basic HTTP Authentication'
it_behaves_like 'pushes require Basic HTTP Authentication'
end end
context 'but the repo is enabled' do
let(:project) { create(:project, :public, :repository, :repository_enabled) }
let(:path) { "#{project.path_with_namespace}.git" }
let(:env) { {} }
it_behaves_like 'pulls are allowed'
end end
it "responds with status 401 to uploads" do context 'but only project members are allowed' do
upload(path, {}) do |response| let(:project) { create(:project, :public, :repository, :repository_private) }
expect(response).to have_http_status(401)
it_behaves_like 'pulls require Basic HTTP Authentication'
it_behaves_like 'pushes require Basic HTTP Authentication'
end end
end end
end end
context "when the project is private" do
let(:project) { create(:project, :repository, :private) }
it_behaves_like 'pulls require Basic HTTP Authentication'
it_behaves_like 'pushes require Basic HTTP Authentication'
context "when username and password are provided" do context "when username and password are provided" do
let(:env) { { user: user.username, password: 'nope' } } let(:env) { { user: user.username, password: 'nope' } }
context "when authentication fails" do context "when authentication fails" do
it "responds with status 401" do
download(path, env) do |response|
expect(response).to have_http_status(401)
end
end
context "when the user is IP banned" do context "when the user is IP banned" do
it "responds with status 401" do it "responds with status 401" do
expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true) expect(Rack::Attack::Allow2Ban).to receive(:filter).and_return(true)
...@@ -336,7 +335,7 @@ describe 'Git HTTP requests', lib: true do ...@@ -336,7 +335,7 @@ describe 'Git HTTP requests', lib: true do
clone_get(path, env) clone_get(path, env)
expect(response).to have_http_status(401) expect(response).to have_http_status(:unauthorized)
end end
end end
end end
...@@ -350,37 +349,39 @@ describe 'Git HTTP requests', lib: true do ...@@ -350,37 +349,39 @@ describe 'Git HTTP requests', lib: true do
end end
context "when the user is blocked" do context "when the user is blocked" do
it "responds with status 401" do it "rejects pulls with 401 Unauthorized" do
user.block user.block
project.team << [user, :master] project.team << [user, :master]
download(path, env) do |response| download(path, env) do |response|
expect(response).to have_http_status(401) expect(response).to have_http_status(:unauthorized)
end end
end end
it "responds with status 401 for unknown projects (no project existence information leak)" do it "rejects pulls with 401 Unauthorized for unknown projects (no project existence information leak)" do
user.block user.block
download('doesnt/exist.git', env) do |response| download('doesnt/exist.git', env) do |response|
expect(response).to have_http_status(401) expect(response).to have_http_status(:unauthorized)
end end
end end
end end
context "when the user isn't blocked" do context "when the user isn't blocked" do
it "downloads get status 200" do it "resets the IP in Rack Attack on download" do
expect(Rack::Attack::Allow2Ban).to receive(:reset) expect(Rack::Attack::Allow2Ban).to receive(:reset).twice
clone_get(path, env) download(path, env) do
expect(response).to have_http_status(:ok)
expect(response).to have_http_status(200)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end end
end
it "uploads get status 200" do it "resets the IP in Rack Attack on upload" do
upload(path, env) do |response| expect(Rack::Attack::Allow2Ban).to receive(:reset).twice
expect(response).to have_http_status(200)
upload(path, env) do
expect(response).to have_http_status(:ok)
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end end
end end
...@@ -400,56 +401,43 @@ describe 'Git HTTP requests', lib: true do ...@@ -400,56 +401,43 @@ describe 'Git HTTP requests', lib: true do
@token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api") @token = Doorkeeper::AccessToken.create!(application_id: application.id, resource_owner_id: user.id, scopes: "api")
end end
it "downloads get status 200" do let(:path) { "#{project.path_with_namespace}.git" }
clone_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token let(:env) { { user: 'oauth2', password: @token.token } }
expect(response).to have_http_status(200) it_behaves_like 'pulls are allowed'
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) it_behaves_like 'pushes are allowed'
end
it "uploads get status 200" do
push_get "#{project.path_with_namespace}.git", user: 'oauth2', password: @token.token
expect(response).to have_http_status(200)
end
end end
context 'when user has 2FA enabled' do context 'when user has 2FA enabled' do
let(:user) { create(:user, :two_factor) } let(:user) { create(:user, :two_factor) }
let(:access_token) { create(:personal_access_token, user: user) } let(:access_token) { create(:personal_access_token, user: user) }
let(:path) { "#{project.path_with_namespace}.git" }
before do before do
project.team << [user, :master] project.team << [user, :master]
end end
context 'when username and password are provided' do context 'when username and password are provided' do
it 'rejects the clone attempt' do it 'rejects pulls with 2FA error message' do
download("#{project.path_with_namespace}.git", user: user.username, password: user.password) do |response| download(path, user: user.username, password: user.password) do |response|
expect(response).to have_http_status(401) expect(response).to have_http_status(:unauthorized)
expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP') expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP')
end end
end end
it 'rejects the push attempt' do it 'rejects the push attempt' do
upload("#{project.path_with_namespace}.git", user: user.username, password: user.password) do |response| upload(path, user: user.username, password: user.password) do |response|
expect(response).to have_http_status(401) expect(response).to have_http_status(:unauthorized)
expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP') expect(response.body).to include('You have 2FA enabled, please use a personal access token for Git over HTTP')
end end
end end
end end
context 'when username and personal access token are provided' do context 'when username and personal access token are provided' do
it 'allows clones' do let(:env) { { user: user.username, password: access_token.token } }
download("#{project.path_with_namespace}.git", user: user.username, password: access_token.token) do |response|
expect(response).to have_http_status(200)
end
end
it 'allows pushes' do it_behaves_like 'pulls are allowed'
upload("#{project.path_with_namespace}.git", user: user.username, password: access_token.token) do |response| it_behaves_like 'pushes are allowed'
expect(response).to have_http_status(200)
end
end
end end
end end
...@@ -485,15 +473,15 @@ describe 'Git HTTP requests', lib: true do ...@@ -485,15 +473,15 @@ describe 'Git HTTP requests', lib: true do
end end
context "when the user doesn't have access to the project" do context "when the user doesn't have access to the project" do
it "downloads get status 404" do it "pulls get status 404 Not Found" do
download(path, user: user.username, password: user.password) do |response| download(path, user: user.username, password: user.password) do |response|
expect(response).to have_http_status(404) expect(response).to have_http_status(:not_found)
end end
end end
it "uploads get status 404" do it "uploads get status 404 Not Found" do
upload(path, user: user.username, password: user.password) do |response| upload(path, user: user.username, password: user.password) do |response|
expect(response).to have_http_status(404) expect(response).to have_http_status(:not_found)
end end
end end
end end
...@@ -501,28 +489,41 @@ describe 'Git HTTP requests', lib: true do ...@@ -501,28 +489,41 @@ describe 'Git HTTP requests', lib: true do
end end
context "when a gitlab ci token is provided" do context "when a gitlab ci token is provided" do
let(:project) { create(:project, :repository) }
let(:build) { create(:ci_build, :running) } let(:build) { create(:ci_build, :running) }
let(:project) { build.project }
let(:other_project) { create(:empty_project) } let(:other_project) { create(:empty_project) }
before do
build.update!(project: project) # can't associate it on factory create
end
context 'when build created by system is authenticated' do context 'when build created by system is authenticated' do
it "downloads get status 200" do let(:path) { "#{project.path_with_namespace}.git" }
clone_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token let(:env) { { user: 'gitlab-ci-token', password: build.token } }
expect(response).to have_http_status(200) it_behaves_like 'pulls are allowed'
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
it "uploads get status 401 (no project existence information leak)" do # A non-401 here is not an information leak since the system is
push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token # "authenticated" as CI using the correct token. It does not have
# push access, so pushes should be rejected as forbidden, and giving
# a reason is fine.
#
# We know for sure it is not an information leak since pulls using
# the build token must be allowed.
it "rejects pushes with 403 Forbidden" do
push_get(path, env)
expect(response).to have_http_status(401) expect(response).to have_http_status(:forbidden)
expect(response.body).to eq(git_access_error(:upload))
end end
it "downloads from other project get status 404" do # We are "authenticated" as CI using a valid token here. But we are
clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token # not authorized to see any other project, so return "not found".
it "rejects pulls for other project with 404 Not Found" do
clone_get("#{other_project.path_with_namespace}.git", env)
expect(response).to have_http_status(404) expect(response).to have_http_status(:not_found)
expect(response.body).to eq(git_access_error(:project_not_found))
end end
end end
...@@ -533,31 +534,27 @@ describe 'Git HTTP requests', lib: true do ...@@ -533,31 +534,27 @@ describe 'Git HTTP requests', lib: true do
end end
shared_examples 'can download code only' do shared_examples 'can download code only' do
it 'downloads get status 200' do let(:path) { "#{project.path_with_namespace}.git" }
allow_any_instance_of(Repository). let(:env) { { user: 'gitlab-ci-token', password: build.token } }
to receive(:exists?).and_return(true)
clone_get "#{project.path_with_namespace}.git", it_behaves_like 'pulls are allowed'
user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(200) context 'when the repo does not exist' do
expect(response.content_type.to_s).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE) let(:project) { create(:empty_project) }
end
it 'downloads from non-existing repository and gets 403' do it 'rejects pulls with 403 Forbidden' do
allow_any_instance_of(Repository). clone_get path, env
to receive(:exists?).and_return(false)
clone_get "#{project.path_with_namespace}.git", expect(response).to have_http_status(:forbidden)
user: 'gitlab-ci-token', password: build.token expect(response.body).to eq(git_access_error(:no_repo))
end
expect(response).to have_http_status(403)
end end
it 'uploads get status 403' do it 'rejects pushes with 403 Forbidden' do
push_get "#{project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token push_get path, env
expect(response).to have_http_status(401) expect(response).to have_http_status(:forbidden)
expect(response.body).to eq(git_access_error(:upload))
end end
end end
...@@ -566,10 +563,10 @@ describe 'Git HTTP requests', lib: true do ...@@ -566,10 +563,10 @@ describe 'Git HTTP requests', lib: true do
it_behaves_like 'can download code only' it_behaves_like 'can download code only'
it 'downloads from other project get status 403' do it 'downloads from other project get status 403 Forbidden' do
clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(403) expect(response).to have_http_status(:forbidden)
end end
end end
...@@ -578,18 +575,147 @@ describe 'Git HTTP requests', lib: true do ...@@ -578,18 +575,147 @@ describe 'Git HTTP requests', lib: true do
it_behaves_like 'can download code only' it_behaves_like 'can download code only'
it 'downloads from other project get status 404' do it 'downloads from other project get status 404 Not Found' do
clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token clone_get "#{other_project.path_with_namespace}.git", user: 'gitlab-ci-token', password: build.token
expect(response).to have_http_status(404) expect(response).to have_http_status(:not_found)
end
end
end
end
context "when Kerberos token is provided" do
let(:env) { { spnego_request_token: 'opaque_request_token' } }
before do
allow_any_instance_of(Projects::GitHttpController).to receive(:allow_kerberos_spnego_auth?).and_return(true)
end
context "when authentication fails because of invalid Kerberos token" do
before do
allow_any_instance_of(Projects::GitHttpController).to receive(:spnego_credentials!).and_return(nil)
end
it "responds with status 401 Unauthorized" do
download(path, env) do |response|
expect(response).to have_http_status(:unauthorized)
end
end
end
context "when authentication fails because of unknown Kerberos identity" do
before do
allow_any_instance_of(Projects::GitHttpController).to receive(:spnego_credentials!).and_return("mylogin@FOO.COM")
end
it "responds with status 401 Unauthorized" do
download(path, env) do |response|
expect(response).to have_http_status(:unauthorized)
end
end
end
context "when authentication succeeds" do
before do
allow_any_instance_of(Projects::GitHttpController).to receive(:spnego_credentials!).and_return("mylogin@FOO.COM")
user.identities.create!(provider: "kerberos", extern_uid: "mylogin@FOO.COM")
end
context "when the user has access to the project" do
before do
project.team << [user, :master]
end
context "when the user is blocked" do
before do
user.block
project.team << [user, :master]
end
it "responds with status 403 Forbidden" do
download(path, env) do |response|
expect(response).to have_http_status(:forbidden)
end
end
end
context "when the user isn't blocked", :redis do
it "responds with status 200 OK" do
download(path, env) do |response|
expect(response).to have_http_status(:ok)
end
end
it 'updates the user last activity' do
download(path, env) do |_response|
expect(user).to have_an_activity_record
end
end
end
it "complies with RFC4559" do
allow_any_instance_of(Projects::GitHttpController).to receive(:spnego_response_token).and_return("opaque_response_token")
download(path, env) do |response|
expect(response.headers['WWW-Authenticate'].split("\n")).to include("Negotiate #{::Base64.strict_encode64('opaque_response_token')}")
end
end
end
context "when the user doesn't have access to the project" do
it "responds with status 404 Not Found" do
download(path, env) do |response|
expect(response).to have_http_status(:not_found)
end
end
it "complies with RFC4559" do
allow_any_instance_of(Projects::GitHttpController).to receive(:spnego_response_token).and_return("opaque_response_token")
download(path, env) do |response|
expect(response.headers['WWW-Authenticate'].split("\n")).to include("Negotiate #{::Base64.strict_encode64('opaque_response_token')}")
end
end
end
end
end
context "when repository is above size limit" do
let(:env) { { user: user.username, password: user.password } }
before do
project.team << [user, :master]
end end
it 'responds with status 403 Forbidden' do
allow_any_instance_of(Project).to receive(:above_size_limit?).and_return(true)
upload(path, env) do |response|
expect(response).to have_http_status(:forbidden)
end
end
end
context 'when license is not provided' do
let(:env) { { user: user.username, password: user.password } }
before do
project.team << [user, :master]
end end
it 'responds with status 403 Forbidden' do
msg = 'No GitLab Enterprise Edition license has been provided yet. Pushing code and creation of issues and merge requests has been disabled. Ask an admin to upload a license to activate this functionality.'
allow(License).to receive(:current).and_return(nil)
upload(path, env) do |response|
expect(response).to have_http_status(:forbidden)
expect(response.body).to eq(msg)
end end
end end
end end
end end
context "when the project path doesn't end in .git" do context "when the project path doesn't end in .git" do
let(:project) { create(:project, :repository, :public, path: 'project.git-project') }
context "GET info/refs" do context "GET info/refs" do
let(:path) { "/#{project.path_with_namespace}/info/refs" } let(:path) { "/#{project.path_with_namespace}/info/refs" }
...@@ -643,7 +769,7 @@ describe 'Git HTTP requests', lib: true do ...@@ -643,7 +769,7 @@ describe 'Git HTTP requests', lib: true do
end end
context "retrieving an info/refs file" do context "retrieving an info/refs file" do
before { project.update_attribute(:visibility_level, Project::PUBLIC) } let(:project) { create(:project, :repository, :public) }
context "when the file exists" do context "when the file exists" do
before do before do
...@@ -657,7 +783,7 @@ describe 'Git HTTP requests', lib: true do ...@@ -657,7 +783,7 @@ describe 'Git HTTP requests', lib: true do
end end
it "returns the file" do it "returns the file" do
expect(response).to have_http_status(200) expect(response).to have_http_status(:ok)
end end
end end
...@@ -665,7 +791,8 @@ describe 'Git HTTP requests', lib: true do ...@@ -665,7 +791,8 @@ describe 'Git HTTP requests', lib: true do
before { get "/#{project.path_with_namespace}/blob/master/info/refs" } before { get "/#{project.path_with_namespace}/blob/master/info/refs" }
it "returns not found" do it "returns not found" do
expect(response).to have_http_status(404) expect(response).to have_http_status(:not_found)
end
end end
end end
end end
...@@ -674,6 +801,7 @@ describe 'Git HTTP requests', lib: true do ...@@ -674,6 +801,7 @@ describe 'Git HTTP requests', lib: true do
describe "User with LDAP identity" do describe "User with LDAP identity" do
let(:user) { create(:omniauth_user, extern_uid: dn) } let(:user) { create(:omniauth_user, extern_uid: dn) }
let(:dn) { 'uid=john,ou=people,dc=example,dc=com' } let(:dn) { 'uid=john,ou=people,dc=example,dc=com' }
let(:path) { 'doesnt/exist.git' }
before do before do
allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true) allow(Gitlab::LDAP::Config).to receive(:enabled?).and_return(true)
...@@ -681,45 +809,37 @@ describe 'Git HTTP requests', lib: true do ...@@ -681,45 +809,37 @@ describe 'Git HTTP requests', lib: true do
allow(Gitlab::LDAP::Authentication).to receive(:login).with(user.username, user.password).and_return(user) allow(Gitlab::LDAP::Authentication).to receive(:login).with(user.username, user.password).and_return(user)
end end
context "when authentication fails" do it_behaves_like 'pulls require Basic HTTP Authentication'
context "when no authentication is provided" do it_behaves_like 'pushes require Basic HTTP Authentication'
it "responds with status 401" do
download('doesnt/exist.git') do |response|
expect(response).to have_http_status(401)
end
end
end
context "when username and invalid password are provided" do
it "responds with status 401" do
download('doesnt/exist.git', user: user.username, password: "nope") do |response|
expect(response).to have_http_status(401)
end
end
end
end
context "when authentication succeeds" do context "when authentication succeeds" do
context "when the project doesn't exist" do context "when the project doesn't exist" do
it "responds with status 404" do it "responds with status 404 Not Found" do
download('/doesnt/exist.git', user: user.username, password: user.password) do |response| download(path, user: user.username, password: user.password) do |response|
expect(response).to have_http_status(404) expect(response).to have_http_status(:not_found)
end end
end end
end end
context "when the project exists" do context "when the project exists" do
let(:project) { create(:project, path: 'project.git-project') } let(:project) { create(:project, :repository) }
let(:path) { "#{project.full_path}.git" }
let(:env) { { user: user.username, password: user.password } }
context 'and the user is on the team' do
before do before do
project.team << [user, :master] project.team << [user, :master]
end end
it "responds with status 200" do it "responds with status 200" do
clone_get(path, user: user.username, password: user.password) do |response| clone_get(path, env) do |response|
expect(response).to have_http_status(200) expect(response).to have_http_status(200)
end end
end end
it_behaves_like 'pulls are allowed'
it_behaves_like 'pushes are allowed'
end
end end
end end
end end
......
...@@ -795,8 +795,8 @@ describe 'Git LFS API and storage' do ...@@ -795,8 +795,8 @@ describe 'Git LFS API and storage' do
context 'tries to push to own project' do context 'tries to push to own project' do
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it 'responds with 401' do it 'responds with 403 (not 404 because project is public)' do
expect(response).to have_http_status(401) expect(response).to have_http_status(403)
end end
end end
...@@ -805,8 +805,9 @@ describe 'Git LFS API and storage' do ...@@ -805,8 +805,9 @@ describe 'Git LFS API and storage' do
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it 'responds with 401' do # I'm not sure what this tests that is different from the previous test
expect(response).to have_http_status(401) it 'responds with 403 (not 404 because project is public)' do
expect(response).to have_http_status(403)
end end
end end
end end
...@@ -814,8 +815,8 @@ describe 'Git LFS API and storage' do ...@@ -814,8 +815,8 @@ describe 'Git LFS API and storage' do
context 'does not have user' do context 'does not have user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline) } let(:build) { create(:ci_build, :running, pipeline: pipeline) }
it 'responds with 401' do it 'responds with 403 (not 404 because project is public)' do
expect(response).to have_http_status(401) expect(response).to have_http_status(403)
end end
end end
end end
...@@ -1026,8 +1027,8 @@ describe 'Git LFS API and storage' do ...@@ -1026,8 +1027,8 @@ describe 'Git LFS API and storage' do
put_authorize put_authorize
end end
it 'responds with 401' do it 'responds with 403 (not 404 because the build user can read the project)' do
expect(response).to have_http_status(401) expect(response).to have_http_status(403)
end end
end end
...@@ -1040,8 +1041,8 @@ describe 'Git LFS API and storage' do ...@@ -1040,8 +1041,8 @@ describe 'Git LFS API and storage' do
put_authorize put_authorize
end end
it 'responds with 401' do it 'responds with 404 (do not leak non-public project existence)' do
expect(response).to have_http_status(401) expect(response).to have_http_status(404)
end end
end end
end end
...@@ -1053,8 +1054,8 @@ describe 'Git LFS API and storage' do ...@@ -1053,8 +1054,8 @@ describe 'Git LFS API and storage' do
put_authorize put_authorize
end end
it 'responds with 401' do it 'responds with 404 (do not leak non-public project existence)' do
expect(response).to have_http_status(401) expect(response).to have_http_status(404)
end end
end end
end end
...@@ -1126,8 +1127,8 @@ describe 'Git LFS API and storage' do ...@@ -1126,8 +1127,8 @@ describe 'Git LFS API and storage' do
context 'tries to push to own project' do context 'tries to push to own project' do
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it 'responds with 401' do it 'responds with 403 (not 404 because project is public)' do
expect(response).to have_http_status(401) expect(response).to have_http_status(403)
end end
end end
...@@ -1136,8 +1137,9 @@ describe 'Git LFS API and storage' do ...@@ -1136,8 +1137,9 @@ describe 'Git LFS API and storage' do
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it 'responds with 401' do # I'm not sure what this tests that is different from the previous test
expect(response).to have_http_status(401) it 'responds with 403 (not 404 because project is public)' do
expect(response).to have_http_status(403)
end end
end end
end end
...@@ -1145,8 +1147,8 @@ describe 'Git LFS API and storage' do ...@@ -1145,8 +1147,8 @@ describe 'Git LFS API and storage' do
context 'does not have user' do context 'does not have user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline) } let(:build) { create(:ci_build, :running, pipeline: pipeline) }
it 'responds with 401' do it 'responds with 403 (not 404 because project is public)' do
expect(response).to have_http_status(401) expect(response).to have_http_status(403)
end end
end end
end end
......
...@@ -35,9 +35,14 @@ module GitHttpHelpers ...@@ -35,9 +35,14 @@ module GitHttpHelpers
yield response yield response
end end
def download_or_upload(*args, &block)
download(*args, &block)
upload(*args, &block)
end
def auth_env(user, password, spnego_request_token) def auth_env(user, password, spnego_request_token)
env = workhorse_internal_api_request_header env = workhorse_internal_api_request_header
if user && password if user
env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user, password) env['HTTP_AUTHORIZATION'] = ActionController::HttpAuthentication::Basic.encode_credentials(user, password)
elsif spnego_request_token elsif spnego_request_token
env['HTTP_AUTHORIZATION'] = "Negotiate #{::Base64.strict_encode64('opaque_request_token')}" env['HTTP_AUTHORIZATION'] = "Negotiate #{::Base64.strict_encode64('opaque_request_token')}"
...@@ -45,4 +50,19 @@ module GitHttpHelpers ...@@ -45,4 +50,19 @@ module GitHttpHelpers
env env
end end
def git_access_error(error_key)
message = Gitlab::GitAccess::ERROR_MESSAGES[error_key]
message || raise("GitAccess error message key '#{error_key}' not found")
end
def git_access_wiki_error(error_key)
message = Gitlab::GitAccessWiki::ERROR_MESSAGES[error_key]
message || raise("GitAccessWiki error message key '#{error_key}' not found")
end
def change_access_error(error_key)
message = Gitlab::Checks::ChangeAccess::ERROR_MESSAGES[error_key]
message || raise("ChangeAccess error message key '#{error_key}' not found")
end
end end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment