diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-17 14:59:07 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2020-12-17 14:59:07 +0300 |
commit | 8b573c94895dc0ac0e1d9d59cf3e8745e8b539ca (patch) | |
tree | 544930fb309b30317ae9797a9683768705d664c4 /spec/requests/lfs_http_spec.rb | |
parent | 4b1de649d0168371549608993deac953eb692019 (diff) |
Add latest changes from gitlab-org/gitlab@13-7-stable-eev13.7.0-rc42
Diffstat (limited to 'spec/requests/lfs_http_spec.rb')
-rw-r--r-- | spec/requests/lfs_http_spec.rb | 1662 |
1 files changed, 814 insertions, 848 deletions
diff --git a/spec/requests/lfs_http_spec.rb b/spec/requests/lfs_http_spec.rb index 48d125a37c3..535d511a459 100644 --- a/spec/requests/lfs_http_spec.rb +++ b/spec/requests/lfs_http_spec.rb @@ -6,1179 +6,1145 @@ RSpec.describe 'Git LFS API and storage' do include ProjectForksHelper include WorkhorseHelpers - let_it_be(:project, reload: true) { create(:project, :repository) } - let_it_be(:other_project) { create(:project, :repository) } + let_it_be(:project, reload: true) { create(:project, :empty_repo) } let_it_be(:user) { create(:user) } - let(:lfs_object) { create(:lfs_object, :with_file) } - let(:headers) do - { - 'Authorization' => authorization, - 'X-Sendfile-Type' => 'X-Sendfile' - }.compact - end - - let(:include_workhorse_jwt_header) { true } - let(:authorization) { } - let(:pipeline) { create(:ci_empty_pipeline, project: project) } + context 'with projects' do + it_behaves_like 'LFS http requests' do + let_it_be(:other_project, reload: true) { create(:project, :empty_repo) } - let(:sample_oid) { lfs_object.oid } - let(:sample_size) { lfs_object.size } - let(:sample_object) { { 'oid' => sample_oid, 'size' => sample_size } } - let(:non_existing_object_oid) { '91eff75a492a3ed0dfcb544d7f31326bc4014c8551849c192fd1e48d4dd2c897' } - let(:non_existing_object_size) { 1575078 } - let(:non_existing_object) { { 'oid' => non_existing_object_oid, 'size' => non_existing_object_size } } - let(:multiple_objects) { [sample_object, non_existing_object] } + let(:container) { project } + let(:authorize_guest) { project.add_guest(user) } + let(:authorize_download) { project.add_reporter(user) } + let(:authorize_upload) { project.add_developer(user) } - let(:lfs_enabled) { true } + context 'project specific LFS settings' do + let(:body) { upload_body(sample_object) } - before do - stub_lfs_setting(enabled: lfs_enabled) - end + before do + authorize_upload + project.update_attribute(:lfs_enabled, project_lfs_enabled) - context 'project specific LFS settings' do - let(:body) { upload_body(sample_object) } - let(:authorization) { authorize_user } + subject + end - before do - project.add_maintainer(user) - project.update_attribute(:lfs_enabled, project_lfs_enabled) + describe 'LFS disabled in project' do + let(:project_lfs_enabled) { false } - subject - end + context 'when uploading' do + subject(:request) { post_lfs_json(batch_url(project), body, headers) } - describe 'LFS disabled in project' do - let(:project_lfs_enabled) { false } + it_behaves_like 'LFS http 404 response' + end - context 'when uploading' do - subject { post_lfs_json(batch_url(project), body, headers) } + context 'when downloading' do + subject(:request) { get(objects_url(project, sample_oid), params: {}, headers: headers) } - it_behaves_like 'LFS http 404 response' - end + it_behaves_like 'LFS http 404 response' + end + end - context 'when downloading' do - subject { get(objects_url(project, sample_oid), params: {}, headers: headers) } + describe 'LFS enabled in project' do + let(:project_lfs_enabled) { true } - it_behaves_like 'LFS http 404 response' - end - end + context 'when uploading' do + subject(:request) { post_lfs_json(batch_url(project), body, headers) } - describe 'LFS enabled in project' do - let(:project_lfs_enabled) { true } + it_behaves_like 'LFS http 200 response' + end - context 'when uploading' do - subject { post_lfs_json(batch_url(project), body, headers) } + context 'when downloading' do + subject(:request) { get(objects_url(project, sample_oid), params: {}, headers: headers) } - it_behaves_like 'LFS http 200 response' + it_behaves_like 'LFS http 200 blob response' + end + end end - context 'when downloading' do - subject { get(objects_url(project, sample_oid), params: {}, headers: headers) } + describe 'when fetching LFS object' do + subject(:request) { get objects_url(project, sample_oid), params: {}, headers: headers } - it_behaves_like 'LFS http 200 blob response' - end - end - end + let(:response) { request && super() } - describe 'when fetching LFS object' do - let(:update_permissions) { } - let(:before_get) { } + before do + project.lfs_objects << lfs_object + end - before do - project.lfs_objects << lfs_object - update_permissions - before_get + context 'when LFS uses object storage' do + before do + authorize_download + end - get objects_url(project, sample_oid), params: {}, headers: headers - end + context 'when proxy download is enabled' do + before do + stub_lfs_object_storage(proxy_download: true) + lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE) + end - context 'when LFS uses object storage' do - let(:authorization) { authorize_user } + 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 - let(:update_permissions) do - project.add_maintainer(user) - end + context 'when proxy download is disabled' do + before do + stub_lfs_object_storage(proxy_download: false) + lfs_object.file.migrate!(LfsObjectUploader::Store::REMOTE) + end - 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 redirect' do + expect(response).to have_gitlab_http_status(:found) + 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:") + it 'responds with the file location' do + expect(response.location).to include(lfs_object.reload.file.path) + end + end end - 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 + context 'when deploy key is authorized' do + let_it_be(:key) { create(:deploy_key) } + let(:authorization) { authorize_deploy_key } - it 'responds with redirect' do - expect(response).to have_gitlab_http_status(:found) - end + before do + project.deploy_keys << key + end - it 'responds with the file location' do - expect(response.location).to include(lfs_object.reload.file.path) + it_behaves_like 'LFS http 200 blob response' end - end - end - context 'when deploy key is authorized' do - let(:key) { create(:deploy_key) } - let(:authorization) { authorize_deploy_key } + context 'when using a user key (LFSToken)' do + let(:authorization) { authorize_user_key } - let(:update_permissions) do - project.deploy_keys << key - end + context 'when user allowed' do + before do + authorize_download + end - it_behaves_like 'LFS http 200 blob response' - end + it_behaves_like 'LFS http 200 blob response' - context 'when using a user key (LFSToken)' do - let(:authorization) { authorize_user_key } + context 'when user password is expired' do + let_it_be(:user) { create(:user, password_expires_at: 1.minute.ago)} - context 'when user allowed' do - let(:update_permissions) do - project.add_maintainer(user) - end + it_behaves_like 'LFS http 401 response' + end - it_behaves_like 'LFS http 200 blob response' + context 'when user is blocked' do + let_it_be(:user) { create(:user, :blocked)} - context 'when user password is expired' do - let(:user) { create(:user, password_expires_at: 1.minute.ago)} + it_behaves_like 'LFS http 401 response' + end + end - it_behaves_like 'LFS http 401 response' + context 'when user not allowed' do + it_behaves_like 'LFS http 404 response' + end end - context 'when user is blocked' do - let(:user) { create(:user, :blocked)} + context 'when build is authorized as' do + let(:authorization) { authorize_ci_project } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } - it_behaves_like 'LFS http 401 response' - end - end + shared_examples 'can download LFS only from own projects' do + context 'for owned project' do + let_it_be(:project) { create(:project, namespace: user.namespace) } - context 'when user not allowed' do - it_behaves_like 'LFS http 404 response' - end - end + it_behaves_like 'LFS http 200 blob response' + end - context 'when build is authorized as' do - let(:authorization) { authorize_ci_project } + context 'for member of project' do + before do + authorize_download + end - 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 200 blob response' + end - it_behaves_like 'LFS http 200 blob response' - end + context 'for other project' do + let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } - context 'for member of project' do - let(:pipeline) { create(:ci_empty_pipeline, project: project) } + it 'rejects downloading code' do + expect(response).to have_gitlab_http_status(:not_found) + end + end + end + + context 'administrator', :enable_admin_mode do + let_it_be(:user) { create(:admin) } - let(:update_permissions) do - project.add_reporter(user) + it_behaves_like 'can download LFS only from own projects' end - it_behaves_like 'LFS http 200 blob response' - end + context 'regular user' do + it_behaves_like 'can download LFS only from own projects' + end - context 'for other project' do - let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } + context 'does not have user' do + let(:build) { create(:ci_build, :running, pipeline: pipeline) } - it 'rejects downloading code' do - expect(response).to have_gitlab_http_status(:not_found) + it_behaves_like 'can download LFS only from own projects' 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' - end + describe 'when handling LFS batch request' do + subject(:request) { post_lfs_json batch_url(project), body, headers } - context 'regular user' do - let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } + let(:response) { request && super() } + let(:lfs_chunked_encoding) { true } - it_behaves_like 'can download LFS only from own projects' - end + before do + stub_feature_flags(lfs_chunked_encoding: lfs_chunked_encoding) + project.lfs_objects << lfs_object + end - context 'does not have user' do - let(:build) { create(:ci_build, :running, pipeline: pipeline) } + shared_examples 'process authorization header' do |renew_authorization:| + let(:response_authorization) do + authorization_in_action(lfs_actions.first) + end - it_behaves_like 'can download LFS only from own projects' - end - end - end + if renew_authorization + context 'when the authorization comes from a user' do + it 'returns a new valid LFS token authorization' do + expect(response_authorization).not_to eq(authorization) + end - describe 'when handling LFS batch request' do - let(:update_lfs_permissions) { } - let(:update_user_permissions) { } + it 'returns a valid token' do + username, token = ::Base64.decode64(response_authorization.split(' ', 2).last).split(':', 2) - before do - update_lfs_permissions - update_user_permissions - post_lfs_json batch_url(project), body, headers - end + expect(username).to eq(user.username) + expect(Gitlab::LfsToken.new(user).token_valid?(token)).to be_truthy + end - shared_examples 'process authorization header' do |renew_authorization:| - let(:response_authorization) do - authorization_in_action(lfs_actions.first) - end + it 'generates only one new token per each request' do + authorizations = lfs_actions.map do |action| + authorization_in_action(action) + end.compact - if renew_authorization - context 'when the authorization comes from a user' do - it 'returns a new valid LFS token authorization' do - expect(response_authorization).not_to eq(authorization) + expect(authorizations.uniq.count).to eq 1 + end + end + else + context 'when the authorization comes from a token' do + it 'returns the same authorization header' do + expect(response_authorization).to eq(authorization) + end + end end - it 'returns a a valid token' do - username, token = ::Base64.decode64(response_authorization.split(' ', 2).last).split(':', 2) - - expect(username).to eq(user.username) - expect(Gitlab::LfsToken.new(user).token_valid?(token)).to be_truthy + def lfs_actions + json_response['objects'].map { |a| a['actions'] }.compact end - it 'generates only one new token per each request' do - authorizations = lfs_actions.map do |action| - authorization_in_action(action) - end.compact - - expect(authorizations.uniq.count).to eq 1 + def authorization_in_action(action) + (action['upload'] || action['download']).dig('header', 'Authorization') end end - else - context 'when the authorization comes from a token' do - it 'returns the same authorization header' do - expect(response_authorization).to eq(authorization) - end - end - end - - def lfs_actions - json_response['objects'].map { |a| a['actions'] }.compact - end - def authorization_in_action(action) - (action['upload'] || action['download']).dig('header', 'Authorization') - end - end + describe 'download' do + let(:body) { download_body(sample_object) } - describe 'download' do - let(:body) { download_body(sample_object) } + shared_examples 'an authorized request' do |renew_authorization:| + context 'when downloading an LFS object that is assigned to our project' do + it_behaves_like 'LFS http 200 response' - shared_examples 'an authorized request' do |renew_authorization:| - context 'when downloading an LFS object that is assigned to our project' do - let(:update_lfs_permissions) do - project.lfs_objects << lfs_object - end + it 'with href to download' do + expect(json_response['objects'].first).to include(sample_object) + expect(json_response['objects'].first['actions']['download']['href']).to eq(objects_url(project, sample_oid)) + end - it_behaves_like 'LFS http 200 response' + it_behaves_like 'process authorization header', renew_authorization: renew_authorization + end - it 'with href to download' do - expect(json_response['objects'].first).to include(sample_object) - expect(json_response['objects'].first['actions']['download']['href']).to eq(objects_url(project, sample_oid)) - end + context 'when downloading an LFS object that is assigned to other project' do + before do + lfs_object.update!(projects: [other_project]) + end - it_behaves_like 'process authorization header', renew_authorization: renew_authorization - end + it_behaves_like 'LFS http 200 response' - context 'when downloading an LFS object that is assigned to other project' do - let(:update_lfs_permissions) do - other_project.lfs_objects << lfs_object - end + it 'with an 404 for specific object' do + expect(json_response['objects'].first).to include(sample_object) + expect(json_response['objects'].first['error']).to include('code' => 404, 'message' => "Object does not exist on the server or you don't have permissions to access it") + end + end - it_behaves_like 'LFS http 200 response' + context 'when downloading a LFS object that does not exist' do + let(:body) { download_body(non_existing_object) } - it 'with an 404 for specific object' do - expect(json_response['objects'].first).to include(sample_object) - expect(json_response['objects'].first['error']).to include('code' => 404, 'message' => "Object does not exist on the server or you don't have permissions to access it") - end - end + it_behaves_like 'LFS http 200 response' - context 'when downloading a LFS object that does not exist' do - let(:body) { download_body(non_existing_object) } + it 'with an 404 for specific object' do + expect(json_response['objects'].first).to include(non_existing_object) + expect(json_response['objects'].first['error']).to include('code' => 404, 'message' => "Object does not exist on the server or you don't have permissions to access it") + end + end - it_behaves_like 'LFS http 200 response' + context 'when downloading one existing and one missing LFS object' do + let(:body) { download_body(multiple_objects) } - it 'with an 404 for specific object' do - expect(json_response['objects'].first).to include(non_existing_object) - expect(json_response['objects'].first['error']).to include('code' => 404, 'message' => "Object does not exist on the server or you don't have permissions to access it") - end - end + it_behaves_like 'LFS http 200 response' - context 'when downloading one new and one existing LFS object' do - let(:body) { download_body(multiple_objects) } - let(:update_lfs_permissions) do - project.lfs_objects << lfs_object - end + it 'responds with download hypermedia link for the existing object' do + expect(json_response['objects'].first).to include(sample_object) + expect(json_response['objects'].first['actions']['download']).to include('href' => objects_url(project, sample_oid)) + expect(json_response['objects'].last).to eq({ + 'oid' => non_existing_object_oid, + 'size' => non_existing_object_size, + 'error' => { + 'code' => 404, + 'message' => "Object does not exist on the server or you don't have permissions to access it" + } + }) + end - it_behaves_like 'LFS http 200 response' + it_behaves_like 'process authorization header', renew_authorization: renew_authorization + end - it 'responds with download hypermedia link for the new object' do - expect(json_response['objects'].first).to include(sample_object) - expect(json_response['objects'].first['actions']['download']).to include('href' => objects_url(project, sample_oid)) - expect(json_response['objects'].last).to eq({ - 'oid' => non_existing_object_oid, - 'size' => non_existing_object_size, - 'error' => { - 'code' => 404, - 'message' => "Object does not exist on the server or you don't have permissions to access it" - } - }) - end + context 'when downloading two existing LFS objects' do + let(:body) { download_body(multiple_objects) } + let(:other_object) { create(:lfs_object, :with_file, oid: non_existing_object_oid, size: non_existing_object_size) } - it_behaves_like 'process authorization header', renew_authorization: renew_authorization - end + before do + project.lfs_objects << other_object + end - context 'when downloading two existing LFS objects' do - let(:body) { download_body(multiple_objects) } - let(:other_object) { create(:lfs_object, :with_file, oid: non_existing_object_oid, size: non_existing_object_size) } - let(:update_lfs_permissions) do - project.lfs_objects << [lfs_object, other_object] - end + it 'responds with the download hypermedia link for each object' do + expect(json_response['objects'].first).to include(sample_object) + expect(json_response['objects'].first['actions']['download']).to include('href' => objects_url(project, sample_oid)) - it 'responds with the download hypermedia link for each object' do - expect(json_response['objects'].first).to include(sample_object) - expect(json_response['objects'].first['actions']['download']).to include('href' => objects_url(project, sample_oid)) + expect(json_response['objects'].last).to include(non_existing_object) + expect(json_response['objects'].last['actions']['download']).to include('href' => objects_url(project, non_existing_object_oid)) + end - expect(json_response['objects'].last).to include(non_existing_object) - expect(json_response['objects'].last['actions']['download']).to include('href' => objects_url(project, non_existing_object_oid)) + it_behaves_like 'process authorization header', renew_authorization: renew_authorization + end end - it_behaves_like 'process authorization header', renew_authorization: renew_authorization - end - end + context 'when user is authenticated' do + before do + project.add_role(user, role) if role + end - context 'when user is authenticated' do - let(:authorization) { authorize_user } + it_behaves_like 'an authorized request', renew_authorization: true do + let(:role) { :reporter } + end - let(:update_user_permissions) do - project.add_role(user, role) - end + context 'when user is not a member of the project' do + let(:role) { nil } - it_behaves_like 'an authorized request', renew_authorization: true do - let(:role) { :reporter } - end + it_behaves_like 'LFS http 404 response' + end - context 'when user is not a member of the project' do - let(:update_user_permissions) { nil } + context 'when user does not have download access' do + let(:role) { :guest } - it_behaves_like 'LFS http 404 response' - end + it_behaves_like 'LFS http 404 response' + end - context 'when user does not have download access' do - let(:role) { :guest } + context 'when user password is expired' do + let_it_be(:user) { create(:user, password_expires_at: 1.minute.ago)} + let(:role) { :reporter} - it_behaves_like 'LFS http 404 response' - end + # TODO: This should return a 404 response + # https://gitlab.com/gitlab-org/gitlab/-/issues/292006 + it_behaves_like 'LFS http 200 response' + end - context 'when user password is expired' do - let(:role) { :reporter} - let(:user) { create(:user, password_expires_at: 1.minute.ago)} + context 'when user is blocked' do + let_it_be(:user) { create(:user, :blocked)} + let(:role) { :reporter} - it 'with an 404 for specific object' do - expect(json_response['objects'].first).to include(sample_object) - expect(json_response['objects'].first['error']).to include('code' => 404, 'message' => "Object does not exist on the server or you don't have permissions to access it") + it_behaves_like 'LFS http 401 response' + end end - end - context 'when user is blocked' do - let(:role) { :reporter} - let(:user) { create(:user, :blocked)} + context 'when using Deploy Tokens' do + let(:authorization) { authorize_deploy_token } - it_behaves_like 'LFS http 401 response' - end - end + context 'when Deploy Token is not valid' do + let(:deploy_token) { create(:deploy_token, projects: [project], read_repository: false) } - context 'when using Deploy Tokens' do - let(:authorization) { authorize_deploy_token } - let(:update_user_permissions) { nil } - let(:role) { nil } - let(:update_lfs_permissions) do - project.lfs_objects << lfs_object - end + it_behaves_like 'LFS http 401 response' + end - context 'when Deploy Token is not valid' do - let(:deploy_token) { create(:deploy_token, projects: [project], read_repository: false) } + context 'when Deploy Token is not related to the project' do + let(:deploy_token) { create(:deploy_token, projects: [other_project]) } - it_behaves_like 'LFS http 401 response' - end + it_behaves_like 'LFS http 401 response' + end - context 'when Deploy Token is not related to the project' do - let(:deploy_token) { create(:deploy_token, projects: [other_project]) } + # TODO: We should fix this test case that causes flakyness by alternating the result of the above test cases. + context 'when Deploy Token is valid' do + let(:deploy_token) { create(:deploy_token, projects: [project]) } - it_behaves_like 'LFS http 401 response' - end + it_behaves_like 'an authorized request', renew_authorization: false + end + end - # TODO: We should fix this test case that causes flakyness by alternating the result of the above test cases. - context 'when Deploy Token is valid' do - let(:deploy_token) { create(:deploy_token, projects: [project]) } + context 'when build is authorized as' do + let(:authorization) { authorize_ci_project } - it_behaves_like 'an authorized request', renew_authorization: false - end - end + shared_examples 'can download LFS only from own projects' do |renew_authorization:| + context 'for own project' do + let(:pipeline) { create(:ci_empty_pipeline, project: project) } - context 'when build is authorized as' do - let(:authorization) { authorize_ci_project } + before do + authorize_download + end - let(:update_lfs_permissions) do - project.lfs_objects << lfs_object - end + it_behaves_like 'an authorized request', renew_authorization: renew_authorization + end - shared_examples 'can download LFS only from own projects' do |renew_authorization:| - context 'for own project' do - let(:pipeline) { create(:ci_empty_pipeline, project: project) } + context 'for other project' do + let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } - let(:update_user_permissions) do - project.add_reporter(user) + it 'rejects downloading code' do + expect(response).to have_gitlab_http_status(:not_found) + end + end end - it_behaves_like 'an authorized request', renew_authorization: renew_authorization - end - - context 'for other project' do - let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } + context 'administrator', :enable_admin_mode do + let_it_be(:user) { create(:admin) } + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } - it 'rejects downloading code' do - expect(response).to have_gitlab_http_status(:not_found) + it_behaves_like 'can download LFS only from own projects', renew_authorization: true 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', renew_authorization: true - end + context 'regular user' do + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } - 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 - 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) } + 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 - end - end + it_behaves_like 'can download LFS only from own projects', renew_authorization: false + end + end - context 'when user is not authenticated' do - describe 'is accessing public project' do - let(:project) { create(:project, :public) } + context 'when user is not authenticated' do + let(:authorization) { nil } - let(:update_lfs_permissions) do - project.lfs_objects << lfs_object - end + describe 'is accessing public project' do + let_it_be(:project) { create(:project, :public) } - it_behaves_like 'LFS http 200 response' + it_behaves_like 'LFS http 200 response' - it 'returns href to download' do - expect(json_response).to eq({ - 'objects' => [ - { - 'oid' => sample_oid, - 'size' => sample_size, - 'authenticated' => true, - 'actions' => { - 'download' => { - 'href' => objects_url(project, sample_oid), - 'header' => {} + it 'returns href to download' do + expect(json_response).to eq({ + 'objects' => [ + { + 'oid' => sample_oid, + 'size' => sample_size, + 'authenticated' => true, + 'actions' => { + 'download' => { + 'href' => objects_url(project, sample_oid), + 'header' => {} + } + } } - } - } - ] - }) - end - end + ] + }) + end + end - describe 'is accessing non-public project' do - let(:update_lfs_permissions) do - project.lfs_objects << lfs_object + describe 'is accessing non-public project' do + it_behaves_like 'LFS http 401 response' + end end - - it_behaves_like 'LFS http 401 response' end - end - end - - describe 'upload' do - let(:project) { create(:project, :public) } - let(:body) { upload_body(sample_object) } - shared_examples 'pushes new LFS objects' do |renew_authorization:| - let(:sample_size) { 150.megabytes } - let(:sample_oid) { non_existing_object_oid } + describe 'upload' do + let_it_be(:project) { create(:project, :public) } + let(:body) { upload_body(sample_object) } - it_behaves_like 'LFS http 200 response' + shared_examples 'pushes new LFS objects' do |renew_authorization:| + let(:sample_size) { 150.megabytes } + let(:sample_oid) { non_existing_object_oid } - it 'responds with upload hypermedia link' do - expect(json_response['objects']).to be_kind_of(Array) - expect(json_response['objects'].first).to include(sample_object) - expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size)) - expect(json_response['objects'].first['actions']['upload']['header']).to include('Content-Type' => 'application/octet-stream') - end - - it_behaves_like 'process authorization header', renew_authorization: renew_authorization - end + it_behaves_like 'LFS http 200 response' - describe 'when request is authenticated' do - describe 'when user has project push access' do - let(:authorization) { authorize_user } + it 'responds with upload hypermedia link' do + expect(json_response['objects']).to be_kind_of(Array) + expect(json_response['objects'].first).to include(sample_object) + expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size)) - let(:update_user_permissions) do - project.add_developer(user) - end + headers = json_response['objects'].first['actions']['upload']['header'] + expect(headers['Content-Type']).to eq('application/octet-stream') + expect(headers['Transfer-Encoding']).to eq('chunked') + end - context 'when pushing an LFS object that already exists' do - shared_examples_for 'batch upload with existing LFS object' do - it_behaves_like 'LFS http 200 response' + context 'when lfs_chunked_encoding feature is disabled' do + let(:lfs_chunked_encoding) { false } - it 'responds with links the object to the project' do + it 'responds with upload hypermedia link' do expect(json_response['objects']).to be_kind_of(Array) expect(json_response['objects'].first).to include(sample_object) - expect(lfs_object.projects.pluck(:id)).not_to include(project.id) - expect(lfs_object.projects.pluck(:id)).to include(other_project.id) expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size)) - expect(json_response['objects'].first['actions']['upload']['header']).to include('Content-Type' => 'application/octet-stream') - end - it_behaves_like 'process authorization header', renew_authorization: true + headers = json_response['objects'].first['actions']['upload']['header'] + expect(headers['Content-Type']).to eq('application/octet-stream') + expect(headers['Transfer-Encoding']).to be_nil + end end - let(:update_lfs_permissions) do - other_project.lfs_objects << lfs_object - end + it_behaves_like 'process authorization header', renew_authorization: renew_authorization + end - context 'in another project' do - it_behaves_like 'batch upload with existing LFS object' - end + describe 'when request is authenticated' do + describe 'when user has project push access' do + before do + authorize_upload + end - context 'in source of fork project' do - let(:project) { fork_project(other_project) } + context 'when pushing an LFS object that already exists' do + shared_examples_for 'batch upload with existing LFS object' do + it_behaves_like 'LFS http 200 response' - it_behaves_like 'batch upload with existing LFS object' - end - end + it 'responds with links to the object in the project' do + expect(json_response['objects']).to be_kind_of(Array) + expect(json_response['objects'].first).to include(sample_object) + expect(lfs_object.projects.pluck(:id)).not_to include(project.id) + expect(lfs_object.projects.pluck(:id)).to include(other_project.id) + expect(json_response['objects'].first['actions']['upload']['href']).to eq(objects_url(project, sample_oid, sample_size)) - context 'when pushing a LFS object that does not exist' do - it_behaves_like 'pushes new LFS objects', renew_authorization: true - end + headers = json_response['objects'].first['actions']['upload']['header'] + expect(headers['Content-Type']).to eq('application/octet-stream') + expect(headers['Transfer-Encoding']).to eq('chunked') + end - context 'when pushing one new and one existing LFS object' do - let(:body) { upload_body(multiple_objects) } - let(:update_lfs_permissions) do - project.lfs_objects << lfs_object - end + it_behaves_like 'process authorization header', renew_authorization: true + end - it_behaves_like 'LFS http 200 response' + context 'in another project' do + before do + lfs_object.update!(projects: [other_project]) + end - it 'responds with upload hypermedia link for the new object' do - expect(json_response['objects']).to be_kind_of(Array) + it_behaves_like 'batch upload with existing LFS object' + end - expect(json_response['objects'].first).to include(sample_object) - expect(json_response['objects'].first).not_to have_key('actions') + context 'in source of fork project' do + let(:project) { fork_project(other_project) } - expect(json_response['objects'].last).to include(non_existing_object) - expect(json_response['objects'].last['actions']['upload']['href']).to eq(objects_url(project, non_existing_object_oid, non_existing_object_size)) - expect(json_response['objects'].last['actions']['upload']['header']).to include('Content-Type' => 'application/octet-stream') - end + before do + lfs_object.update!(projects: [other_project]) + end - it_behaves_like 'process authorization header', renew_authorization: true - end - end + it_behaves_like 'batch upload with existing LFS object' + end + end - context 'when user does not have push access' do - let(:authorization) { authorize_user } + context 'when pushing a LFS object that does not exist' do + it_behaves_like 'pushes new LFS objects', renew_authorization: true + end - it_behaves_like 'LFS http 403 response' - end + context 'when pushing one new and one existing LFS object' do + let(:body) { upload_body(multiple_objects) } - context 'when build is authorized' do - let(:authorization) { authorize_ci_project } + it_behaves_like 'LFS http 200 response' - context 'build has an user' do - let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } + it 'responds with upload hypermedia link for the new object' do + expect(json_response['objects']).to be_kind_of(Array) - context 'tries to push to own project' do - it_behaves_like 'LFS http 403 response' - end + expect(json_response['objects'].first).to include(sample_object) + expect(json_response['objects'].first).not_to have_key('actions') - context 'tries to push to other project' do - let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } + expect(json_response['objects'].last).to include(non_existing_object) + expect(json_response['objects'].last['actions']['upload']['href']).to eq(objects_url(project, non_existing_object_oid, non_existing_object_size)) + + headers = json_response['objects'].last['actions']['upload']['header'] + expect(headers['Content-Type']).to eq('application/octet-stream') + expect(headers['Transfer-Encoding']).to eq('chunked') + end + + it_behaves_like 'process authorization header', renew_authorization: true + end + end - # I'm not sure what this tests that is different from the previous test + context 'when user does not have push access' do it_behaves_like 'LFS http 403 response' end - end - context 'does not have user' do - let(:build) { create(:ci_build, :running, pipeline: pipeline) } + context 'when build is authorized' do + let(:authorization) { authorize_ci_project } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } - it_behaves_like 'LFS http 403 response' - end - end + context 'build has an user' do + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } - context 'when deploy key has project push access' do - let(:key) { create(:deploy_key) } - let(:authorization) { authorize_deploy_key } + context 'tries to push to own project' do + it_behaves_like 'LFS http 403 response' + end - let(:update_user_permissions) do - project.deploy_keys_projects.create!(deploy_key: key, can_push: true) - end + context 'tries to push to other project' do + let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } - it_behaves_like 'pushes new LFS objects', renew_authorization: false - end - end + # I'm not sure what this tests that is different from the previous test + it_behaves_like 'LFS http 403 response' + end + end - context 'when user is not authenticated' do - context 'when user has push access' do - let(:update_user_permissions) do - project.add_maintainer(user) - end + context 'does not have user' do + let(:build) { create(:ci_build, :running, pipeline: pipeline) } - it_behaves_like 'LFS http 401 response' - end + it_behaves_like 'LFS http 403 response' + end + end - context 'when user does not have push access' do - it_behaves_like 'LFS http 401 response' - end - end - end + context 'when deploy key has project push access' do + let(:key) { create(:deploy_key) } + let(:authorization) { authorize_deploy_key } - describe 'unsupported' do - let(:authorization) { authorize_user } - let(:body) { request_body('other', sample_object) } + before do + project.deploy_keys_projects.create!(deploy_key: key, can_push: true) + end - it_behaves_like 'LFS http 404 response' - end - end + it_behaves_like 'pushes new LFS objects', renew_authorization: false + end + end - describe 'when handling LFS batch request on a read-only GitLab instance' do - let(:authorization) { authorize_user } + context 'when user is not authenticated' do + let(:authorization) { nil } - subject { post_lfs_json(batch_url(project), body, headers) } + context 'when user has push access' do + before do + authorize_upload + end - before do - allow(Gitlab::Database).to receive(:read_only?) { true } + it_behaves_like 'LFS http 401 response' + end - project.add_maintainer(user) + context 'when user does not have push access' do + it_behaves_like 'LFS http 401 response' + end + end + end - subject - end + describe 'unsupported' do + let(:body) { request_body('other', sample_object) } - context 'when downloading' do - let(:body) { download_body(sample_object) } + it_behaves_like 'LFS http 404 response' + end + end - it_behaves_like 'LFS http 200 response' - end + describe 'when handling LFS batch request on a read-only GitLab instance' do + subject { post_lfs_json(batch_url(project), body, headers) } - context 'when uploading' do - let(:body) { upload_body(sample_object) } + before do + allow(Gitlab::Database).to receive(:read_only?) { true } - it_behaves_like 'LFS http expected response code and message' do - let(:response_code) { 403 } - let(:message) { 'You cannot write to this read-only GitLab instance.' } - end - end - end + project.add_maintainer(user) - describe 'when pushing a LFS object' do - shared_examples 'unauthorized' do - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - put_authorize + subject end - it_behaves_like 'LFS http 401 response' - end + context 'when downloading' do + let(:body) { download_body(sample_object) } - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - put_finalize + it_behaves_like 'LFS http 200 response' end - it_behaves_like 'LFS http 401 response' - end + context 'when uploading' do + let(:body) { upload_body(sample_object) } - context 'and request is sent with a malformed headers' do - before do - put_finalize('/etc/passwd') + it_behaves_like 'LFS http expected response code and message' do + let(:response_code) { 403 } + let(:message) { 'You cannot write to this read-only GitLab instance.' } + end end - - it_behaves_like 'LFS http 401 response' end - end - shared_examples 'forbidden' do - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - put_authorize - end + describe 'when pushing a LFS object' do + let(:include_workhorse_jwt_header) { true } - it_behaves_like 'LFS http 403 response' - end + shared_examples 'unauthorized' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + put_authorize + end - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - put_finalize - end + it_behaves_like 'LFS http 401 response' + end - it_behaves_like 'LFS http 403 response' - end + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + put_finalize + end - context 'and request is sent with a malformed headers' do - before do - put_finalize('/etc/passwd') + it_behaves_like 'LFS http 401 response' + end + + context 'and request is sent with a malformed headers' do + before do + put_finalize('/etc/passwd') + end + + it_behaves_like 'LFS http 401 response' + end end - it_behaves_like 'LFS http 403 response' - end - end + shared_examples 'forbidden' do + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + put_authorize + end - describe 'to one project' do - describe 'when user is authenticated' do - let(:authorization) { authorize_user } + it_behaves_like 'LFS http 403 response' + end - describe 'when user has push access to the project' do - before do - project.add_developer(user) + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + put_finalize + end + + it_behaves_like 'LFS http 403 response' end - context 'and the request bypassed workhorse' do - it 'raises an exception' do - expect { put_authorize(verified: false) }.to raise_error JWT::DecodeError + context 'and request is sent with a malformed headers' do + before do + put_finalize('/etc/passwd') end + + it_behaves_like 'LFS http 403 response' end + end - context 'and request is sent by gitlab-workhorse to authorize the request' do - shared_examples 'a valid response' do + describe 'to one project' do + describe 'when user is authenticated' do + describe 'when user has push access to the project' do before do - put_authorize + project.add_developer(user) end - it_behaves_like 'LFS http 200 workhorse response' - end - - shared_examples 'a local file' do - it_behaves_like 'a valid response' do - it 'responds with status 200, location of LFS store and object details' do - expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path) - expect(json_response['RemoteObject']).to be_nil - expect(json_response['LfsOid']).to eq(sample_oid) - expect(json_response['LfsSize']).to eq(sample_size) + context 'and the request bypassed workhorse' do + it 'raises an exception' do + expect { put_authorize(verified: false) }.to raise_error JWT::DecodeError end end - end - context 'when using local storage' do - it_behaves_like 'a local file' - end + context 'and request is sent by gitlab-workhorse to authorize the request' do + shared_examples 'a valid response' do + before do + put_authorize + end - context 'when using remote storage' do - context 'when direct upload is enabled' do - before do - stub_lfs_object_storage(enabled: true, direct_upload: true) + it_behaves_like 'LFS http 200 workhorse response' end - it_behaves_like 'a valid response' do - it 'responds with status 200, location of LFS remote store and object details' do - expect(json_response).not_to have_key('TempPath') - expect(json_response['RemoteObject']).to have_key('ID') - expect(json_response['RemoteObject']).to have_key('GetURL') - expect(json_response['RemoteObject']).to have_key('StoreURL') - expect(json_response['RemoteObject']).to have_key('DeleteURL') - expect(json_response['RemoteObject']).not_to have_key('MultipartUpload') - expect(json_response['LfsOid']).to eq(sample_oid) - expect(json_response['LfsSize']).to eq(sample_size) + shared_examples 'a local file' do + it_behaves_like 'a valid response' do + it 'responds with status 200, location of LFS store and object details' do + expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path) + expect(json_response['RemoteObject']).to be_nil + expect(json_response['LfsOid']).to eq(sample_oid) + expect(json_response['LfsSize']).to eq(sample_size) + end end end - end - context 'when direct upload is disabled' do - before do - stub_lfs_object_storage(enabled: true, direct_upload: false) + context 'when using local storage' do + it_behaves_like 'a local file' end - it_behaves_like 'a local file' - end - end - end + context 'when using remote storage' do + context 'when direct upload is enabled' do + before do + stub_lfs_object_storage(enabled: true, direct_upload: true) + end - context 'and request is sent by gitlab-workhorse to finalize the upload' do - before do - put_finalize - end + it_behaves_like 'a valid response' do + it 'responds with status 200, location of LFS remote store and object details' do + expect(json_response).not_to have_key('TempPath') + expect(json_response['RemoteObject']).to have_key('ID') + expect(json_response['RemoteObject']).to have_key('GetURL') + expect(json_response['RemoteObject']).to have_key('StoreURL') + expect(json_response['RemoteObject']).to have_key('DeleteURL') + expect(json_response['RemoteObject']).not_to have_key('MultipartUpload') + expect(json_response['LfsOid']).to eq(sample_oid) + expect(json_response['LfsSize']).to eq(sample_size) + end + end + end - it_behaves_like 'LFS http 200 response' + context 'when direct upload is disabled' do + before do + stub_lfs_object_storage(enabled: true, direct_upload: false) + end - it 'LFS object is linked to the project' do - expect(lfs_object.projects.pluck(:id)).to include(project.id) - end - end + it_behaves_like 'a local file' + end + end + end - context 'and request to finalize the upload is not sent by gitlab-workhorse' do - it 'fails with a JWT decode error' do - expect { put_finalize(lfs_tmp_file, verified: false) }.to raise_error(JWT::DecodeError) - end - end + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + put_finalize + end - context 'and workhorse requests upload finalize for a new LFS object' do - before do - lfs_object.destroy! - end + it_behaves_like 'LFS http 200 response' - context 'with object storage disabled' do - it "doesn't attempt to migrate file to object storage" do - expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async) + it 'LFS object is linked to the project' do + expect(lfs_object.projects.pluck(:id)).to include(project.id) + end + end - put_finalize(with_tempfile: true) + context 'and request to finalize the upload is not sent by gitlab-workhorse' do + it 'fails with a JWT decode error' do + expect { put_finalize(lfs_tmp_file, verified: false) }.to raise_error(JWT::DecodeError) + end end - end - context 'with object storage enabled' do - context 'and direct upload enabled' do - let!(:fog_connection) do - stub_lfs_object_storage(direct_upload: true) + context 'and workhorse requests upload finalize for a new LFS object' do + before do + lfs_object.destroy! end - let(:tmp_object) do - fog_connection.directories.new(key: 'lfs-objects').files.create( # rubocop: disable Rails/SaveBang - key: 'tmp/uploads/12312300', - body: 'content' - ) + context 'with object storage disabled' do + it "doesn't attempt to migrate file to object storage" do + expect(ObjectStorage::BackgroundMoveWorker).not_to receive(:perform_async) + + put_finalize(with_tempfile: true) + end end - ['123123', '../../123123'].each do |remote_id| - context "with invalid remote_id: #{remote_id}" do - subject do - put_finalize(remote_object: tmp_object, args: { - 'file.remote_id' => remote_id - }) + context 'with object storage enabled' do + context 'and direct upload enabled' do + let!(:fog_connection) do + stub_lfs_object_storage(direct_upload: true) end - it 'responds with status 403' do - subject + let(:tmp_object) do + fog_connection.directories.new(key: 'lfs-objects').files.create( # rubocop: disable Rails/SaveBang + key: 'tmp/uploads/12312300', + body: 'content' + ) + end + + ['123123', '../../123123'].each do |remote_id| + context "with invalid remote_id: #{remote_id}" do + subject do + put_finalize(remote_object: tmp_object, args: { + 'file.remote_id' => remote_id + }) + end + + it 'responds with status 403' do + subject - expect(response).to have_gitlab_http_status(:forbidden) + expect(response).to have_gitlab_http_status(:forbidden) + end + end end - end - end - context 'with valid remote_id' do - subject do - put_finalize(remote_object: tmp_object, args: { - 'file.remote_id' => '12312300', - 'file.name' => 'name' - }) - end + context 'with valid remote_id' do + subject do + put_finalize(remote_object: tmp_object, args: { + 'file.remote_id' => '12312300', + 'file.name' => 'name' + }) + end - it 'responds with status 200' do - subject + it 'responds with status 200' do + subject - expect(response).to have_gitlab_http_status(:ok) + expect(response).to have_gitlab_http_status(:ok) - object = LfsObject.find_by_oid(sample_oid) - expect(object).to be_present - expect(object.file.read).to eq(tmp_object.body) - end + object = LfsObject.find_by_oid(sample_oid) + expect(object).to be_present + expect(object.file.read).to eq(tmp_object.body) + end + + it 'schedules migration of file to object storage' do + subject + + expect(LfsObject.last.projects).to include(project) + end - it 'schedules migration of file to object storage' do - subject + it 'have valid file' do + subject - expect(LfsObject.last.projects).to include(project) + expect(LfsObject.last.file_store).to eq(ObjectStorage::Store::REMOTE) + expect(LfsObject.last.file).to be_exists + end + end end - it 'have valid file' do - subject + context 'and background upload enabled' do + before do + stub_lfs_object_storage(background_upload: true) + end + + it 'schedules migration of file to object storage' do + expect(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async).with('LfsObjectUploader', 'LfsObject', :file, kind_of(Numeric)) - expect(LfsObject.last.file_store).to eq(ObjectStorage::Store::REMOTE) - expect(LfsObject.last.file).to be_exists + put_finalize(with_tempfile: true) + end end end end - context 'and background upload enabled' do + context 'without the lfs object' do before do - stub_lfs_object_storage(background_upload: true) + lfs_object.destroy! end - it 'schedules migration of file to object storage' do - expect(ObjectStorage::BackgroundMoveWorker).to receive(:perform_async).with('LfsObjectUploader', 'LfsObject', :file, kind_of(Numeric)) + it 'rejects slashes in the tempfile name (path traversal)' do + put_finalize('../bar', with_tempfile: true) + expect(response).to have_gitlab_http_status(:bad_request) + end + + context 'not sending the workhorse jwt header' do + let(:include_workhorse_jwt_header) { false } - put_finalize(with_tempfile: true) + it 'rejects the request' do + put_finalize(with_tempfile: true) + + expect(response).to have_gitlab_http_status(:unprocessable_entity) + end end end end - end - context 'without the lfs object' do - before do - lfs_object.destroy! - end + describe 'and user does not have push access' do + before do + project.add_reporter(user) + end - it 'rejects slashes in the tempfile name (path traversal)' do - put_finalize('../bar', with_tempfile: true) - expect(response).to have_gitlab_http_status(:bad_request) + it_behaves_like 'forbidden' end + end - context 'not sending the workhorse jwt header' do - let(:include_workhorse_jwt_header) { false } - - it 'rejects the request' do - put_finalize(with_tempfile: true) + context 'when build is authorized' do + let(:authorization) { authorize_ci_project } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } - expect(response).to have_gitlab_http_status(:unprocessable_entity) - end - end - end - end + context 'build has an user' do + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } - describe 'and user does not have push access' do - before do - project.add_reporter(user) - end + context 'tries to push to own project' do + before do + project.add_developer(user) + put_authorize + end - it_behaves_like 'forbidden' - end - end + it_behaves_like 'LFS http 403 response' + end - context 'when build is authorized' do - let(:authorization) { authorize_ci_project } + context 'tries to push to other project' do + let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } - context 'build has an user' do - let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } + before do + put_authorize + end - context 'tries to push to own project' do - before do - project.add_developer(user) - put_authorize + it_behaves_like 'LFS http 404 response' + end end - it_behaves_like 'LFS http 403 response' - end + context 'does not have user' do + let(:build) { create(:ci_build, :running, pipeline: pipeline) } - context 'tries to push to other project' do - let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } + before do + put_authorize + end - before do - put_authorize + it_behaves_like 'LFS http 403 response' end - - it_behaves_like 'LFS http 404 response' end - end - context 'does not have user' do - let(:build) { create(:ci_build, :running, pipeline: pipeline) } + describe 'when using a user key (LFSToken)' do + let(:authorization) { authorize_user_key } - before do - put_authorize - end + context 'when user allowed' do + before do + project.add_developer(user) + put_authorize + end - it_behaves_like 'LFS http 403 response' - end - end + it_behaves_like 'LFS http 200 workhorse response' - describe 'when using a user key (LFSToken)' do - let(:authorization) { authorize_user_key } + context 'when user password is expired' do + let_it_be(:user) { create(:user, password_expires_at: 1.minute.ago)} - context 'when user allowed' do - before do - project.add_developer(user) - put_authorize - end + it_behaves_like 'LFS http 401 response' + end - it_behaves_like 'LFS http 200 workhorse response' + context 'when user is blocked' do + let_it_be(:user) { create(:user, :blocked)} - context 'when user password is expired' do - let(:user) { create(:user, password_expires_at: 1.minute.ago)} + it_behaves_like 'LFS http 401 response' + end + end - it_behaves_like 'LFS http 401 response' + context 'when user not allowed' do + before do + put_authorize + end + + it_behaves_like 'LFS http 404 response' + end end - context 'when user is blocked' do - let(:user) { create(:user, :blocked)} + context 'for unauthenticated' do + let(:authorization) { nil } - it_behaves_like 'LFS http 401 response' + it_behaves_like 'unauthorized' end end - context 'when user not allowed' do - before do - put_authorize - end + describe 'to a forked project' do + let_it_be(:upstream_project) { create(:project, :public) } + let_it_be(:project_owner) { create(:user) } + let(:project) { fork_project(upstream_project, project_owner) } - it_behaves_like 'LFS http 404 response' - end - end + describe 'when user is authenticated' do + describe 'when user has push access to the project' do + before do + project.add_developer(user) + end - context 'for unauthenticated' do - it_behaves_like 'unauthorized' - end - end + context 'and request is sent by gitlab-workhorse to authorize the request' do + before do + put_authorize + end - describe 'to a forked project' do - let(:upstream_project) { create(:project, :public) } - let(:project_owner) { create(:user) } - let(:project) { fork_project(upstream_project, project_owner) } + it_behaves_like 'LFS http 200 workhorse response' - describe 'when user is authenticated' do - let(:authorization) { authorize_user } + it 'with location of LFS store and object details' do + expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path) + expect(json_response['LfsOid']).to eq(sample_oid) + expect(json_response['LfsSize']).to eq(sample_size) + end + end - describe 'when user has push access to the project' do - before do - project.add_developer(user) - end + context 'and request is sent by gitlab-workhorse to finalize the upload' do + before do + put_finalize + end - context 'and request is sent by gitlab-workhorse to authorize the request' do - before do - put_authorize - end + it_behaves_like 'LFS http 200 response' - it_behaves_like 'LFS http 200 workhorse response' + it 'LFS object is linked to the forked project' do + expect(lfs_object.projects.pluck(:id)).to include(project.id) + end + end + end - it 'with location of LFS store and object details' do - expect(json_response['TempPath']).to eq(LfsObjectUploader.workhorse_local_upload_path) - expect(json_response['LfsOid']).to eq(sample_oid) - expect(json_response['LfsSize']).to eq(sample_size) + describe 'and user does not have push access' do + it_behaves_like 'forbidden' end end - context 'and request is sent by gitlab-workhorse to finalize the upload' do + context 'when build is authorized' do + let(:authorization) { authorize_ci_project } + let(:pipeline) { create(:ci_empty_pipeline, project: project) } + before do - put_finalize + put_authorize end - it_behaves_like 'LFS http 200 response' + context 'build has an user' do + let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } - it 'LFS object is linked to the forked project' do - expect(lfs_object.projects.pluck(:id)).to include(project.id) - end - end - end - - describe 'and user does not have push access' do - it_behaves_like 'forbidden' - end - end + context 'tries to push to own project' do + it_behaves_like 'LFS http 403 response' + end - context 'when build is authorized' do - let(:authorization) { authorize_ci_project } + context 'tries to push to other project' do + let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } - before do - put_authorize - end + # I'm not sure what this tests that is different from the previous test + it_behaves_like 'LFS http 403 response' + end + end - context 'build has an user' do - let(:build) { create(:ci_build, :running, pipeline: pipeline, user: user) } + context 'does not have user' do + let(:build) { create(:ci_build, :running, pipeline: pipeline) } - context 'tries to push to own project' do - it_behaves_like 'LFS http 403 response' + it_behaves_like 'LFS http 403 response' + end end - context 'tries to push to other project' do - let(:pipeline) { create(:ci_empty_pipeline, project: other_project) } + context 'for unauthenticated' do + let(:authorization) { nil } - # I'm not sure what this tests that is different from the previous test - it_behaves_like 'LFS http 403 response' + it_behaves_like 'unauthorized' end - end - context 'does not have user' do - let(:build) { create(:ci_build, :running, pipeline: pipeline) } + describe 'and second project not related to fork or a source project' do + let_it_be(:second_project) { create(:project) } - it_behaves_like 'LFS http 403 response' - end - end - - context 'for unauthenticated' do - it_behaves_like 'unauthorized' - end + before do + second_project.add_maintainer(user) + upstream_project.lfs_objects << lfs_object + end - describe 'and second project not related to fork or a source project' do - let(:second_project) { create(:project) } - let(:authorization) { authorize_user } + context 'when pushing the same LFS object to the second project' do + before do + finalize_headers = headers + .merge('X-Gitlab-Lfs-Tmp' => lfs_tmp_file) + .merge(workhorse_internal_api_request_header) - before do - second_project.add_maintainer(user) - upstream_project.lfs_objects << lfs_object - end + put objects_url(second_project, sample_oid, sample_size), + params: {}, + headers: finalize_headers + end - context 'when pushing the same LFS object to the second project' do - before do - finalize_headers = headers - .merge('X-Gitlab-Lfs-Tmp' => lfs_tmp_file) - .merge(workhorse_internal_api_request_header) + it_behaves_like 'LFS http 200 response' - put objects_url(second_project, sample_oid, sample_size), - params: {}, - headers: finalize_headers + 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 - it_behaves_like 'LFS http 200 response' + def put_authorize(verified: true) + authorize_headers = headers + authorize_headers.merge!(workhorse_internal_api_request_header) if verified - it 'links the LFS object to the project' do - expect(lfs_object.projects.pluck(:id)).to include(second_project.id, upstream_project.id) - end + put authorize_url(project, sample_oid, sample_size), params: {}, headers: authorize_headers end - end - end - def put_authorize(verified: true) - authorize_headers = headers - authorize_headers.merge!(workhorse_internal_api_request_header) if verified + def put_finalize(lfs_tmp = lfs_tmp_file, with_tempfile: false, verified: true, remote_object: nil, args: {}) + uploaded_file = nil - put authorize_url(project, sample_oid, sample_size), params: {}, headers: authorize_headers - end + if with_tempfile + upload_path = LfsObjectUploader.workhorse_local_upload_path + file_path = upload_path + '/' + lfs_tmp if lfs_tmp - def put_finalize(lfs_tmp = lfs_tmp_file, with_tempfile: false, verified: true, remote_object: nil, args: {}) - uploaded_file = nil + FileUtils.mkdir_p(upload_path) + FileUtils.touch(file_path) - if with_tempfile - upload_path = LfsObjectUploader.workhorse_local_upload_path - file_path = upload_path + '/' + lfs_tmp if lfs_tmp + uploaded_file = UploadedFile.new(file_path, filename: File.basename(file_path)) + elsif remote_object + uploaded_file = fog_to_uploaded_file(remote_object) + end - FileUtils.mkdir_p(upload_path) - FileUtils.touch(file_path) + finalize_headers = headers + finalize_headers.merge!(workhorse_internal_api_request_header) if verified + + workhorse_finalize( + objects_url(project, sample_oid, sample_size), + method: :put, + file_key: :file, + params: args.merge(file: uploaded_file), + headers: finalize_headers, + send_rewritten_field: include_workhorse_jwt_header + ) + end - uploaded_file = UploadedFile.new(file_path, filename: File.basename(file_path)) - elsif remote_object - uploaded_file = fog_to_uploaded_file(remote_object) + def lfs_tmp_file + "#{sample_oid}012345678" + end end - - finalize_headers = headers - finalize_headers.merge!(workhorse_internal_api_request_header) if verified - - workhorse_finalize( - objects_url(project, sample_oid, sample_size), - method: :put, - file_key: :file, - params: args.merge(file: uploaded_file), - headers: finalize_headers, - send_rewritten_field: include_workhorse_jwt_header - ) - end - - def lfs_tmp_file - "#{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 |