Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'spec/requests/lfs_http_spec.rb')
-rw-r--r--spec/requests/lfs_http_spec.rb1662
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