Commit 78977b06 authored by Stan Hu's avatar Stan Hu

Merge branch '207869-lfs-enabled-checks' into 'master'

Block LFS requests on snippets

See merge request gitlab-org/gitlab!45874
parents 7721a229 63e1fcb7
# frozen_string_literal: true
# This concern assumes:
# - a `#container` accessor
# - a `#project` accessor
# - a `#user` accessor
# - a `#authentication_result` accessor
......@@ -11,6 +12,7 @@
# - a `#has_authentication_ability?(ability)` method
module LfsRequest
extend ActiveSupport::Concern
include Gitlab::Utils::StrongMemoize
CONTENT_TYPE = 'application/vnd.git-lfs+json'
......@@ -29,16 +31,19 @@ module LfsRequest
message: _('Git LFS is not enabled on this GitLab server, contact your admin.'),
documentation_url: help_url
},
content_type: CONTENT_TYPE,
status: :not_implemented
)
end
def lfs_check_access!
return render_lfs_not_found unless project
return render_lfs_not_found unless container&.lfs_enabled?
return if download_request? && lfs_download_access?
return if upload_request? && lfs_upload_access?
if project.public? || can?(user, :read_project, project)
# Only return a 403 response if the user has download access permission,
# otherwise return a 404 to avoid exposing the existence of the container.
if lfs_download_access?
lfs_forbidden!
else
render_lfs_not_found
......@@ -72,9 +77,9 @@ module LfsRequest
end
def lfs_download_access?
return false unless project.lfs_enabled?
ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code? || deploy_token_can_download_code?
strong_memoize(:lfs_download_access) do
ci? || lfs_deploy_token? || user_can_download_code? || build_can_download_code? || deploy_token_can_download_code?
end
end
def deploy_token_can_download_code?
......@@ -93,11 +98,12 @@ module LfsRequest
end
def lfs_upload_access?
return false unless project.lfs_enabled?
return false unless has_authentication_ability?(:push_code)
return false if limit_exceeded?
strong_memoize(:lfs_upload_access) do
next false unless has_authentication_ability?(:push_code)
next false if limit_exceeded?
lfs_deploy_token? || can?(user, :push_code, project)
lfs_deploy_token? || can?(user, :push_code, project)
end
end
def lfs_deploy_token?
......
......@@ -6,7 +6,7 @@ module Repositories
include KerberosSpnegoHelper
include Gitlab::Utils::StrongMemoize
attr_reader :authentication_result, :redirected_path, :container
attr_reader :authentication_result, :redirected_path
delegate :actor, :authentication_abilities, to: :authentication_result, allow_nil: true
delegate :type, to: :authentication_result, allow_nil: true, prefix: :auth_result
......@@ -75,6 +75,12 @@ module Repositories
headers['Www-Authenticate'] = challenges.join("\n") if challenges.any?
end
def container
parse_repo_path unless defined?(@container)
@container
end
def project
parse_repo_path unless defined?(@project)
......
......@@ -17,9 +17,9 @@ module Repositories
end
if download_request?
render json: { objects: download_objects! }
render json: { objects: download_objects! }, content_type: LfsRequest::CONTENT_TYPE
elsif upload_request?
render json: { objects: upload_objects! }
render json: { objects: upload_objects! }, content_type: LfsRequest::CONTENT_TYPE
else
raise "Never reached"
end
......@@ -31,6 +31,7 @@ module Repositories
message: _('Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.'),
documentation_url: "#{Gitlab.config.gitlab.url}/help"
},
content_type: LfsRequest::CONTENT_TYPE,
status: :not_implemented
)
end
......
......@@ -29,7 +29,7 @@ module Repositories
def upload_finalize
if store_file!(oid, size)
head 200
head 200, content_type: LfsRequest::CONTENT_TYPE
else
render plain: 'Unprocessable entity', status: :unprocessable_entity
end
......
---
title: Block LFS requests on snippets
merge_request: 45874
author:
type: fixed
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe LfsRequest do
include ProjectForksHelper
controller(Repositories::GitHttpClientController) do
# `described_class` is not available in this context
include LfsRequest
def show
head :ok
end
def project
@project ||= Project.find_by(id: params[:id])
end
def download_request?
true
end
def upload_request?
false
end
def ci?
false
end
end
let(:project) { create(:project, :public) }
before do
stub_lfs_setting(enabled: true)
end
context 'user is authenticated without access to lfs' do
before do
allow(controller).to receive(:authenticate_user)
allow(controller).to receive(:authentication_result) do
Gitlab::Auth::Result.new
end
end
context 'with access to the project' do
it 'returns 403' do
get :show, params: { id: project.id }
expect(response).to have_gitlab_http_status(:forbidden)
end
end
context 'without access to the project' do
context 'project does not exist' do
it 'returns 404' do
get :show, params: { id: 'does not exist' }
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'project is private' do
let(:project) { create(:project, :private) }
it 'returns 404' do
get :show, params: { id: project.id }
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
end
end
......@@ -17,5 +17,9 @@ FactoryBot.define do
container { project }
end
trait :empty_repo do
after(:create, &:create_wiki_repository)
end
end
end
......@@ -9,18 +9,17 @@ RSpec.describe 'Git LFS API and storage' do
let_it_be(:project, reload: true) { create(:project, :repository) }
let_it_be(:other_project) { create(:project, :repository) }
let_it_be(:user) { create(:user) }
let!(:lfs_object) { create(:lfs_object, :with_file) }
let(:lfs_object) { create(:lfs_object, :with_file) }
let(:headers) do
{
'Authorization' => authorization,
'X-Sendfile-Type' => sendfile
'X-Sendfile-Type' => 'X-Sendfile'
}.compact
end
let(:include_workhorse_jwt_header) { true }
let(:authorization) { }
let(:sendfile) { }
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:sample_oid) { lfs_object.oid }
......@@ -37,18 +36,6 @@ RSpec.describe 'Git LFS API and storage' do
stub_lfs_setting(enabled: lfs_enabled)
end
describe 'when LFS is disabled' do
let(:lfs_enabled) { false }
let(:body) { upload_body(multiple_objects) }
let(:authorization) { authorize_user }
before do
post_lfs_json batch_url(project), body, headers
end
it_behaves_like 'LFS http 501 response'
end
context 'project specific LFS settings' do
let(:body) { upload_body(sample_object) }
let(:authorization) { authorize_user }
......@@ -60,105 +47,36 @@ RSpec.describe 'Git LFS API and storage' do
subject
end
context 'with LFS disabled globally' do
let(:lfs_enabled) { false }
describe 'LFS disabled in project' do
let(:project_lfs_enabled) { false }
context 'when uploading' do
subject { post_lfs_json(batch_url(project), body, headers) }
it_behaves_like 'LFS http 501 response'
end
describe 'LFS disabled in project' do
let(:project_lfs_enabled) { false }
context 'when downloading' do
subject { get(objects_url(project, sample_oid), params: {}, headers: headers) }
context 'when uploading' do
subject { post_lfs_json(batch_url(project), body, headers) }
it_behaves_like 'LFS http 501 response'
end
it_behaves_like 'LFS http 404 response'
end
describe 'LFS enabled in project' do
let(:project_lfs_enabled) { true }
context 'when uploading' do
subject { post_lfs_json(batch_url(project), body, headers) }
it_behaves_like 'LFS http 501 response'
end
context 'when downloading' do
subject { get(objects_url(project, sample_oid), params: {}, headers: headers) }
context 'when downloading' do
subject { get(objects_url(project, sample_oid), params: {}, headers: headers) }
it_behaves_like 'LFS http 501 response'
end
it_behaves_like 'LFS http 404 response'
end
end
context 'with LFS enabled globally' do
describe 'LFS disabled in project' do
let(:project_lfs_enabled) { false }
context 'when uploading' do
subject { post_lfs_json(batch_url(project), body, headers) }
it_behaves_like 'LFS http 403 response'
end
context 'when downloading' do
subject { get(objects_url(project, sample_oid), params: {}, headers: headers) }
it_behaves_like 'LFS http 403 response'
end
end
describe 'LFS enabled in project' do
let(:project_lfs_enabled) { true }
context 'when uploading' do
subject { post_lfs_json(batch_url(project), body, headers) }
it_behaves_like 'LFS http 200 response'
end
describe 'LFS enabled in project' do
let(:project_lfs_enabled) { true }
context 'when downloading' do
subject { get(objects_url(project, sample_oid), params: {}, headers: headers) }
context 'when uploading' do
subject { post_lfs_json(batch_url(project), body, headers) }
it_behaves_like 'LFS http 200 response'
end
it_behaves_like 'LFS http 200 response'
end
end
end
describe 'deprecated API' do
let(:authorization) { authorize_user }
context 'when downloading' do
subject { get(objects_url(project, sample_oid), params: {}, headers: headers) }
shared_examples 'deprecated request' do
before do
subject
it_behaves_like 'LFS http 200 blob response'
end
it_behaves_like 'LFS http expected response code and message' do
let(:response_code) { 501 }
let(:message) { 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.' }
end
end
context 'when fetching LFS object using deprecated API' do
subject { get(deprecated_objects_url(project, sample_oid), params: {}, headers: headers) }
it_behaves_like 'deprecated request'
end
context 'when handling LFS request using deprecated API' do
subject { post_lfs_json(deprecated_objects_url(project), nil, headers) }
it_behaves_like 'deprecated request'
end
def deprecated_objects_url(project, oid = nil)
File.join(["#{project.http_url_to_repo}/info/lfs/objects/", oid].compact)
end
end
......@@ -167,196 +85,133 @@ RSpec.describe 'Git LFS API and storage' do
let(:before_get) { }
before do
project.lfs_objects << lfs_object
update_permissions
before_get
get objects_url(project, sample_oid), params: {}, headers: headers
end
context 'and request comes from gitlab-workhorse' do
context 'without user being authorized' do
it_behaves_like 'LFS http 401 response'
end
context 'when LFS uses object storage' do
let(:authorization) { authorize_user }
context 'with required headers' do
shared_examples 'responds with a file' do
let(:sendfile) { 'X-Sendfile' }
let(:update_permissions) do
project.add_maintainer(user)
end
it_behaves_like 'LFS http 200 response'
context 'when proxy download is enabled' do
let(:before_get) do
stub_lfs_object_storage(proxy_download: true)
lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
end
it 'responds with the file location' do
expect(response.headers['Content-Type']).to eq('application/octet-stream')
expect(response.headers['X-Sendfile']).to eq(lfs_object.file.path)
end
it 'responds with the workhorse send-url' do
expect(response).to have_gitlab_http_status(:ok)
expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("send-url:")
end
end
context 'with user is authorized' do
let(:authorization) { authorize_user }
context 'when proxy download is disabled' do
let(:before_get) do
stub_lfs_object_storage(proxy_download: false)
lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
end
context 'and does not have project access' do
let(:update_permissions) do
project.lfs_objects << lfs_object
end
it 'responds with redirect' do
expect(response).to have_gitlab_http_status(:found)
end
it_behaves_like 'LFS http 404 response'
end
it 'responds with the file location' do
expect(response.location).to include(lfs_object.reload.file.path)
end
end
end
context 'and does have project access' do
let(:update_permissions) do
project.add_maintainer(user)
project.lfs_objects << lfs_object
end
context 'when deploy key is authorized' do
let(:key) { create(:deploy_key) }
let(:authorization) { authorize_deploy_key }
it_behaves_like 'responds with a file'
let(:update_permissions) do
project.deploy_keys << key
end
context 'when LFS uses object storage' do
context 'when proxy download is enabled' do
let(:before_get) do
stub_lfs_object_storage(proxy_download: true)
lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
end
it_behaves_like 'LFS http 200 blob response'
end
it_behaves_like 'LFS http 200 response'
context 'when using a user key (LFSToken)' do
let(:authorization) { authorize_user_key }
it 'responds with the workhorse send-url' do
expect(response.headers[Gitlab::Workhorse::SEND_DATA_HEADER]).to start_with("send-url:")
end
end
context 'when user allowed' do
let(:update_permissions) do
project.add_maintainer(user)
end
context 'when proxy download is disabled' do
let(:before_get) do
stub_lfs_object_storage(proxy_download: false)
lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE)
end
it_behaves_like 'LFS http 200 blob response'
it 'responds with redirect' do
expect(response).to have_gitlab_http_status(:found)
end
context 'when user password is expired' do
let(:user) { create(:user, password_expires_at: 1.minute.ago)}
it 'responds with the file location' do
expect(response.location).to include(lfs_object.reload.file.path)
end
end
end
end
it_behaves_like 'LFS http 401 response'
end
context 'when deploy key is authorized' do
let(:key) { create(:deploy_key) }
let(:authorization) { authorize_deploy_key }
let(:update_permissions) do
project.deploy_keys << key
project.lfs_objects << lfs_object
end
context 'when user is blocked' do
let(:user) { create(:user, :blocked)}
it_behaves_like 'responds with a file'
it_behaves_like 'LFS http 401 response'
end
end
describe 'when using a user key (LFSToken)' do
let(:authorization) { authorize_user_key }
context 'when user allowed' do
let(:update_permissions) do
project.add_maintainer(user)
project.lfs_objects << lfs_object
end
context 'when user not allowed' do
it_behaves_like 'LFS http 404 response'
end
end
it_behaves_like 'responds with a file'
context 'when build is authorized as' do
let(:authorization) { authorize_ci_project }
context 'when user password is expired' do
let(:user) { create(:user, password_expires_at: 1.minute.ago)}
shared_examples 'can download LFS only from own projects' do
context 'for owned project' do
let(:project) { create(:project, namespace: user.namespace) }
it_behaves_like 'LFS http 401 response'
end
it_behaves_like 'LFS http 200 blob response'
end
context 'when user is blocked' do
let(:user) { create(:user, :blocked)}
context 'for member of project' do
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
it_behaves_like 'LFS http 401 response'
end
let(:update_permissions) do
project.add_reporter(user)
end
context 'when user not allowed' do
let(:update_permissions) do
project.lfs_objects << lfs_object
end
it_behaves_like 'LFS http 404 response'
end
it_behaves_like 'LFS http 200 blob response'
end
context 'when build is authorized as' do
let(:authorization) { authorize_ci_project }
shared_examples 'can download LFS only from own projects' do
context 'for owned project' do
let(:project) { create(:project, namespace: user.namespace) }
let(:update_permissions) do
project.lfs_objects << lfs_object
end
it_behaves_like 'responds with a file'
end
context 'for member of project' do
let(:pipeline) { create(:ci_empty_pipeline, project: project) }
let(:update_permissions) do
project.add_reporter(user)
project.lfs_objects << lfs_object
end
it_behaves_like 'responds with a file'
end
context 'for other project' do
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
context 'for other project' do
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
let(:update_permissions) do
project.lfs_objects << lfs_object
end
it 'rejects downloading code' do
expect(response).to have_gitlab_http_status(other_project_status)
end
end
end
context 'administrator' do
let(:user) { create(:admin) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it_behaves_like 'can download LFS only from own projects' do
# We render 403, because administrator does have normally access
let(:other_project_status) { 403 }
end
it 'rejects downloading code' do
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
context 'regular user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
context 'administrator' do
let(:user) { create(:admin) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it_behaves_like 'can download LFS only from own projects' do
# We render 404, to prevent data leakage about existence of the project
let(:other_project_status) { 404 }
end
end
it_behaves_like 'can download LFS only from own projects'
end
context 'does not have user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline) }
context 'regular user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it_behaves_like 'can download LFS only from own projects' do
# We render 404, to prevent data leakage about existence of the project
let(:other_project_status) { 404 }
end
end
end
it_behaves_like 'can download LFS only from own projects'
end
context 'without required headers' do
let(:authorization) { authorize_user }
context 'does not have user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline) }
it_behaves_like 'LFS http 404 response'
it_behaves_like 'can download LFS only from own projects'
end
end
end
......@@ -511,7 +366,7 @@ RSpec.describe 'Git LFS API and storage' do
let(:role) { :reporter }
end
context 'when user does is not member of the project' do
context 'when user is not a member of the project' do
let(:update_user_permissions) { nil }
it_behaves_like 'LFS http 404 response'
......@@ -520,7 +375,7 @@ RSpec.describe 'Git LFS API and storage' do
context 'when user does not have download access' do
let(:role) { :guest }
it_behaves_like 'LFS http 403 response'
it_behaves_like 'LFS http 404 response'
end
context 'when user password is expired' do
......@@ -591,7 +446,7 @@ RSpec.describe 'Git LFS API and storage' do
let(:pipeline) { create(:ci_empty_pipeline, project: other_project) }
it 'rejects downloading code' do
expect(response).to have_gitlab_http_status(other_project_status)
expect(response).to have_gitlab_http_status(:not_found)
end
end
end
......@@ -600,28 +455,19 @@ RSpec.describe 'Git LFS API and storage' do
let(:user) { create(:admin) }
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it_behaves_like 'can download LFS only from own projects', renew_authorization: true do
# We render 403, because administrator does have normally access
let(:other_project_status) { 403 }
end
it_behaves_like 'can download LFS only from own projects', renew_authorization: true
end
context 'regular user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) }
it_behaves_like 'can download LFS only from own projects', renew_authorization: true do
# We render 404, to prevent data leakage about existence of the project
let(:other_project_status) { 404 }
end
it_behaves_like 'can download LFS only from own projects', renew_authorization: true
end
context 'does not have user' do
let(:build) { create(:ci_build, :running, pipeline: pipeline) }
it_behaves_like 'can download LFS only from own projects', renew_authorization: false do
# We render 404, to prevent data leakage about existence of the project
let(:other_project_status) { 404 }
end
it_behaves_like 'can download LFS only from own projects', renew_authorization: false
end
end
......@@ -919,11 +765,7 @@ RSpec.describe 'Git LFS API and storage' do
put_authorize
end
it_behaves_like 'LFS http 200 response'
it 'uses the gitlab-workhorse content type' do
expect(response.media_type).to eq(Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE)
end
it_behaves_like 'LFS http 200 workhorse response'
end
shared_examples 'a local file' do
......@@ -1142,7 +984,7 @@ RSpec.describe 'Git LFS API and storage' do
put_authorize
end
it_behaves_like 'LFS http 404 response'
it_behaves_like 'LFS http 403 response'
end
end
......@@ -1155,7 +997,7 @@ RSpec.describe 'Git LFS API and storage' do
put_authorize
end
it_behaves_like 'LFS http 200 response'
it_behaves_like 'LFS http 200 workhorse response'
context 'when user password is expired' do
let(:user) { create(:user, password_expires_at: 1.minute.ago)}
......@@ -1202,7 +1044,7 @@ RSpec.describe 'Git LFS API and storage' do
put_authorize
end
it_behaves_like 'LFS http 200 response'
it_behaves_like 'LFS http 200 workhorse response'
it 'with location of LFS store and object details' do
expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path)
......@@ -1330,4 +1172,50 @@ RSpec.describe 'Git LFS API and storage' do
"#{sample_oid}012345678"
end
end
context 'with projects' do
it_behaves_like 'LFS http requests' do
let(:container) { project }
let(:authorize_guest) { project.add_guest(user) }
let(:authorize_download) { project.add_reporter(user) }
let(:authorize_upload) { project.add_developer(user) }
end
end
context 'with project wikis' do
it_behaves_like 'LFS http requests' do
let(:container) { create(:project_wiki, :empty_repo, project: project) }
let(:authorize_guest) { project.add_guest(user) }
let(:authorize_download) { project.add_reporter(user) }
let(:authorize_upload) { project.add_developer(user) }
end
end
context 'with snippets' do
# LFS is not supported on snippets, so we override the shared examples
# to expect 404 responses instead.
[
'LFS http 200 response',
'LFS http 200 blob response',
'LFS http 403 response'
].each do |examples|
shared_examples_for(examples) { it_behaves_like 'LFS http 404 response' }
end
context 'with project snippets' do
it_behaves_like 'LFS http requests' do
let(:container) { create(:project_snippet, :empty_repo, project: project) }
let(:authorize_guest) { project.add_guest(user) }
let(:authorize_download) { project.add_reporter(user) }
let(:authorize_upload) { project.add_developer(user) }
end
end
context 'with personal snippets' do
it_behaves_like 'LFS http requests' do
let(:container) { create(:personal_snippet, :empty_repo) }
let(:authorize_upload) { container.update!(author: user) }
end
end
end
end
......@@ -3,24 +3,38 @@
require 'spec_helper'
RSpec.describe 'Git LFS File Locking API' do
include LfsHttpHelpers
include WorkhorseHelpers
let(:project) { create(:project) }
let(:maintainer) { create(:user) }
let(:developer) { create(:user) }
let(:guest) { create(:user) }
let(:path) { 'README.md' }
let_it_be(:project) { create(:project) }
let_it_be(:maintainer) { create(:user) }
let_it_be(:developer) { create(:user) }
let_it_be(:reporter) { create(:user) }
let_it_be(:guest) { create(:user) }
let_it_be(:path) { 'README.md' }
let(:user) { developer }
let(:headers) do
{
'Authorization' => authorization
'Authorization' => authorize_user
}.compact
end
shared_examples 'unauthorized request' do
context 'when user is not authorized' do
let(:authorization) { authorize_user(guest) }
context 'when user does not have download permission' do
let(:user) { guest }
it 'returns a forbidden 403 response' do
it 'returns a 404 response' do
post_lfs_json url, body, headers
expect(response).to have_gitlab_http_status(:not_found)
end
end
context 'when user does not have upload permission' do
let(:user) { reporter }
it 'returns a 403 response' do
post_lfs_json url, body, headers
expect(response).to have_gitlab_http_status(:forbidden)
......@@ -31,15 +45,15 @@ RSpec.describe 'Git LFS File Locking API' do
before do
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
project.add_developer(maintainer)
project.add_maintainer(maintainer)
project.add_developer(developer)
project.add_reporter(reporter)
project.add_guest(guest)
end
describe 'Create File Lock endpoint' do
let(:url) { "#{project.http_url_to_repo}/info/lfs/locks" }
let(:authorization) { authorize_user(developer) }
let(:body) { { path: path } }
let(:url) { "#{project.http_url_to_repo}/info/lfs/locks" }
let(:body) { { path: path } }
include_examples 'unauthorized request'
......@@ -76,8 +90,7 @@ RSpec.describe 'Git LFS File Locking API' do
end
describe 'Listing File Locks endpoint' do
let(:url) { "#{project.http_url_to_repo}/info/lfs/locks" }
let(:authorization) { authorize_user(developer) }
let(:url) { "#{project.http_url_to_repo}/info/lfs/locks" }
include_examples 'unauthorized request'
......@@ -95,8 +108,7 @@ RSpec.describe 'Git LFS File Locking API' do
end
describe 'List File Locks for verification endpoint' do
let(:url) { "#{project.http_url_to_repo}/info/lfs/locks/verify" }
let(:authorization) { authorize_user(developer) }
let(:url) { "#{project.http_url_to_repo}/info/lfs/locks/verify" }
include_examples 'unauthorized request'
......@@ -116,9 +128,8 @@ RSpec.describe 'Git LFS File Locking API' do
end
describe 'Delete File Lock endpoint' do
let!(:lock) { lock_file('README.md', developer) }
let(:url) { "#{project.http_url_to_repo}/info/lfs/locks/#{lock[:id]}/unlock" }
let(:authorization) { authorize_user(developer) }
let!(:lock) { lock_file('README.md', developer) }
let(:url) { "#{project.http_url_to_repo}/info/lfs/locks/#{lock[:id]}/unlock" }
include_examples 'unauthorized request'
......@@ -136,7 +147,7 @@ RSpec.describe 'Git LFS File Locking API' do
end
context 'when a maintainer uses force' do
let(:authorization) { authorize_user(maintainer) }
let(:user) { maintainer }
it 'deletes the lock' do
project.add_maintainer(maintainer)
......@@ -154,14 +165,6 @@ RSpec.describe 'Git LFS File Locking API' do
result[:lock]
end
def authorize_user(user)
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
end
def post_lfs_json(url, body = nil, headers = nil)
post(url, params: body.try(:to_json), headers: (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
end
def do_get(url, params = nil, headers = nil)
get(url, params: (params || {}), headers: (headers || {}).merge('Content-Type' => LfsRequest::CONTENT_TYPE))
end
......
......@@ -31,16 +31,16 @@ module LfsHttpHelpers
post(url, params: params, headers: headers)
end
def batch_url(project)
"#{project.http_url_to_repo}/info/lfs/objects/batch"
def batch_url(container)
"#{container.http_url_to_repo}/info/lfs/objects/batch"
end
def objects_url(project, oid = nil, size = nil)
File.join(["#{project.http_url_to_repo}/gitlab-lfs/objects", oid, size].compact.map(&:to_s))
def objects_url(container, oid = nil, size = nil)
File.join(["#{container.http_url_to_repo}/gitlab-lfs/objects", oid, size].compact.map(&:to_s))
end
def authorize_url(project, oid, size)
File.join(objects_url(project, oid, size), 'authorize')
def authorize_url(container, oid, size)
File.join(objects_url(container, oid, size), 'authorize')
end
def download_body(objects)
......
......@@ -2,42 +2,252 @@
RSpec.shared_examples 'LFS http 200 response' do
it_behaves_like 'LFS http expected response code and message' do
let(:response_code) { 200 }
let(:response_code) { :ok }
end
end
RSpec.shared_examples 'LFS http 200 blob response' do
it_behaves_like 'LFS http expected response code and message' do
let(:response_code) { :ok }
let(:content_type) { Repositories::LfsApiController::LFS_TRANSFER_CONTENT_TYPE }
let(:response_headers) { { 'X-Sendfile' => lfs_object.file.path } }
end
end
RSpec.shared_examples 'LFS http 200 workhorse response' do
it_behaves_like 'LFS http expected response code and message' do
let(:response_code) { :ok }
let(:content_type) { Gitlab::Workhorse::INTERNAL_API_CONTENT_TYPE }
end
end
RSpec.shared_examples 'LFS http 401 response' do
it_behaves_like 'LFS http expected response code and message' do
let(:response_code) { 401 }
let(:response_code) { :unauthorized }
let(:content_type) { 'text/plain' }
end
end
RSpec.shared_examples 'LFS http 403 response' do
it_behaves_like 'LFS http expected response code and message' do
let(:response_code) { 403 }
let(:response_code) { :forbidden }
let(:message) { 'Access forbidden. Check your access level.' }
end
end
RSpec.shared_examples 'LFS http 501 response' do
it_behaves_like 'LFS http expected response code and message' do
let(:response_code) { 501 }
let(:response_code) { :not_implemented }
let(:message) { 'Git LFS is not enabled on this GitLab server, contact your admin.' }
end
end
RSpec.shared_examples 'LFS http 404 response' do
it_behaves_like 'LFS http expected response code and message' do
let(:response_code) { 404 }
let(:response_code) { :not_found }
end
end
RSpec.shared_examples 'LFS http expected response code and message' do
let(:response_code) { }
let(:message) { }
let(:response_headers) { {} }
let(:content_type) { LfsRequest::CONTENT_TYPE }
let(:message) {}
it 'responds with the expected response code and message' do
specify do
expect(response).to have_gitlab_http_status(response_code)
expect(response.headers.to_hash).to include(response_headers)
expect(response.media_type).to match(content_type)
expect(json_response['message']).to eq(message) if message
end
end
RSpec.shared_examples 'LFS http requests' do
include LfsHttpHelpers
let(:authorize_guest) {}
let(:authorize_download) {}
let(:authorize_upload) {}
let(:lfs_object) { create(:lfs_object, :with_file) }
let(:sample_oid) { lfs_object.oid }
let(:authorization) { authorize_user }
let(:headers) do
{
'Authorization' => authorization,
'X-Sendfile-Type' => 'X-Sendfile'
}
end
let(:request_download) do
get objects_url(container, sample_oid), params: {}, headers: headers
end
let(:request_upload) do
post_lfs_json batch_url(container), upload_body(multiple_objects), headers
end
before do
stub_lfs_setting(enabled: true)
end
context 'when LFS is disabled globally' do
before do
stub_lfs_setting(enabled: false)
end
describe 'download request' do
before do
request_download
end
it_behaves_like 'LFS http 501 response'
end
describe 'upload request' do
before do
request_upload
end
it_behaves_like 'LFS http 501 response'
end
end
context 'unauthenticated' do
let(:headers) { {} }
describe 'download request' do
before do
request_download
end
it_behaves_like 'LFS http 401 response'
end
describe 'upload request' do
before do
request_upload
end
it_behaves_like 'LFS http 401 response'
end
end
context 'without access' do
describe 'download request' do
before do
request_download
end
it_behaves_like 'LFS http 404 response'
end
describe 'upload request' do
before do
request_upload
end
it_behaves_like 'LFS http 404 response'
end
end
context 'with guest access' do
before do
authorize_guest
end
describe 'download request' do
before do
request_download
end
it_behaves_like 'LFS http 404 response'
end
describe 'upload request' do
before do
request_upload
end
it_behaves_like 'LFS http 404 response'
end
end
context 'with download permission' do
before do
authorize_download
end
describe 'download request' do
before do
request_download
end
it_behaves_like 'LFS http 200 blob response'
context 'when container does not exist' do
def objects_url(*args)
super.sub(container.full_path, 'missing/path')
end
it_behaves_like 'LFS http 404 response'
end
end
describe 'upload request' do
before do
request_upload
end
it_behaves_like 'LFS http 403 response'
end
end
context 'with upload permission' do
before do
authorize_upload
end
describe 'upload request' do
before do
request_upload
end
it_behaves_like 'LFS http 200 response'
end
end
describe 'deprecated API' do
shared_examples 'deprecated request' do
before do
request
end
it_behaves_like 'LFS http expected response code and message' do
let(:response_code) { 501 }
let(:message) { 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.' }
end
end
context 'when fetching LFS object using deprecated API' do
subject(:request) do
get deprecated_objects_url(container, sample_oid), params: {}, headers: headers
end
it_behaves_like 'deprecated request'
end
context 'when handling LFS request using deprecated API' do
subject(:request) do
post_lfs_json deprecated_objects_url(container), nil, headers
end
it_behaves_like 'deprecated request'
end
def deprecated_objects_url(container, oid = nil)
File.join(["#{container.http_url_to_repo}/info/lfs/objects/", oid].compact)
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