Commit 31a5a03c authored by Rémy Coutable's avatar Rémy Coutable

Merge branch 'fix-lfs-ci-fetch' into 'master'

Fix LFS CI fetch

## What does this MR do?
Fixes LFS fetching for CI objects that were kind of broken.

## Why was this MR needed?
This also refactors LFS tests to be a blackbox tests instead of whitebox tests. Actually testing GrackAuth instead of LFSRouter.

## Related isssues
Resolves: https://gitlab.com/gitlab-org/gitlab-ci-multi-runner/issues/1199

- [ ] [CHANGELOG](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CHANGELOG) entry added
- [ ] [Documentation created/updated](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/doc/development/doc_styleguide.md)
- [ ] API support added
- Tests
  - [ ] Added for this feature/bug
  - [ ] All builds are passing
- [ ] Conform by the [style guides](https://gitlab.com/gitlab-org/gitlab-ce/blob/master/CONTRIBUTING.md#style-guides)
- [ ] Branch has no merge conflicts with `master` (if you do - rebase it please)
- [ ] [Squashed related commits together](https://git-scm.com/book/en/Git-Tools-Rewriting-History#Squashing-Commits)


See merge request !5270
parents a95a32f0 e300dac2
...@@ -31,6 +31,7 @@ v 8.10.0 (unreleased) ...@@ -31,6 +31,7 @@ v 8.10.0 (unreleased)
- Support U2F devices in Firefox. !5177 - Support U2F devices in Firefox. !5177
- Fix issue, preventing users w/o push access to sort tags !5105 (redetection) - Fix issue, preventing users w/o push access to sort tags !5105 (redetection)
- Add Spring EmojiOne updates. - Add Spring EmojiOne updates.
- Fix fetching LFS objects for private CI projects
- Add syntax for multiline blockquote using `>>>` fence !3954 - Add syntax for multiline blockquote using `>>>` fence !3954
- Fix viewing notification settings when a project is pending deletion - Fix viewing notification settings when a project is pending deletion
- Updated compare dropdown menus to use GL dropdown - Updated compare dropdown menus to use GL dropdown
......
...@@ -63,7 +63,7 @@ module Grack ...@@ -63,7 +63,7 @@ module Grack
def ci_request?(login, password) def ci_request?(login, password)
matched_login = /(?<s>^[a-zA-Z]*-ci)-token$/.match(login) matched_login = /(?<s>^[a-zA-Z]*-ci)-token$/.match(login)
if project && matched_login.present? && git_cmd == 'git-upload-pack' if project && matched_login.present?
underscored_service = matched_login['s'].underscore underscored_service = matched_login['s'].underscore
if underscored_service == 'gitlab_ci' if underscored_service == 'gitlab_ci'
......
...@@ -47,6 +47,8 @@ module Gitlab ...@@ -47,6 +47,8 @@ module Gitlab
end end
def render_storage_upload_store_response(oid, size, tmp_file_name) def render_storage_upload_store_response(oid, size, tmp_file_name)
return render_forbidden unless tmp_file_name
render_response_to_push do render_response_to_push do
render_lfs_upload_ok(oid, size, tmp_file_name) render_lfs_upload_ok(oid, size, tmp_file_name)
end end
......
...@@ -74,8 +74,6 @@ module Gitlab ...@@ -74,8 +74,6 @@ module Gitlab
lfs.render_storage_upload_authorize_response(oid, size) lfs.render_storage_upload_authorize_response(oid, size)
else else
tmp_file_name = sanitize_tmp_filename(@request.env['HTTP_X_GITLAB_LFS_TMP']) tmp_file_name = sanitize_tmp_filename(@request.env['HTTP_X_GITLAB_LFS_TMP'])
return nil unless tmp_file_name
lfs.render_storage_upload_store_response(oid, size, tmp_file_name) lfs.render_storage_upload_store_response(oid, size, tmp_file_name)
end end
end end
......
require 'spec_helper' require 'spec_helper'
describe Gitlab::Lfs::Router, lib: true do describe Gitlab::Lfs::Router do
let(:project) { create(:project) }
let(:public_project) { create(:project, :public) }
let(:forked_project) { fork_project(public_project, user) }
let(:user) { create(:user) } let(:user) { create(:user) }
let(:user_two) { create(:user) }
let!(:lfs_object) { create(:lfs_object, :with_file) } let!(:lfs_object) { create(:lfs_object, :with_file) }
let(:request) { Rack::Request.new(env) } let(:headers) do
let(:env) do
{ {
'rack.input' => '', 'Authorization' => authorization,
'REQUEST_METHOD' => 'GET', 'X-Sendfile-Type' => sendfile
} }.compact
end end
let(:authorization) { }
let(:sendfile) { }
let(:lfs_router_auth) { new_lfs_router(project, user: user) } let(:sample_oid) { lfs_object.oid }
let(:lfs_router_ci_auth) { new_lfs_router(project, ci: true) } let(:sample_size) { lfs_object.size }
let(:lfs_router_noauth) { new_lfs_router(project) }
let(:lfs_router_public_auth) { new_lfs_router(public_project, user: user) }
let(:lfs_router_public_ci_auth) { new_lfs_router(public_project, ci: true) }
let(:lfs_router_public_noauth) { new_lfs_router(public_project) }
let(:lfs_router_forked_noauth) { new_lfs_router(forked_project) }
let(:lfs_router_forked_auth) { new_lfs_router(forked_project, user: user_two) }
let(:lfs_router_forked_ci_auth) { new_lfs_router(forked_project, ci: true) }
let(:sample_oid) { "b68143e6463773b1b6c6fd009a76c32aeec041faff32ba2ed42fd7f708a17f80" }
let(:sample_size) { 499013 }
let(:respond_with_deprecated) {[ 501, { "Content-Type" => "application/json; charset=utf-8" }, ["{\"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\"}"]]}
let(:respond_with_disabled) {[ 501, { "Content-Type" => "application/json; charset=utf-8" }, ["{\"message\":\"Git LFS is not enabled on this GitLab server, contact your admin.\",\"documentation_url\":\"#{Gitlab.config.gitlab.url}/help\"}"]]}
describe 'when lfs is disabled' do describe 'when lfs is disabled' do
before do let(:project) { create(:empty_project) }
allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) let(:body) do
env['REQUEST_METHOD'] = 'POST' {
body = {
'objects' => [ 'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078 'size' => 1575078
...@@ -46,136 +29,171 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -46,136 +29,171 @@ describe Gitlab::Lfs::Router, lib: true do
} }
], ],
'operation' => 'upload' 'operation' => 'upload'
}.to_json }
env['rack.input'] = StringIO.new(body) end
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch"
before do
allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
post_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
end end
it 'responds with 501' do it 'responds with 501' do
expect(lfs_router_auth.try_call).to match_array(respond_with_disabled) expect(response).to have_http_status(501)
expect(json_response).to include('message' => 'Git LFS is not enabled on this GitLab server, contact your admin.')
end end
end end
describe 'when fetching lfs object using deprecated API' do describe 'deprecated API' do
let(:project) { create(:empty_project) }
before do before do
enable_lfs enable_lfs
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/#{sample_oid}"
end end
shared_examples 'a deprecated' do
it 'responds with 501' do it 'responds with 501' do
expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated) expect(response).to have_http_status(501)
end
it 'returns deprecated message' do
expect(json_response).to include('message' => 'Server supports batch API only, please update your Git LFS client to version 1.0.1 and up.')
end end
end end
describe 'when fetching lfs object' do context 'when fetching lfs object using deprecated API' do
let(:authorization) { authorize_user }
before do before do
enable_lfs get "#{project.http_url_to_repo}/info/lfs/objects/#{sample_oid}", nil, headers
env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8"
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}"
end end
describe 'and request comes from gitlab-workhorse' do it_behaves_like 'a deprecated'
context 'without user being authorized' do
it "responds with status 401" do
expect(lfs_router_noauth.try_call.first).to eq(401)
end
end end
context 'with required headers' do context 'when handling lfs request using deprecated API' do
before do before do
project.lfs_objects << lfs_object post_json "#{project.http_url_to_repo}/info/lfs/objects", nil, headers
env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile"
end end
context 'when user does not have project access' do it_behaves_like 'a deprecated'
it "responds with status 403" do
expect(lfs_router_auth.try_call.first).to eq(403)
end end
end end
context 'when user has project access' do describe 'when fetching lfs object' do
let(:project) { create(:empty_project) }
let(:update_permissions) { }
before do before do
project.team << [user, :master] enable_lfs
update_permissions
get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
end end
it "responds with status 200" do context 'and request comes from gitlab-workhorse' do
expect(lfs_router_auth.try_call.first).to eq(200) context 'without user being authorized' do
it 'responds with status 401' do
expect(response).to have_http_status(401)
end end
it "responds with the file location" do
expect(lfs_router_auth.try_call[1]['Content-Type']).to eq("application/octet-stream")
expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path)
end end
context 'with required headers' do
shared_examples 'responds with a file' do
let(:sendfile) { 'X-Sendfile' }
it 'responds with status 200' do
expect(response).to have_http_status(200)
end end
context 'when CI is authorized' do it 'responds with the file location' do
it "responds with status 200" do expect(response.headers['Content-Type']).to eq('application/octet-stream')
expect(lfs_router_ci_auth.try_call.first).to eq(200) expect(response.headers['X-Sendfile']).to eq(lfs_object.file.path)
end
end end
it "responds with the file location" do context 'with user is authorized' do
expect(lfs_router_ci_auth.try_call[1]['Content-Type']).to eq("application/octet-stream") let(:authorization) { authorize_user }
expect(lfs_router_ci_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path)
context 'and does not have project access' do
let(:update_permissions) do
project.lfs_objects << lfs_object
end end
it 'responds with status 403' do
expect(response).to have_http_status(403)
end end
end end
context 'without required headers' do context 'and does have project access' do
it "responds with status 403" do let(:update_permissions) do
expect(lfs_router_auth.try_call.first).to eq(403) project.team << [user, :master]
project.lfs_objects << lfs_object
end end
it_behaves_like 'responds with a file'
end end
end end
context 'when CI is authorized' do
let(:authorization) { authorize_ci_project }
let(:update_permissions) do
project.lfs_objects << lfs_object
end end
describe 'when handling lfs request using deprecated API' do it_behaves_like 'responds with a file'
before do end
enable_lfs
env['REQUEST_METHOD'] = 'POST'
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects"
end end
it 'responds with 501' do context 'without required headers' do
expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated) let(:authorization) { authorize_user }
it 'responds with status 403' do
expect(response).to have_http_status(403)
end
end
end end
end end
describe 'when handling lfs batch request' do describe 'when handling lfs batch request' do
let(:update_lfs_permissions) { }
let(:update_user_permissions) { }
before do before do
enable_lfs enable_lfs
env['REQUEST_METHOD'] = 'POST' update_lfs_permissions
env['PATH_INFO'] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch" update_user_permissions
post_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
end end
describe 'download' do describe 'download' do
before do let(:project) { create(:empty_project) }
body = { 'operation' => 'download', let(:body) do
{ 'operation' => 'download',
'objects' => [ 'objects' => [
{ 'oid' => sample_oid, { 'oid' => sample_oid,
'size' => sample_size 'size' => sample_size
}] }]
}.to_json }
env['rack.input'] = StringIO.new(body)
end end
shared_examples 'an authorized requests' do shared_examples 'an authorized requests' do
context 'when downloading an lfs object that is assigned to our project' do context 'when downloading an lfs object that is assigned to our project' do
before do let(:update_lfs_permissions) do
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
it 'responds with status 200 and href to download' do it 'responds with status 200' do
response = router.try_call expect(response).to have_http_status(200)
expect(response.first).to eq(200) end
response_body = ActiveSupport::JSON.decode(response.last.first)
expect(response_body).to eq('objects' => [ it 'with href to download' do
expect(json_response).to eq('objects' => [
{ 'oid' => sample_oid, { 'oid' => sample_oid,
'size' => sample_size, 'size' => sample_size,
'actions' => { 'actions' => {
'download' => { 'download' => {
'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
'header' => { 'Authorization' => auth } 'header' => { 'Authorization' => authorization }
} }
} }
}]) }])
...@@ -183,16 +201,17 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -183,16 +201,17 @@ describe Gitlab::Lfs::Router, lib: true do
end end
context 'when downloading an lfs object that is assigned to other project' do context 'when downloading an lfs object that is assigned to other project' do
before do let(:other_project) { create(:empty_project) }
public_project.lfs_objects << lfs_object let(:update_lfs_permissions) do
other_project.lfs_objects << lfs_object
end end
it 'responds with status 200 and error message' do it 'responds with status 200' do
response = router.try_call expect(response).to have_http_status(200)
expect(response.first).to eq(200) end
response_body = ActiveSupport::JSON.decode(response.last.first)
expect(response_body).to eq('objects' => [ it 'with href to download' do
expect(json_response).to eq('objects' => [
{ 'oid' => sample_oid, { 'oid' => sample_oid,
'size' => sample_size, 'size' => sample_size,
'error' => { 'error' => {
...@@ -204,22 +223,21 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -204,22 +223,21 @@ describe Gitlab::Lfs::Router, lib: true do
end end
context 'when downloading a lfs object that does not exist' do context 'when downloading a lfs object that does not exist' do
before do let(:body) do
body = { 'operation' => 'download', { 'operation' => 'download',
'objects' => [ 'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078 'size' => 1575078
}] }]
}.to_json }
env['rack.input'] = StringIO.new(body)
end end
it "responds with status 200 and error message" do it 'responds with status 200' do
response = router.try_call expect(response).to have_http_status(200)
expect(response.first).to eq(200) end
response_body = ActiveSupport::JSON.decode(response.last.first)
expect(response_body).to eq('objects' => [ it 'with an 404 for specific object' do
expect(json_response).to eq('objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078, 'size' => 1575078,
'error' => { 'error' => {
...@@ -231,8 +249,8 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -231,8 +249,8 @@ describe Gitlab::Lfs::Router, lib: true do
end end
context 'when downloading one new and one existing lfs object' do context 'when downloading one new and one existing lfs object' do
before do let(:body) do
body = { 'operation' => 'download', { 'operation' => 'download',
'objects' => [ 'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078 'size' => 1575078
...@@ -241,17 +259,19 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -241,17 +259,19 @@ describe Gitlab::Lfs::Router, lib: true do
'size' => sample_size 'size' => sample_size
} }
] ]
}.to_json }
env['rack.input'] = StringIO.new(body) end
let(:update_lfs_permissions) do
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
it "responds with status 200 with upload hypermedia link for the new object" do it 'responds with status 200' do
response = router.try_call expect(response).to have_http_status(200)
expect(response.first).to eq(200) end
response_body = ActiveSupport::JSON.decode(response.last.first)
expect(response_body).to eq('objects' => [ it 'responds with upload hypermedia link for the new object' do
expect(json_response).to eq('objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078, 'size' => 1575078,
'error' => { 'error' => {
...@@ -264,7 +284,7 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -264,7 +284,7 @@ describe Gitlab::Lfs::Router, lib: true do
'actions' => { 'actions' => {
'download' => { 'download' => {
'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
'header' => { 'Authorization' => auth } 'header' => { 'Authorization' => authorization }
} }
} }
}]) }])
...@@ -273,23 +293,21 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -273,23 +293,21 @@ describe Gitlab::Lfs::Router, lib: true do
end end
context 'when user is authenticated' do context 'when user is authenticated' do
let(:auth) { authorize(user) } let(:authorization) { authorize_user }
before do let(:update_user_permissions) do
env["HTTP_AUTHORIZATION"] = auth
project.team << [user, role] project.team << [user, role]
end end
it_behaves_like 'an authorized requests' do it_behaves_like 'an authorized requests' do
let(:role) { :reporter } let(:role) { :reporter }
let(:router) { lfs_router_auth }
end end
context 'when user does is not member of the project' do context 'when user does is not member of the project' do
let(:role) { :guest } let(:role) { :guest }
it 'responds with 403' do it 'responds with 403' do
expect(lfs_router_auth.try_call.first).to eq(403) expect(response).to have_http_status(403)
end end
end end
...@@ -297,40 +315,36 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -297,40 +315,36 @@ describe Gitlab::Lfs::Router, lib: true do
let(:role) { :guest } let(:role) { :guest }
it 'responds with 403' do it 'responds with 403' do
expect(lfs_router_auth.try_call.first).to eq(403) expect(response).to have_http_status(403)
end end
end end
end end
context 'when CI is authorized' do context 'when CI is authorized' do
let(:auth) { 'gitlab-ci-token:password' } let(:authorization) { authorize_ci_project }
before do it_behaves_like 'an authorized requests'
env["HTTP_AUTHORIZATION"] = auth
end
it_behaves_like 'an authorized requests' do
let(:router) { lfs_router_ci_auth }
end
end end
context 'when user is not authenticated' do context 'when user is not authenticated' do
describe 'is accessing public project' do describe 'is accessing public project' do
before do let(:project) { create(:project, :public) }
public_project.lfs_objects << lfs_object
let(:update_lfs_permissions) do
project.lfs_objects << lfs_object
end end
it 'responds with status 200 and href to download' do it 'responds with status 200 and href to download' do
response = lfs_router_public_noauth.try_call expect(response).to have_http_status(200)
expect(response.first).to eq(200) end
response_body = ActiveSupport::JSON.decode(response.last.first)
expect(response_body).to eq('objects' => [ it 'responds with status 200 and href to download' do
expect(json_response).to eq('objects' => [
{ 'oid' => sample_oid, { 'oid' => sample_oid,
'size' => sample_size, 'size' => sample_size,
'actions' => { 'actions' => {
'download' => { 'download' => {
'href' => "#{public_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", 'href' => "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}",
'header' => {} 'header' => {}
} }
} }
...@@ -339,83 +353,83 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -339,83 +353,83 @@ describe Gitlab::Lfs::Router, lib: true do
end end
describe 'is accessing non-public project' do describe 'is accessing non-public project' do
before do let(:update_lfs_permissions) do
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
it 'responds with authorization required' do it 'responds with authorization required' do
expect(lfs_router_noauth.try_call.first).to eq(401) expect(response).to have_http_status(401)
end end
end end
end end
end end
describe 'upload' do describe 'upload' do
before do let(:project) { create(:project, :public) }
body = { 'operation' => 'upload', let(:body) do
{ 'operation' => 'upload',
'objects' => [ 'objects' => [
{ 'oid' => sample_oid, { 'oid' => sample_oid,
'size' => sample_size 'size' => sample_size
}] }]
}.to_json }
env['rack.input'] = StringIO.new(body)
end end
describe 'when request is authenticated' do describe 'when request is authenticated' do
describe 'when user has project push access' do describe 'when user has project push access' do
before do let(:authorization) { authorize_user }
@auth = authorize(user)
env["HTTP_AUTHORIZATION"] = @auth let(:update_user_permissions) do
project.team << [user, :developer] project.team << [user, :developer]
end end
context 'when pushing an lfs object that already exists' do context 'when pushing an lfs object that already exists' do
before do let(:other_project) { create(:empty_project) }
public_project.lfs_objects << lfs_object let(:update_lfs_permissions) do
other_project.lfs_objects << lfs_object
end end
it "responds with status 200 and links the object to the project" do it 'responds with status 200' do
response_body = lfs_router_auth.try_call.last expect(response).to have_http_status(200)
response = ActiveSupport::JSON.decode(response_body.first) end
expect(response['objects']).to be_kind_of(Array) it 'responds with links the object to the project' do
expect(response['objects'].first['oid']).to eq(sample_oid) expect(json_response['objects']).to be_kind_of(Array)
expect(response['objects'].first['size']).to eq(sample_size) expect(json_response['objects'].first['oid']).to eq(sample_oid)
expect(json_response['objects'].first['size']).to eq(sample_size)
expect(lfs_object.projects.pluck(:id)).not_to include(project.id) expect(lfs_object.projects.pluck(:id)).not_to include(project.id)
expect(lfs_object.projects.pluck(:id)).to include(public_project.id) expect(lfs_object.projects.pluck(:id)).to include(other_project.id)
expect(response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}") expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}")
expect(response['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth) expect(json_response['objects'].first['actions']['upload']['header']).to eq('Authorization' => authorization)
end end
end end
context 'when pushing a lfs object that does not exist' do context 'when pushing a lfs object that does not exist' do
before do let(:body) do
body = { 'operation' => 'upload', { 'operation' => 'upload',
'objects' => [ 'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078 'size' => 1575078
}] }]
}.to_json }
env['rack.input'] = StringIO.new(body)
end end
it "responds with status 200 and upload hypermedia link" do it 'responds with status 200' do
response = lfs_router_auth.try_call expect(response).to have_http_status(200)
expect(response.first).to eq(200) end
response_body = ActiveSupport::JSON.decode(response.last.first) it 'responds with upload hypermedia link' do
expect(response_body['objects']).to be_kind_of(Array) expect(json_response['objects']).to be_kind_of(Array)
expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") expect(json_response['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
expect(response_body['objects'].first['size']).to eq(1575078) expect(json_response['objects'].first['size']).to eq(1575078)
expect(lfs_object.projects.pluck(:id)).not_to include(project.id) expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") expect(json_response['objects'].first['actions']['upload']['header']).to eq('Authorization' => authorization)
expect(response_body['objects'].first['actions']['upload']['header']).to eq('Authorization' => @auth)
end end
end end
context 'when pushing one new and one existing lfs object' do context 'when pushing one new and one existing lfs object' do
before do let(:body) do
body = { 'operation' => 'upload', { 'operation' => 'upload',
'objects' => [ 'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897', { 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078 'size' => 1575078
...@@ -424,87 +438,89 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -424,87 +438,89 @@ describe Gitlab::Lfs::Router, lib: true do
'size' => sample_size 'size' => sample_size
} }
] ]
}.to_json }
env['rack.input'] = StringIO.new(body) end
let(:update_lfs_permissions) do
project.lfs_objects << lfs_object project.lfs_objects << lfs_object
end end
it "responds with status 200 with upload hypermedia link for the new object" do it 'responds with status 200' do
response = lfs_router_auth.try_call expect(response).to have_http_status(200)
expect(response.first).to eq(200) end
response_body = ActiveSupport::JSON.decode(response.last.first) it 'responds with upload hypermedia link for the new object' do
expect(response_body['objects']).to be_kind_of(Array) expect(json_response['objects']).to be_kind_of(Array)
expect(response_body['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897") expect(json_response['objects'].first['oid']).to eq("91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897")
expect(response_body['objects'].first['size']).to eq(1575078) expect(json_response['objects'].first['size']).to eq(1575078)
expect(response_body['objects'].first['actions']['upload']['href']).to eq("#{Gitlab.config.gitlab.url}/#{project.path_with_namespace}.git/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078") expect(json_response['objects'].first['actions']['upload']['href']).to eq("#{project.http_url_to_repo}/gitlab-lfs/objects/91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897/1575078")
expect(response_body['objects'].first['actions']['upload']['header']).to eq("Authorization" => @auth) expect(json_response['objects'].first['actions']['upload']['header']).to eq("Authorization" => authorization)
expect(response_body['objects'].last['oid']).to eq(sample_oid) expect(json_response['objects'].last['oid']).to eq(sample_oid)
expect(response_body['objects'].last['size']).to eq(sample_size) expect(json_response['objects'].last['size']).to eq(sample_size)
expect(response_body['objects'].last).not_to have_key('actions') expect(json_response['objects'].last).not_to have_key('actions')
end end
end end
end end
context 'when user does not have push access' do context 'when user does not have push access' do
let(:authorization) { authorize_user }
it 'responds with 403' do it 'responds with 403' do
expect(lfs_router_auth.try_call.first).to eq(403) expect(response).to have_http_status(403)
end end
end end
context 'when CI is authorized' do context 'when CI is authorized' do
let(:authorization) { authorize_ci_project }
it 'responds with 401' do it 'responds with 401' do
expect(lfs_router_ci_auth.try_call.first).to eq(401) expect(response).to have_http_status(401)
end end
end end
end end
context 'when user is not authenticated' do context 'when user is not authenticated' do
context 'when user has push access' do context 'when user has push access' do
before do let(:update_user_permissions) do
project.team << [user, :master] project.team << [user, :master]
end end
it "responds with status 401" do it 'responds with status 401' do
expect(lfs_router_public_noauth.try_call.first).to eq(401) expect(response).to have_http_status(401)
end end
end end
context 'when user does not have push access' do context 'when user does not have push access' do
it "responds with status 401" do it 'responds with status 401' do
expect(lfs_router_public_noauth.try_call.first).to eq(401) expect(response).to have_http_status(401)
end end
end end
end end
context 'when CI is authorized' do context 'when CI is authorized' do
let(:auth) { 'gitlab-ci-token:password' } let(:authorization) { authorize_ci_project }
before do it 'responds with status 403' do
env["HTTP_AUTHORIZATION"] = auth expect(response).to have_http_status(401)
end
it "responds with status 403" do
expect(lfs_router_public_ci_auth.try_call.first).to eq(401)
end end
end end
end end
describe 'unsupported' do describe 'unsupported' do
before do let(:project) { create(:empty_project) }
body = { 'operation' => 'other', let(:body) do
{ 'operation' => 'other',
'objects' => [ 'objects' => [
{ 'oid' => sample_oid, { 'oid' => sample_oid,
'size' => sample_size 'size' => sample_size
}] }]
}.to_json }
env['rack.input'] = StringIO.new(body)
end end
it 'responds with status 404' do it 'responds with status 404' do
expect(lfs_router_public_noauth.try_call.first).to eq(404) expect(response).to have_http_status(404)
end end
end end
end end
...@@ -512,38 +528,36 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -512,38 +528,36 @@ describe Gitlab::Lfs::Router, lib: true do
describe 'when pushing a lfs object' do describe 'when pushing a lfs object' do
before do before do
enable_lfs enable_lfs
env['REQUEST_METHOD'] = 'PUT'
end end
shared_examples 'unauthorized' do shared_examples 'unauthorized' do
context 'and request is sent by gitlab-workhorse to authorize the request' do context 'and request is sent by gitlab-workhorse to authorize the request' do
before do before do
header_for_upload_authorize(router.project) put_authorize
end end
it 'responds with status 401' do it 'responds with status 401' do
expect(router.try_call.first).to eq(401) expect(response).to have_http_status(401)
end end
end end
context 'and request is sent by gitlab-workhorse to finalize the upload' do context 'and request is sent by gitlab-workhorse to finalize the upload' do
before do before do
headers_for_upload_finalize(router.project) put_finalize
end end
it 'responds with status 401' do it 'responds with status 401' do
expect(router.try_call.first).to eq(401) expect(response).to have_http_status(401)
end end
end end
context 'and request is sent with a malformed headers' do context 'and request is sent with a malformed headers' do
before do before do
env["PATH_INFO"] = "#{router.project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" put_finalize('cat /etc/passwd')
env["HTTP_X_GITLAB_LFS_TMP"] = "cat /etc/passwd"
end end
it 'does not recognize it as a valid lfs command' do it 'does not recognize it as a valid lfs command' do
expect(router.try_call).to eq(nil) expect(response).to have_http_status(403)
end end
end end
end end
...@@ -551,27 +565,31 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -551,27 +565,31 @@ describe Gitlab::Lfs::Router, lib: true do
shared_examples 'forbidden' do shared_examples 'forbidden' do
context 'and request is sent by gitlab-workhorse to authorize the request' do context 'and request is sent by gitlab-workhorse to authorize the request' do
before do before do
header_for_upload_authorize(router.project) put_authorize
end end
it 'responds with 403' do it 'responds with 403' do
expect(router.try_call.first).to eq(403) expect(response).to have_http_status(403)
end end
end end
context 'and request is sent by gitlab-workhorse to finalize the upload' do context 'and request is sent by gitlab-workhorse to finalize the upload' do
before do before do
headers_for_upload_finalize(router.project) put_finalize
end end
it 'responds with 403' do it 'responds with 403' do
expect(router.try_call.first).to eq(403) expect(response).to have_http_status(403)
end end
end end
end end
describe 'to one project' do describe 'to one project' do
let(:project) { create(:empty_project) }
describe 'when user is authenticated' do describe 'when user is authenticated' do
let(:authorization) { authorize_user }
describe 'when user has push access to the project' do describe 'when user has push access to the project' do
before do before do
project.team << [user, :developer] project.team << [user, :developer]
...@@ -579,13 +597,14 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -579,13 +597,14 @@ describe Gitlab::Lfs::Router, lib: true do
context 'and request is sent by gitlab-workhorse to authorize the request' do context 'and request is sent by gitlab-workhorse to authorize the request' do
before do before do
header_for_upload_authorize(project) put_authorize
end end
it 'responds with status 200, location of lfs store and object details' do it 'responds with status 200' do
json_response = ActiveSupport::JSON.decode(lfs_router_auth.try_call.last.first) expect(response).to have_http_status(200)
end
expect(lfs_router_auth.try_call.first).to eq(200) it 'responds with status 200, location of lfs store and object details' do
expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload")
expect(json_response['LfsOid']).to eq(sample_oid) expect(json_response['LfsOid']).to eq(sample_oid)
expect(json_response['LfsSize']).to eq(sample_size) expect(json_response['LfsSize']).to eq(sample_size)
...@@ -594,54 +613,58 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -594,54 +613,58 @@ describe Gitlab::Lfs::Router, lib: true do
context 'and request is sent by gitlab-workhorse to finalize the upload' do context 'and request is sent by gitlab-workhorse to finalize the upload' do
before do before do
headers_for_upload_finalize(project) put_finalize
end
it 'responds with status 200' do
expect(response).to have_http_status(200)
end end
it 'responds with status 200 and lfs object is linked to the project' do it 'lfs object is linked to the project' do
expect(lfs_router_auth.try_call.first).to eq(200)
expect(lfs_object.projects.pluck(:id)).to include(project.id) expect(lfs_object.projects.pluck(:id)).to include(project.id)
end end
end end
end end
describe 'and user does not have push access' do describe 'and user does not have push access' do
let(:router) { lfs_router_auth }
it_behaves_like 'forbidden' it_behaves_like 'forbidden'
end end
end end
context 'when CI is authenticated' do context 'when CI is authenticated' do
let(:router) { lfs_router_ci_auth } let(:authorization) { authorize_ci_project }
it_behaves_like 'unauthorized' it_behaves_like 'unauthorized'
end end
context 'for unauthenticated' do context 'for unauthenticated' do
let(:router) { new_lfs_router(project) }
it_behaves_like 'unauthorized' it_behaves_like 'unauthorized'
end end
end end
describe 'to a forked project' do describe 'to a forked project' do
let(:forked_project) { fork_project(public_project, user) } let(:upstream_project) { create(:project, :public) }
let(:project_owner) { create(:user) }
let(:project) { fork_project(upstream_project, project_owner) }
describe 'when user is authenticated' do describe 'when user is authenticated' do
let(:authorization) { authorize_user }
describe 'when user has push access to the project' do describe 'when user has push access to the project' do
before do before do
forked_project.team << [user_two, :developer] project.team << [user, :developer]
end end
context 'and request is sent by gitlab-workhorse to authorize the request' do context 'and request is sent by gitlab-workhorse to authorize the request' do
before do before do
header_for_upload_authorize(forked_project) put_authorize
end end
it 'responds with status 200, location of lfs store and object details' do it 'responds with status 200' do
json_response = ActiveSupport::JSON.decode(lfs_router_forked_auth.try_call.last.first) expect(response).to have_http_status(200)
end
expect(lfs_router_forked_auth.try_call.first).to eq(200) it 'with location of lfs store and object details' do
expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload") expect(json_response['StoreLFSPath']).to eq("#{Gitlab.config.shared.path}/lfs-objects/tmp/upload")
expect(json_response['LfsOid']).to eq(sample_oid) expect(json_response['LfsOid']).to eq(sample_oid)
expect(json_response['LfsSize']).to eq(sample_size) expect(json_response['LfsSize']).to eq(sample_size)
...@@ -650,81 +673,96 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -650,81 +673,96 @@ describe Gitlab::Lfs::Router, lib: true do
context 'and request is sent by gitlab-workhorse to finalize the upload' do context 'and request is sent by gitlab-workhorse to finalize the upload' do
before do before do
headers_for_upload_finalize(forked_project) put_finalize
end
it 'responds with status 200' do
expect(response).to have_http_status(200)
end end
it 'responds with status 200 and lfs object is linked to the source project' do it 'lfs object is linked to the source project' do
expect(lfs_router_forked_auth.try_call.first).to eq(200) expect(lfs_object.projects.pluck(:id)).to include(upstream_project.id)
expect(lfs_object.projects.pluck(:id)).to include(public_project.id)
end end
end end
end end
describe 'and user does not have push access' do describe 'and user does not have push access' do
let(:router) { lfs_router_forked_auth }
it_behaves_like 'forbidden' it_behaves_like 'forbidden'
end end
end end
context 'when CI is authenticated' do context 'when CI is authenticated' do
let(:router) { lfs_router_forked_ci_auth } let(:authorization) { authorize_ci_project }
it_behaves_like 'unauthorized' it_behaves_like 'unauthorized'
end end
context 'for unauthenticated' do context 'for unauthenticated' do
let(:router) { lfs_router_forked_noauth }
it_behaves_like 'unauthorized' it_behaves_like 'unauthorized'
end end
describe 'and second project not related to fork or a source project' do describe 'and second project not related to fork or a source project' do
let(:second_project) { create(:project) } let(:second_project) { create(:empty_project) }
let(:lfs_router_second_project) { new_lfs_router(second_project, user: user) } let(:authorization) { authorize_user }
before do before do
public_project.lfs_objects << lfs_object second_project.team << [user, :master]
headers_for_upload_finalize(second_project) upstream_project.lfs_objects << lfs_object
end end
context 'when pushing the same lfs object to the second project' do context 'when pushing the same lfs object to the second project' do
before do before do
second_project.team << [user, :master] put "#{second_project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", nil,
headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp_file).compact
end end
it 'responds with 200 and links the lfs object to the project' do it 'responds with status 200' do
expect(lfs_router_second_project.try_call.first).to eq(200) expect(response).to have_http_status(200)
expect(lfs_object.projects.pluck(:id)).to include(second_project.id, public_project.id)
end end
it 'links the lfs object to the project' do
expect(lfs_object.projects.pluck(:id)).to include(second_project.id, upstream_project.id)
end end
end end
end end
end end
def enable_lfs def put_authorize
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, headers
end end
def authorize(user) def put_finalize(lfs_tmp = lfs_tmp_file)
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", nil,
headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp).compact
end end
def new_lfs_router(project, user: nil, ci: false) def lfs_tmp_file
Gitlab::Lfs::Router.new(project, user, ci, request) "#{sample_oid}012345678"
end
end end
def header_for_upload_authorize(project) def enable_lfs
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize" allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end
def authorize_ci_project
ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', project.runners_token)
end end
def headers_for_upload_finalize(project) def authorize_user
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
env["HTTP_X_GITLAB_LFS_TMP"] = "#{sample_oid}6e561c9d4"
end end
def fork_project(project, user, object = nil) def fork_project(project, user, object = nil)
allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
Projects::ForkService.new(project, user, {}).execute Projects::ForkService.new(project, user, {}).execute
end end
def post_json(url, body = nil, headers = nil)
post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => 'application/json'))
end
def json_response
@json_response ||= JSON.parse(response.body)
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