2019-09-27 20:06:20 -04:00
# frozen_string_literal: true
2016-07-15 08:01:34 -04:00
require 'spec_helper'
2020-06-24 02:09:01 -04:00
RSpec . describe 'Git LFS API and storage' do
2022-05-04 05:09:02 -04:00
using RSpec :: Parameterized :: TableSyntax
2019-09-27 20:06:20 -04:00
include LfsHttpHelpers
2017-09-29 04:04:50 -04:00
include ProjectForksHelper
2019-10-18 07:11:44 -04:00
include WorkhorseHelpers
2022-05-04 05:09:02 -04:00
include WorkhorseLfsHelpers
2016-08-19 13:10:41 -04:00
2020-12-08 13:10:08 -05:00
let_it_be ( :project , reload : true ) { create ( :project , :empty_repo ) }
2020-03-26 14:08:03 -04:00
let_it_be ( :user ) { create ( :user ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'with projects' do
it_behaves_like 'LFS http requests' do
let_it_be ( :other_project , reload : true ) { create ( :project , :empty_repo ) }
2016-08-24 18:08:23 -04:00
2020-12-08 13:10:08 -05:00
let ( :container ) { project }
let ( :authorize_guest ) { project . add_guest ( user ) }
let ( :authorize_download ) { project . add_reporter ( user ) }
let ( :authorize_upload ) { project . add_developer ( user ) }
2016-08-24 18:08:23 -04:00
2020-12-08 13:10:08 -05:00
context 'project specific LFS settings' do
let ( :body ) { upload_body ( sample_object ) }
2016-08-24 18:08:23 -04:00
2020-12-08 13:10:08 -05:00
before do
authorize_upload
project . update_attribute ( :lfs_enabled , project_lfs_enabled )
2016-08-24 18:08:23 -04:00
2020-12-08 13:10:08 -05:00
subject
end
2016-08-24 18:08:23 -04:00
2020-12-08 13:10:08 -05:00
describe 'LFS disabled in project' do
let ( :project_lfs_enabled ) { false }
2016-08-24 18:08:23 -04:00
2020-12-08 13:10:08 -05:00
context 'when uploading' do
subject ( :request ) { post_lfs_json ( batch_url ( project ) , body , headers ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 404 response'
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'when downloading' do
subject ( :request ) { get ( objects_url ( project , sample_oid ) , params : { } , headers : headers ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 404 response'
end
end
2020-11-10 19:08:58 -05:00
2020-12-08 13:10:08 -05:00
describe 'LFS enabled in project' do
let ( :project_lfs_enabled ) { true }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'when uploading' do
subject ( :request ) { post_lfs_json ( batch_url ( project ) , body , headers ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 response'
end
2016-07-15 08:23:26 -04:00
2020-12-08 13:10:08 -05:00
context 'when downloading' do
subject ( :request ) { get ( objects_url ( project , sample_oid ) , params : { } , headers : headers ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 blob response'
end
2016-07-15 08:01:34 -04:00
end
2020-11-10 19:08:58 -05:00
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
describe 'when fetching LFS object' do
subject ( :request ) { get objects_url ( project , sample_oid ) , params : { } , headers : headers }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
let ( :response ) { request && super ( ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
before do
project . lfs_objects << lfs_object
2020-11-10 19:08:58 -05:00
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'when LFS uses object storage' do
before do
authorize_download
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
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
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
2017-09-07 17:27:04 -04:00
2020-12-08 13:10:08 -05:00
context 'when proxy download is disabled' do
before do
stub_lfs_object_storage ( proxy_download : false )
lfs_object . file . migrate! ( LfsObjectUploader :: Store :: REMOTE )
end
2018-03-23 06:07:22 -04:00
2020-12-08 13:10:08 -05:00
it 'responds with redirect' do
expect ( response ) . to have_gitlab_http_status ( :found )
end
2018-03-23 06:07:22 -04:00
2020-12-08 13:10:08 -05:00
it 'responds with the file location' do
expect ( response . location ) . to include ( lfs_object . reload . file . path )
end
end
2020-11-10 19:08:58 -05:00
end
2017-09-07 17:27:04 -04:00
2020-12-08 13:10:08 -05:00
context 'when deploy key is authorized' do
let_it_be ( :key ) { create ( :deploy_key ) }
2021-06-28 23:07:32 -04:00
2020-12-08 13:10:08 -05:00
let ( :authorization ) { authorize_deploy_key }
2018-03-23 06:07:22 -04:00
2020-12-08 13:10:08 -05:00
before do
project . deploy_keys << key
end
2017-09-07 17:27:04 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 blob response'
2016-07-15 08:01:34 -04:00
end
2020-12-08 13:10:08 -05:00
context 'when using a user key (LFSToken)' do
let ( :authorization ) { authorize_user_key }
2016-09-19 10:34:32 -04:00
2020-12-08 13:10:08 -05:00
context 'when user allowed' do
before do
authorize_download
end
2016-09-27 14:23:51 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 blob response'
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
context 'when user password is expired' do
2021-09-30 14:11:31 -04:00
let_it_be ( :user ) { create ( :user , password_expires_at : 1 . minute . ago ) }
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 401 response'
end
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
context 'when user is blocked' do
let_it_be ( :user ) { create ( :user , :blocked ) }
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 401 response'
end
2016-09-27 14:23:51 -04:00
end
2020-12-08 13:10:08 -05:00
context 'when user not allowed' do
it_behaves_like 'LFS http 404 response'
2016-09-16 05:06:57 -04:00
end
2020-11-10 19:08:58 -05:00
end
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
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 ) }
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
shared_examples 'can download LFS only from own projects' do
context 'for owned project' do
let_it_be ( :project ) { create ( :project , namespace : user . namespace ) }
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 blob response'
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'for member of project' do
before do
authorize_download
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 blob response'
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'for other project' do
let ( :pipeline ) { create ( :ci_empty_pipeline , project : other_project ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it 'rejects downloading code' do
expect ( response ) . to have_gitlab_http_status ( :not_found )
end
end
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'administrator' , :enable_admin_mode do
let_it_be ( :user ) { create ( :admin ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'can download LFS only from own projects'
2016-07-15 08:01:34 -04:00
end
2020-12-08 13:10:08 -05:00
context 'regular user' do
it_behaves_like 'can download LFS only from own projects'
2016-07-15 08:01:34 -04:00
end
2020-12-08 13:10:08 -05:00
context 'does not have user' do
let ( :build ) { create ( :ci_build , :running , pipeline : pipeline ) }
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'can download LFS only from own projects'
2016-07-15 08:01:34 -04:00
end
end
2019-09-27 20:06:20 -04:00
end
2020-12-08 13:10:08 -05:00
describe 'when handling LFS batch request' do
subject ( :request ) { post_lfs_json batch_url ( project ) , body , headers }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
let ( :response ) { request && super ( ) }
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
before do
project . lfs_objects << lfs_object
end
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
shared_examples 'process authorization header' do | renew_authorization : |
let ( :response_authorization ) do
authorization_in_action ( lfs_actions . first )
2016-07-15 08:01:34 -04:00
end
2020-12-08 13:10:08 -05:00
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
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it 'returns a valid token' do
username , token = :: Base64 . decode64 ( response_authorization . split ( ' ' , 2 ) . last ) . split ( ':' , 2 )
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
expect ( username ) . to eq ( user . username )
expect ( Gitlab :: LfsToken . new ( user ) . token_valid? ( token ) ) . to be_truthy
end
it 'generates only one new token per each request' do
authorizations = lfs_actions . map do | action |
authorization_in_action ( action )
end . compact
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
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
2016-07-15 08:01:34 -04:00
end
2020-12-08 13:10:08 -05:00
def lfs_actions
json_response [ 'objects' ] . map { | a | a [ 'actions' ] } . compact
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
def authorization_in_action ( action )
( action [ 'upload' ] || action [ 'download' ] ) . dig ( 'header' , 'Authorization' )
2016-07-15 08:01:34 -04:00
end
end
2020-12-08 13:10:08 -05:00
describe 'download' do
let ( :body ) { download_body ( sample_object ) }
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
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'
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
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
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'process authorization header' , renew_authorization : renew_authorization
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'when downloading an LFS object that is assigned to other project' do
before do
lfs_object . update! ( projects : [ other_project ] )
end
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 response'
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
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
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
context 'when downloading a LFS object that does not exist' do
let ( :body ) { download_body ( non_existing_object ) }
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 response'
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
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
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'when downloading one existing and one missing LFS object' do
let ( :body ) { download_body ( multiple_objects ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 response'
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
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
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'process authorization header' , renew_authorization : renew_authorization
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
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 ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
before do
project . lfs_objects << other_object
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
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 ) )
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
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
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'process authorization header' , renew_authorization : renew_authorization
end
2016-07-15 08:01:34 -04:00
end
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
context 'when user is authenticated' do
before do
project . add_role ( user , role ) if role
end
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'an authorized request' , renew_authorization : true do
let ( :role ) { :reporter }
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'when user is not a member of the project' do
let ( :role ) { nil }
2018-07-23 05:23:08 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 404 response'
end
2018-07-23 05:23:08 -04:00
2020-12-08 13:10:08 -05:00
context 'when user does not have download access' do
let ( :role ) { :guest }
2018-07-23 05:23:08 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 404 response'
end
2018-07-23 05:23:08 -04:00
2020-12-08 13:10:08 -05:00
context 'when user password is expired' do
2021-09-30 14:11:31 -04:00
let_it_be ( :user ) { create ( :user , password_expires_at : 1 . minute . ago ) }
2021-06-28 23:07:32 -04:00
2020-12-08 13:10:08 -05:00
let ( :role ) { :reporter }
2020-08-18 14:10:10 -04:00
2021-06-01 17:10:06 -04:00
it_behaves_like 'LFS http 401 response'
2020-12-08 13:10:08 -05:00
end
2020-08-18 14:10:10 -04:00
2020-12-08 13:10:08 -05:00
context 'when user is blocked' do
let_it_be ( :user ) { create ( :user , :blocked ) }
2021-06-28 23:07:32 -04:00
2020-12-08 13:10:08 -05:00
let ( :role ) { :reporter }
2018-07-23 05:23:08 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 401 response'
end
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'when using Deploy Tokens' do
let ( :authorization ) { authorize_deploy_token }
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
context 'when Deploy Token is not valid' do
let ( :deploy_token ) { create ( :deploy_token , projects : [ project ] , read_repository : false ) }
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 401 response'
2016-09-16 05:06:57 -04:00
end
2020-12-08 13:10:08 -05:00
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
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
# 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 ] ) }
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'an authorized request' , renew_authorization : false
2016-09-16 05:06:57 -04:00
end
end
2020-12-08 13:10:08 -05:00
context 'when build is authorized as' do
let ( :authorization ) { authorize_ci_project }
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
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 ) }
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
before do
authorize_download
end
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'an authorized request' , renew_authorization : renew_authorization
end
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
context 'for other project' do
let ( :pipeline ) { create ( :ci_empty_pipeline , project : other_project ) }
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
it 'rejects downloading code' do
expect ( response ) . to have_gitlab_http_status ( :not_found )
end
end
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'administrator' , :enable_admin_mode do
let_it_be ( :user ) { create ( :admin ) }
2021-06-28 23:07:32 -04:00
2020-12-08 13:10:08 -05:00
let ( :build ) { create ( :ci_build , :running , pipeline : pipeline , user : user ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'can download LFS only from own projects' , renew_authorization : true
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'regular user' do
let ( :build ) { create ( :ci_build , :running , pipeline : pipeline , user : user ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'can download LFS only from own projects' , renew_authorization : true
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'does not have user' do
let ( :build ) { create ( :ci_build , :running , pipeline : pipeline ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'can download LFS only from own projects' , renew_authorization : false
end
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'when user is not authenticated' do
let ( :authorization ) { nil }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
describe 'is accessing public project' do
let_it_be ( :project ) { create ( :project , :public ) }
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 response'
2017-11-08 11:21:39 -05:00
2020-12-08 13:10:08 -05:00
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
2020-11-23 13:09:14 -05:00
2020-12-08 13:10:08 -05:00
describe 'is accessing non-public project' do
it_behaves_like 'LFS http 401 response'
end
end
2020-11-23 13:09:14 -05:00
end
2020-12-08 13:10:08 -05:00
describe 'upload' do
let_it_be ( :project ) { create ( :project , :public ) }
2021-06-28 23:07:32 -04:00
2020-12-08 13:10:08 -05:00
let ( :body ) { upload_body ( sample_object ) }
2020-11-23 13:09:14 -05:00
2020-12-08 13:10:08 -05:00
shared_examples 'pushes new LFS objects' do | renew_authorization : |
let ( :sample_size ) { 150 . megabytes }
let ( :sample_oid ) { non_existing_object_oid }
2020-11-23 13:09:14 -05:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 response'
2017-11-08 11:21:39 -05:00
2020-12-08 13:10:08 -05:00
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 ) )
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
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
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'process authorization header' , renew_authorization : renew_authorization
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
describe 'when request is authenticated' do
describe 'when user has project push access' do
before do
authorize_upload
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
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'
2020-03-04 22:07:52 -05:00
2020-12-08 13:10:08 -05:00
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 ) )
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
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
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'process authorization header' , renew_authorization : true
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'in another project' do
before do
lfs_object . update! ( projects : [ other_project ] )
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'batch upload with existing LFS object'
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'in source of fork project' do
2021-12-09 19:13:05 -05:00
let ( :other_project ) { create ( :project , :empty_repo ) }
2020-12-08 13:10:08 -05:00
let ( :project ) { fork_project ( other_project ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
before do
lfs_object . update! ( projects : [ other_project ] )
end
2020-11-23 13:09:14 -05:00
2021-12-09 19:13:05 -05:00
context 'when user has access to both the parent and fork' do
before do
project . add_developer ( user )
other_project . add_developer ( user )
end
it 'links existing LFS objects to other project' do
2021-12-16 13:14:09 -05:00
expect ( Gitlab :: AppJsonLogger ) . to receive ( :info ) . with (
message : " LFS object auto-linked to forked project " ,
lfs_object_oid : lfs_object . oid ,
lfs_object_size : lfs_object . size ,
source_project_id : other_project . id ,
source_project_path : other_project . full_path ,
target_project_id : project . id ,
target_project_path : project . full_path ) . and_call_original
2021-12-09 19:13:05 -05:00
expect ( json_response [ 'objects' ] ) . to be_kind_of ( Array )
expect ( json_response [ 'objects' ] . first ) . to include ( sample_object )
expect ( json_response [ 'objects' ] . first ) . not_to have_key ( 'actions' )
expect ( lfs_object . reload . projects . pluck ( :id ) ) . to match_array ( [ other_project . id , project . id ] )
end
end
context 'when user does not have access to parent' do
before do
project . add_developer ( user )
end
it_behaves_like 'batch upload with existing LFS object'
end
2020-12-08 13:10:08 -05:00
end
end
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
context 'when pushing a LFS object that does not exist' do
it_behaves_like 'pushes new LFS objects' , renew_authorization : true
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'when pushing one new and one existing LFS object' do
let ( :body ) { upload_body ( multiple_objects ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 response'
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it 'responds with upload hypermedia link for the new object' do
expect ( json_response [ 'objects' ] ) . to be_kind_of ( Array )
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
expect ( json_response [ 'objects' ] . first ) . to include ( sample_object )
expect ( json_response [ 'objects' ] . first ) . not_to have_key ( 'actions' )
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
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 ) )
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
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
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
context 'when user does not have push access' do
2019-09-27 20:06:20 -04:00
it_behaves_like 'LFS http 403 response'
2016-09-16 05:06:57 -04:00
end
2020-12-08 13:10:08 -05:00
context 'when build is authorized' do
let ( :authorization ) { authorize_ci_project }
let ( :pipeline ) { create ( :ci_empty_pipeline , project : project ) }
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
context 'build has an user' do
let ( :build ) { create ( :ci_build , :running , pipeline : pipeline , user : user ) }
2017-11-08 11:21:39 -05:00
2020-12-08 13:10:08 -05:00
context 'tries to push to own project' do
it_behaves_like 'LFS http 403 response'
end
2017-11-08 11:21:39 -05:00
2020-12-08 13:10:08 -05:00
context 'tries to push to other project' do
let ( :pipeline ) { create ( :ci_empty_pipeline , project : other_project ) }
2017-11-08 11:21:39 -05:00
2020-12-08 13:10:08 -05:00
# I'm not sure what this tests that is different from the previous test
2021-07-16 08:10:21 -04:00
it_behaves_like 'LFS http 403 response'
2020-12-08 13:10:08 -05:00
end
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'does not have user' do
let ( :build ) { create ( :ci_build , :running , pipeline : pipeline ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 403 response'
end
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'when deploy key has project push access' do
let ( :key ) { create ( :deploy_key ) }
let ( :authorization ) { authorize_deploy_key }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
before do
project . deploy_keys_projects . create! ( deploy_key : key , can_push : true )
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'pushes new LFS objects' , renew_authorization : false
end
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'when user is not authenticated' do
let ( :authorization ) { nil }
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
context 'when user has push access' do
before do
authorize_upload
end
2017-09-19 03:44:58 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 401 response'
end
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
context 'when user does not have push access' do
it_behaves_like 'LFS http 401 response'
end
end
end
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
describe 'unsupported' do
let ( :body ) { request_body ( 'other' , sample_object ) }
2017-09-19 03:44:58 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 404 response'
end
end
2017-09-19 03:44:58 -04:00
2020-12-08 13:10:08 -05:00
describe 'when handling LFS batch request on a read-only GitLab instance' do
subject { post_lfs_json ( batch_url ( project ) , body , headers ) }
2017-09-19 03:44:58 -04:00
2020-12-08 13:10:08 -05:00
before do
2021-08-02 05:10:09 -04:00
allow ( Gitlab :: Database ) . to receive ( :read_only? ) { true }
2017-09-19 03:44:58 -04:00
2020-12-08 13:10:08 -05:00
project . add_maintainer ( user )
2017-09-19 03:44:58 -04:00
2020-12-08 13:10:08 -05:00
subject
2016-07-15 08:01:34 -04:00
end
2020-12-08 13:10:08 -05:00
context 'when downloading' do
let ( :body ) { download_body ( sample_object ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 response'
2016-07-15 08:01:34 -04:00
end
2020-12-08 13:10:08 -05:00
context 'when uploading' do
let ( :body ) { upload_body ( sample_object ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
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
2016-07-15 08:01:34 -04:00
end
end
2020-12-08 13:10:08 -05:00
describe 'when pushing a LFS object' do
let ( :include_workhorse_jwt_header ) { true }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
shared_examples 'unauthorized' do
context 'and request is sent by gitlab-workhorse to authorize the request' do
before do
put_authorize
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 401 response'
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'and request is sent by gitlab-workhorse to finalize the upload' do
before do
put_finalize
end
2016-07-20 12:41:26 -04:00
2020-12-08 13:10:08 -05:00
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
2016-07-20 12:41:26 -04:00
end
2020-12-08 13:10:08 -05:00
shared_examples 'forbidden' do
context 'and request is sent by gitlab-workhorse to authorize the request' do
before do
put_authorize
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 403 response'
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
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'
2016-07-15 08:01:34 -04:00
end
2020-12-08 13:10:08 -05:00
context 'and request is sent with a malformed headers' do
before do
put_finalize ( '/etc/passwd' )
2016-08-19 13:10:41 -04:00
end
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 403 response'
2016-08-19 13:10:41 -04:00
end
2020-12-08 13:10:08 -05:00
end
2016-08-19 13:10:41 -04:00
2020-12-08 13:10:08 -05:00
describe 'to one project' do
describe 'when user is authenticated' do
describe 'when user has push access to the project' do
2018-03-23 06:07:22 -04:00
before do
2020-12-08 13:10:08 -05:00
project . add_developer ( user )
2018-03-23 06:07:22 -04:00
end
2020-12-08 13:10:08 -05:00
context 'and the request bypassed workhorse' do
it 'raises an exception' do
expect { put_authorize ( verified : false ) } . to raise_error JWT :: DecodeError
2018-03-23 06:07:22 -04:00
end
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'and request is sent by gitlab-workhorse to authorize the request' do
shared_examples 'a valid response' do
before do
put_authorize
end
2016-08-19 13:10:41 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 workhorse response'
2018-03-23 06:07:22 -04:00
end
2020-12-08 13:10:08 -05:00
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
2018-03-23 06:07:22 -04:00
end
end
2020-12-08 13:10:08 -05:00
context 'when using local storage' do
it_behaves_like 'a local file'
2018-03-23 06:07:22 -04:00
end
2020-12-08 13:10:08 -05:00
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
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
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' )
2021-01-22 16:09:10 -05:00
expect ( json_response [ 'RemoteObject' ] ) . to have_key ( 'MultipartUpload' )
2020-12-08 13:10:08 -05:00
expect ( json_response [ 'LfsOid' ] ) . to eq ( sample_oid )
expect ( json_response [ 'LfsSize' ] ) . to eq ( sample_size )
end
end
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'when direct upload is disabled' do
before do
stub_lfs_object_storage ( enabled : true , direct_upload : false )
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'a local file'
end
end
end
2016-08-10 11:40:20 -04:00
2020-12-08 13:10:08 -05:00
context 'and request is sent by gitlab-workhorse to finalize the upload' do
before do
put_finalize
end
Verify that LFS upload requests are genuine
LFS uploads are handled in concert by workhorse and rails. In normal
use, workhorse:
* Authorizes the request with rails (upload_authorize)
* Handles the upload of the file to a tempfile - disk or object storage
* Validates the file size and contents
* Hands off to rails to complete the upload (upload_finalize)
In `upload_finalize`, the LFS object is linked to the project. As LFS
objects are deduplicated across all projects, it may already exist. If
not, the temporary file is copied to the correct place, and will be
used by all future LFS objects with the same OID.
Workhorse uses the Content-Type of the request to decide to follow this
routine, as the URLs are ambiguous. If the Content-Type is anything but
"application/octet-stream", the request is proxied directly to rails,
on the assumption that this is a normal file edit request. If it's an
actual LFS request with a different content-type, however, it is routed
to the Rails `upload_finalize` action, which treats it as an LFS upload
just as it would a workhorse-modified request.
The outcome is that users can upload LFS objects that don't match the
declared size or OID. They can also create links to LFS objects they
don't really own, allowing them to read the contents of files if they
know just the size or OID.
We can close this hole by requiring requests to `upload_finalize` to be
sourced from Workhorse. The mechanism to do this already exists.
2019-01-08 12:57:58 -05:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 response'
2017-09-07 17:27:04 -04:00
2020-12-08 13:10:08 -05:00
it 'LFS object is linked to the project' do
expect ( lfs_object . projects . pluck ( :id ) ) . to include ( project . id )
end
end
2017-09-07 17:27:04 -04:00
2020-12-08 13:10:08 -05:00
context 'and request to finalize the upload is not sent by gitlab-workhorse' do
it 'fails with a JWT decode error' do
2022-05-04 05:09:02 -04:00
expect { put_finalize ( verified : false ) } . to raise_error ( JWT :: DecodeError )
end
end
context 'and the uploaded file is invalid' do
where ( :size , :sha256 , :status ) do
nil | nil | :ok # Test setup sanity check
0 | nil | :bad_request
nil | 'a' * 64 | :bad_request
end
with_them do
it 'validates the upload size and SHA256' do
put_finalize ( size : size , sha256 : sha256 )
expect ( response ) . to have_gitlab_http_status ( status )
end
2020-12-08 13:10:08 -05:00
end
2017-09-07 17:27:04 -04:00
end
2020-12-08 13:10:08 -05:00
context 'and workhorse requests upload finalize for a new LFS object' do
before do
lfs_object . destroy!
2018-03-23 06:07:22 -04:00
end
2020-12-08 13:10:08 -05:00
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
2019-10-18 07:11:44 -04:00
end
2020-12-08 13:10:08 -05:00
context 'with object storage enabled' do
context 'and direct upload enabled' do
let! ( :fog_connection ) do
stub_lfs_object_storage ( direct_upload : true )
end
let ( :tmp_object ) do
fog_connection . directories . new ( key : 'lfs-objects' ) . files . create ( # rubocop: disable Rails/SaveBang
key : 'tmp/uploads/12312300' ,
2022-05-04 05:09:02 -04:00
body : 'x' * sample_size
2020-12-08 13:10:08 -05:00
)
2018-03-23 06:07:22 -04:00
end
2020-12-08 13:10:08 -05:00
[ '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
2018-03-23 06:07:22 -04:00
2020-12-08 13:10:08 -05:00
expect ( response ) . to have_gitlab_http_status ( :forbidden )
end
end
2018-03-23 06:07:22 -04:00
end
2020-12-08 13:10:08 -05:00
context 'with valid remote_id' do
subject do
put_finalize ( remote_object : tmp_object , args : {
'file.remote_id' = > '12312300' ,
'file.name' = > 'name'
} )
end
2018-03-23 06:07:22 -04:00
2020-12-08 13:10:08 -05:00
it 'responds with status 200' do
subject
2018-03-23 06:07:22 -04:00
2020-12-08 13:10:08 -05:00
expect ( response ) . to have_gitlab_http_status ( :ok )
2019-10-18 07:11:44 -04:00
2020-12-08 13:10:08 -05:00
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
2018-03-23 06:07:22 -04:00
2020-12-08 13:10:08 -05:00
it 'have valid file' do
subject
2018-03-23 06:07:22 -04:00
2020-12-08 13:10:08 -05:00
expect ( LfsObject . last . file_store ) . to eq ( ObjectStorage :: Store :: REMOTE )
expect ( LfsObject . last . file ) . to be_exists
end
end
2018-03-23 06:07:22 -04:00
end
2020-12-08 13:10:08 -05:00
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 ) )
2018-03-23 06:07:22 -04:00
2020-12-08 13:10:08 -05:00
put_finalize ( with_tempfile : true )
end
2018-03-23 06:07:22 -04:00
end
end
2017-09-07 17:27:04 -04:00
end
2020-12-08 13:10:08 -05:00
context 'without the lfs object' do
2018-03-23 06:07:22 -04:00
before do
2020-12-08 13:10:08 -05:00
lfs_object . destroy!
2018-03-23 06:07:22 -04:00
end
2017-09-07 17:27:04 -04:00
2020-12-08 13:10:08 -05:00
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 }
it 'rejects the request' do
put_finalize ( with_tempfile : true )
2018-03-23 06:07:22 -04:00
2020-12-08 13:10:08 -05:00
expect ( response ) . to have_gitlab_http_status ( :unprocessable_entity )
end
2018-03-23 06:07:22 -04:00
end
2017-09-07 17:27:04 -04:00
end
end
2020-12-08 13:10:08 -05:00
describe 'and user does not have push access' do
before do
project . add_reporter ( user )
end
2016-08-10 11:40:20 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'forbidden'
2020-08-05 17:09:40 -04:00
end
2020-12-08 13:10:08 -05:00
end
2020-08-05 17:09:40 -04:00
2020-12-08 13:10:08 -05:00
context 'when build is authorized' do
let ( :authorization ) { authorize_ci_project }
let ( :pipeline ) { create ( :ci_empty_pipeline , project : project ) }
2020-08-05 17:09:40 -04:00
2020-12-08 13:10:08 -05:00
context 'build has an user' do
let ( :build ) { create ( :ci_build , :running , pipeline : pipeline , user : user ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'tries to push to own project' do
before do
project . add_developer ( user )
put_authorize
end
2016-07-20 12:41:26 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 403 response'
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'tries to push to other project' do
let ( :pipeline ) { create ( :ci_empty_pipeline , project : other_project ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
before do
put_authorize
end
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 404 response'
end
2016-09-16 05:06:57 -04:00
end
2020-12-08 13:10:08 -05:00
context 'does not have user' do
let ( :build ) { create ( :ci_build , :running , pipeline : pipeline ) }
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
before do
put_authorize
end
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 403 response'
2016-09-16 05:06:57 -04:00
end
end
2020-12-08 13:10:08 -05:00
describe 'when using a user key (LFSToken)' do
let ( :authorization ) { authorize_user_key }
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
context 'when user allowed' do
before do
project . add_developer ( user )
put_authorize
end
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 workhorse response'
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
context 'when user password is expired' do
2021-09-30 14:11:31 -04:00
let_it_be ( :user ) { create ( :user , password_expires_at : 1 . minute . ago ) }
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 401 response'
end
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
context 'when user is blocked' do
let_it_be ( :user ) { create ( :user , :blocked ) }
it_behaves_like 'LFS http 401 response'
end
end
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
context 'when user not allowed' do
before do
put_authorize
end
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 404 response'
end
2019-09-27 20:06:20 -04:00
end
2020-12-08 13:10:08 -05:00
context 'for unauthenticated' do
let ( :authorization ) { nil }
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'unauthorized'
2019-09-27 20:06:20 -04:00
end
end
2020-12-08 13:10:08 -05:00
describe 'to a forked project' do
let_it_be ( :upstream_project ) { create ( :project , :public ) }
let_it_be ( :project_owner ) { create ( :user ) }
2021-06-28 23:07:32 -04:00
2020-12-08 13:10:08 -05:00
let ( :project ) { fork_project ( upstream_project , project_owner ) }
2019-09-27 20:06:20 -04:00
2020-12-08 13:10:08 -05:00
describe 'when user is authenticated' do
describe 'when user has push access to the project' do
before do
project . add_developer ( user )
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'and request is sent by gitlab-workhorse to authorize the request' do
before do
put_authorize
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 workhorse response'
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
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
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'and request is sent by gitlab-workhorse to finalize the upload' do
before do
put_finalize
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 response'
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it 'LFS object is linked to the forked project' do
expect ( lfs_object . projects . pluck ( :id ) ) . to include ( project . id )
end
end
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
describe 'and user does not have push access' do
it_behaves_like 'forbidden'
2016-07-15 08:01:34 -04:00
end
end
2020-12-08 13:10:08 -05:00
context 'when build is authorized' do
let ( :authorization ) { authorize_ci_project }
let ( :pipeline ) { create ( :ci_empty_pipeline , project : project ) }
2016-07-15 08:01:34 -04:00
before do
2020-12-08 13:10:08 -05:00
put_authorize
2016-07-15 08:01:34 -04:00
end
2020-12-08 13:10:08 -05:00
context 'build has an user' do
let ( :build ) { create ( :ci_build , :running , pipeline : pipeline , user : user ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'tries to push to own project' do
it_behaves_like 'LFS http 403 response'
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'tries to push to other project' do
let ( :pipeline ) { create ( :ci_empty_pipeline , project : other_project ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
# I'm not sure what this tests that is different from the previous test
2021-07-16 08:10:21 -04:00
it_behaves_like 'LFS http 403 response'
2020-12-08 13:10:08 -05:00
end
end
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
context 'does not have user' do
let ( :build ) { create ( :ci_build , :running , pipeline : pipeline ) }
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 403 response'
end
2016-09-16 05:06:57 -04:00
end
2020-12-08 13:10:08 -05:00
context 'for unauthenticated' do
let ( :authorization ) { nil }
2016-09-16 05:06:57 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'unauthorized'
2016-09-16 05:06:57 -04:00
end
2020-12-08 13:10:08 -05:00
describe 'and second project not related to fork or a source project' do
let_it_be ( :second_project ) { create ( :project ) }
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
before do
second_project . add_maintainer ( user )
upstream_project . lfs_objects << lfs_object
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
context 'when pushing the same LFS object to the second project' do
before do
2022-05-04 05:09:02 -04:00
put_finalize ( with_tempfile : true , to_project : second_project )
2020-12-08 13:10:08 -05:00
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
it_behaves_like 'LFS http 200 response'
Verify that LFS upload requests are genuine
LFS uploads are handled in concert by workhorse and rails. In normal
use, workhorse:
* Authorizes the request with rails (upload_authorize)
* Handles the upload of the file to a tempfile - disk or object storage
* Validates the file size and contents
* Hands off to rails to complete the upload (upload_finalize)
In `upload_finalize`, the LFS object is linked to the project. As LFS
objects are deduplicated across all projects, it may already exist. If
not, the temporary file is copied to the correct place, and will be
used by all future LFS objects with the same OID.
Workhorse uses the Content-Type of the request to decide to follow this
routine, as the URLs are ambiguous. If the Content-Type is anything but
"application/octet-stream", the request is proxied directly to rails,
on the assumption that this is a normal file edit request. If it's an
actual LFS request with a different content-type, however, it is routed
to the Rails `upload_finalize` action, which treats it as an LFS upload
just as it would a workhorse-modified request.
The outcome is that users can upload LFS objects that don't match the
declared size or OID. They can also create links to LFS objects they
don't really own, allowing them to read the contents of files if they
know just the size or OID.
We can close this hole by requiring requests to `upload_finalize` to be
sourced from Workhorse. The mechanism to do this already exists.
2019-01-08 12:57:58 -05:00
2020-12-08 13:10:08 -05:00
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
2016-07-15 08:01:34 -04:00
end
2020-12-08 13:10:08 -05:00
end
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
def put_authorize ( verified : true )
authorize_headers = headers
authorize_headers . merge! ( workhorse_internal_api_request_header ) if verified
2016-07-15 08:01:34 -04:00
2020-12-08 13:10:08 -05:00
put authorize_url ( project , sample_oid , sample_size ) , params : { } , headers : authorize_headers
2016-07-15 08:01:34 -04:00
end
2019-10-18 07:11:44 -04:00
end
2020-11-10 19:08:58 -05:00
end
end
context 'with project wikis' do
it_behaves_like 'LFS http requests' do
let ( :container ) { create ( :project_wiki , :empty_repo , project : project ) }
let ( :authorize_guest ) { project . add_guest ( user ) }
let ( :authorize_download ) { project . add_reporter ( user ) }
let ( :authorize_upload ) { project . add_developer ( user ) }
end
end
context 'with snippets' do
# LFS is not supported on snippets, so we override the shared examples
# to expect 404 responses instead.
[
'LFS http 200 response' ,
'LFS http 200 blob response' ,
'LFS http 403 response'
] . each do | examples |
shared_examples_for ( examples ) { it_behaves_like 'LFS http 404 response' }
end
context 'with project snippets' do
it_behaves_like 'LFS http requests' do
let ( :container ) { create ( :project_snippet , :empty_repo , project : project ) }
let ( :authorize_guest ) { project . add_guest ( user ) }
let ( :authorize_download ) { project . add_reporter ( user ) }
let ( :authorize_upload ) { project . add_developer ( user ) }
end
end
context 'with personal snippets' do
it_behaves_like 'LFS http requests' do
let ( :container ) { create ( :personal_snippet , :empty_repo ) }
let ( :authorize_upload ) { container . update! ( author : user ) }
end
end
end
2016-07-15 08:01:34 -04:00
end