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
let(:project) { create(:empty_project) }
let(:body) do
{
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078
},
{ 'oid' => sample_oid,
'size' => sample_size
}
],
'operation' => 'upload'
}
end
before do before do
allow(Gitlab.config.lfs).to receive(:enabled).and_return(false) allow(Gitlab.config.lfs).to receive(:enabled).and_return(false)
env['REQUEST_METHOD'] = 'POST' post_json "#{project.http_url_to_repo}/info/lfs/objects/batch", body, headers
body = {
'objects' => [
{ 'oid' => '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897',
'size' => 1575078
},
{ 'oid' => sample_oid,
'size' => sample_size
}
],
'operation' => 'upload'
}.to_json
env['rack.input'] = StringIO.new(body)
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects/batch"
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
it 'responds with 501' do shared_examples 'a deprecated' do
expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated) it 'responds with 501' do
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
context 'when fetching lfs object using deprecated API' do
let(:authorization) { authorize_user }
before do
get "#{project.http_url_to_repo}/info/lfs/objects/#{sample_oid}", nil, headers
end
it_behaves_like 'a deprecated'
end
context 'when handling lfs request using deprecated API' do
before do
post_json "#{project.http_url_to_repo}/info/lfs/objects", nil, headers
end
it_behaves_like 'a deprecated'
end end
end end
describe 'when fetching lfs object' do describe 'when fetching lfs object' do
let(:project) { create(:empty_project) }
let(:update_permissions) { }
before do before do
enable_lfs enable_lfs
env['HTTP_ACCEPT'] = "application/vnd.git-lfs+json; charset=utf-8" update_permissions
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}" get "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}", nil, headers
end end
describe 'and request comes from gitlab-workhorse' do context 'and request comes from gitlab-workhorse' do
context 'without user being authorized' do context 'without user being authorized' do
it "responds with status 401" do it 'responds with status 401' do
expect(lfs_router_noauth.try_call.first).to eq(401) expect(response).to have_http_status(401)
end end
end end
context 'with required headers' do context 'with required headers' do
before do shared_examples 'responds with a file' do
project.lfs_objects << lfs_object let(:sendfile) { 'X-Sendfile' }
env['HTTP_X_SENDFILE_TYPE'] = "X-Sendfile"
end
context 'when user does not have project access' do it 'responds with status 200' do
it "responds with status 403" do expect(response).to have_http_status(200)
expect(lfs_router_auth.try_call.first).to eq(403)
end end
end
context 'when user has project access' do it 'responds with the file location' do
before do expect(response.headers['Content-Type']).to eq('application/octet-stream')
project.team << [user, :master] expect(response.headers['X-Sendfile']).to eq(lfs_object.file.path)
end end
end
context 'with user is authorized' do
let(:authorization) { authorize_user }
context 'and does not have project access' do
let(:update_permissions) do
project.lfs_objects << lfs_object
end
it "responds with status 200" do it 'responds with status 403' do
expect(lfs_router_auth.try_call.first).to eq(200) expect(response).to have_http_status(403)
end
end end
it "responds with the file location" do context 'and does have project access' do
expect(lfs_router_auth.try_call[1]['Content-Type']).to eq("application/octet-stream") let(:update_permissions) do
expect(lfs_router_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path) project.team << [user, :master]
project.lfs_objects << lfs_object
end
it_behaves_like 'responds with a file'
end end
end end
context 'when CI is authorized' do context 'when CI is authorized' do
it "responds with status 200" do let(:authorization) { authorize_ci_project }
expect(lfs_router_ci_auth.try_call.first).to eq(200)
end
it "responds with the file location" do let(:update_permissions) do
expect(lfs_router_ci_auth.try_call[1]['Content-Type']).to eq("application/octet-stream") project.lfs_objects << lfs_object
expect(lfs_router_ci_auth.try_call[1]['X-Sendfile']).to eq(lfs_object.file.path)
end end
it_behaves_like 'responds with a file'
end end
end end
context 'without required headers' do context 'without required headers' do
it "responds with status 403" do let(:authorization) { authorize_user }
expect(lfs_router_auth.try_call.first).to eq(403)
it 'responds with status 403' do
expect(response).to have_http_status(403)
end end
end end
end end
end end
describe 'when handling lfs request using deprecated API' do
before do
enable_lfs
env['REQUEST_METHOD'] = 'POST'
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/info/lfs/objects"
end
it 'responds with 501' do
expect(lfs_router_auth.try_call).to match_array(respond_with_deprecated)
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
'objects' => [ { 'operation' => 'download',
{ 'oid' => sample_oid, 'objects' => [
'size' => sample_size { 'oid' => sample_oid,
}] '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,27 +249,29 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -231,27 +249,29 @@ 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
}, },
{ 'oid' => sample_oid, { 'oid' => sample_oid,
'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
env["HTTP_AUTHORIZATION"] = auth
end
it_behaves_like 'an authorized requests' do it_behaves_like 'an authorized requests'
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,172 +353,174 @@ describe Gitlab::Lfs::Router, lib: true do ...@@ -339,172 +353,174 @@ 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
'objects' => [ { 'operation' => 'upload',
{ 'oid' => sample_oid, 'objects' => [
'size' => sample_size { 'oid' => sample_oid,
}] '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
}, },
{ 'oid' => sample_oid, { 'oid' => sample_oid,
'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
'objects' => [ { 'operation' => 'other',
{ 'oid' => sample_oid, 'objects' => [
'size' => sample_size { 'oid' => sample_oid,
}] '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
it 'responds with status 200' do
expect(response).to have_http_status(200)
end end
it 'responds with 200 and links the lfs object to the project' do it 'links the lfs object to the project' do
expect(lfs_router_second_project.try_call.first).to eq(200) expect(lfs_object.projects.pluck(:id)).to include(second_project.id, upstream_project.id)
expect(lfs_object.projects.pluck(:id)).to include(second_project.id, public_project.id)
end end
end end
end end
end end
def put_authorize
put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize", nil, headers
end
def put_finalize(lfs_tmp = lfs_tmp_file)
put "#{project.http_url_to_repo}/gitlab-lfs/objects/#{sample_oid}/#{sample_size}", nil,
headers.merge('X-Gitlab-Lfs-Tmp' => lfs_tmp).compact
end
def lfs_tmp_file
"#{sample_oid}012345678"
end
end end
def enable_lfs def enable_lfs
allow(Gitlab.config.lfs).to receive(:enabled).and_return(true) allow(Gitlab.config.lfs).to receive(:enabled).and_return(true)
end end
def authorize(user) def authorize_ci_project
ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password) ActionController::HttpAuthentication::Basic.encode_credentials('gitlab-ci-token', project.runners_token)
end end
def new_lfs_router(project, user: nil, ci: false) def authorize_user
Gitlab::Lfs::Router.new(project, user, ci, request) ActionController::HttpAuthentication::Basic.encode_credentials(user.username, user.password)
end end
def header_for_upload_authorize(project) def fork_project(project, user, object = nil)
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}/authorize" allow(RepositoryForkWorker).to receive(:perform_async).and_return(true)
Projects::ForkService.new(project, user, {}).execute
end end
def headers_for_upload_finalize(project) def post_json(url, body = nil, headers = nil)
env["PATH_INFO"] = "#{project.repository.path_with_namespace}.git/gitlab-lfs/objects/#{sample_oid}/#{sample_size}" post(url, body.try(:to_json), (headers || {}).merge('Content-Type' => 'application/json'))
env["HTTP_X_GITLAB_LFS_TMP"] = "#{sample_oid}6e561c9d4"
end end
def fork_project(project, user, object = nil) def json_response
allow(RepositoryForkWorker).to receive(:perform_async).and_return(true) @json_response ||= JSON.parse(response.body)
Projects::ForkService.new(project, user, {}).execute
end 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