2020-07-28 23:09:34 +05:30
# frozen_string_literal: true
module API
2021-01-03 14:25:43 +05:30
class MavenPackages < :: API :: Base
2020-07-28 23:09:34 +05:30
MAVEN_ENDPOINT_REQUIREMENTS = {
file_name : API :: NO_SLASH_URL_PART_REGEX
} . freeze
2021-01-29 00:20:46 +05:30
feature_category :package_registry
2022-07-16 23:28:13 +05:30
urgency :low
2021-01-29 00:20:46 +05:30
2020-07-28 23:09:34 +05:30
content_type :md5 , 'text/plain'
content_type :sha1 , 'text/plain'
content_type :binary , 'application/octet-stream'
rescue_from ActiveRecord :: RecordInvalid do | e |
render_api_error! ( e . message , 400 )
end
before do
require_packages_enabled!
authenticate_non_get!
end
helpers :: API :: Helpers :: PackagesHelpers
2022-10-11 01:57:18 +05:30
helpers :: API :: Helpers :: Packages :: DependencyProxyHelpers
2020-07-28 23:09:34 +05:30
helpers do
2021-04-29 21:17:54 +05:30
def path_exists? ( path )
return false if path . blank?
Packages :: Maven :: Metadatum . with_path ( path )
. exists?
end
2020-07-28 23:09:34 +05:30
def extract_format ( file_name )
name , _ , format = file_name . rpartition ( '.' )
if %w( md5 sha1 ) . include? ( format )
2022-08-13 15:12:31 +05:30
unprocessable_entity! if Gitlab :: FIPS . enabled? && format == 'md5'
2020-07-28 23:09:34 +05:30
[ name , format ]
else
[ file_name , format ]
end
end
2022-08-27 11:52:29 +05:30
# The sha verification done by the maven api is between:
# - the sha256 set by workhorse helpers
# - the sha256 of the sha1 of the uploaded package file
2020-07-28 23:09:34 +05:30
def verify_package_file ( package_file , uploaded_file )
2021-01-03 14:25:43 +05:30
stored_sha256 = Digest :: SHA256 . hexdigest ( package_file . file_sha1 )
expected_sha256 = uploaded_file . sha256
2020-07-28 23:09:34 +05:30
2021-01-03 14:25:43 +05:30
if stored_sha256 == expected_sha256
2020-07-28 23:09:34 +05:30
no_content!
else
2022-08-27 11:52:29 +05:30
# Track sha1 conflicts.
# See https://gitlab.com/gitlab-org/gitlab/-/issues/367356
Gitlab :: ErrorTracking . log_exception (
ArgumentError . new ,
message : 'maven package file sha1 conflict' ,
stored_sha1 : package_file . file_sha1 ,
received_sha256 : uploaded_file . sha256 ,
sha256_hexdigest_of_stored_sha1 : stored_sha256
)
2020-07-28 23:09:34 +05:30
conflict!
end
end
def find_project_by_path ( path )
project_path = path . rpartition ( '/' ) . first
Project . find_by_full_path ( project_path )
end
def jar_file? ( format )
format == 'jar'
end
2022-10-11 01:57:18 +05:30
def present_carrierwave_file_with_head_support! ( package_file , supports_direct_download : true )
package_file . package . touch_last_downloaded_at
file = package_file . file
2020-07-28 23:09:34 +05:30
if head_request_on_aws_file? ( file , supports_direct_download ) && ! file . file_storage?
return redirect ( signed_head_url ( file ) )
end
present_carrierwave_file! ( file , supports_direct_download : supports_direct_download )
end
def signed_head_url ( file )
fog_storage = :: Fog :: Storage . new ( file . fog_credentials )
fog_dir = fog_storage . directories . new ( key : file . fog_directory )
fog_file = fog_dir . files . new ( key : file . path )
expire_at = :: Fog :: Time . now + file . fog_authenticated_url_expiration
fog_file . collection . head_url ( fog_file . key , expire_at )
end
def head_request_on_aws_file? ( file , supports_direct_download )
Gitlab . config . packages . object_store . enabled &&
supports_direct_download &&
file . class . direct_download_enabled? &&
request . head? &&
file . fog_credentials [ :provider ] == 'AWS'
end
2021-04-29 21:17:54 +05:30
def fetch_package ( file_name : , project : nil , group : nil )
2021-06-08 01:23:25 +05:30
order_by_package_file = file_name . include? ( :: Packages :: Maven :: Metadata . filename ) &&
2023-03-04 22:38:38 +05:30
! params [ :path ] . include? ( :: Packages :: Maven :: FindOrCreatePackageService :: SNAPSHOT_TERM )
2021-04-29 21:17:54 +05:30
:: Packages :: Maven :: PackageFinder . new (
current_user ,
2021-06-08 01:23:25 +05:30
project || group ,
path : params [ :path ] ,
2021-04-29 21:17:54 +05:30
order_by_package_file : order_by_package_file
2022-10-11 01:57:18 +05:30
) . execute
end
def find_and_present_package_file ( package , file_name , format , params )
project = package & . project
package_file = nil
package_file = :: Packages :: PackageFileFinder . new ( package , file_name ) . execute if package
no_package_found = package_file ? false : true
2022-11-25 23:54:43 +05:30
redirect_registry_request (
forward_to_registry : no_package_found ,
package_type : :maven ,
target : params [ :target ] ,
path : params [ :path ] ,
file_name : params [ :file_name ]
) do
2022-10-11 01:57:18 +05:30
not_found! ( 'Package' ) if no_package_found
case format
when 'md5'
package_file . file_md5
when 'sha1'
package_file . file_sha1
else
track_package_event ( 'pull_package' , :maven , project : project , namespace : project & . namespace ) if jar_file? ( format )
present_carrierwave_file_with_head_support! ( package_file )
end
end
2021-04-29 21:17:54 +05:30
end
2020-07-28 23:09:34 +05:30
end
desc 'Download the maven package file at instance level' do
detail 'This feature was introduced in GitLab 11.6'
2023-03-04 22:38:38 +05:30
success code : 200
failure [
{ code : 401 , message : 'Unauthorized' } ,
{ code : 403 , message : 'Forbidden' } ,
{ code : 404 , message : 'Not Found' }
]
tags %w[ maven_packages ]
2020-07-28 23:09:34 +05:30
end
params do
2023-03-04 22:38:38 +05:30
requires :path , type : String , desc : 'Package path' , documentation : { example : 'foo/bar/mypkg/1.0-SNAPSHOT' }
requires :file_name , type : String , desc : 'Package file name' , documentation : { example : 'mypkg-1.0-SNAPSHOT.jar' }
2020-07-28 23:09:34 +05:30
end
route_setting :authentication , job_token_allowed : true , deploy_token_allowed : true
get 'packages/maven/*path/:file_name' , requirements : MAVEN_ENDPOINT_REQUIREMENTS do
2021-04-29 21:17:54 +05:30
# return a similar failure to authorize_read_package!(project)
2022-08-13 15:12:31 +05:30
2021-04-29 21:17:54 +05:30
forbidden! unless path_exists? ( params [ :path ] )
2020-07-28 23:09:34 +05:30
file_name , format = extract_format ( params [ :file_name ] )
# To avoid name collision we require project path and project package be the same.
# For packages that have different name from the project we should use
# the endpoint that includes project id
project = find_project_by_path ( params [ :path ] )
authorize_read_package! ( project )
2021-04-29 21:17:54 +05:30
package = fetch_package ( file_name : file_name , project : project )
2020-07-28 23:09:34 +05:30
2022-10-11 01:57:18 +05:30
not_found! ( 'Package' ) unless package
2020-07-28 23:09:34 +05:30
package_file = :: Packages :: PackageFileFinder
. new ( package , file_name ) . execute!
case format
when 'md5'
package_file . file_md5
when 'sha1'
package_file . file_sha1
else
2021-09-04 01:27:46 +05:30
track_package_event ( 'pull_package' , :maven , project : project , namespace : project . namespace ) if jar_file? ( format )
2023-05-27 22:25:52 +05:30
2022-10-11 01:57:18 +05:30
present_carrierwave_file_with_head_support! ( package_file )
2020-07-28 23:09:34 +05:30
end
end
desc 'Download the maven package file at a group level' do
detail 'This feature was introduced in GitLab 11.7'
2023-03-04 22:38:38 +05:30
success [
{ code : 200 } ,
{ code : 302 }
]
failure [
{ code : 401 , message : 'Unauthorized' } ,
{ code : 403 , message : 'Forbidden' } ,
{ code : 404 , message : 'Not Found' }
]
tags %w[ maven_packages ]
2020-07-28 23:09:34 +05:30
end
params do
2023-03-04 22:38:38 +05:30
requires :id , types : [ String , Integer ] , desc : 'The ID or URL-encoded path of the group'
2020-07-28 23:09:34 +05:30
end
resource :groups , requirements : API :: NAMESPACE_OR_PROJECT_REQUIREMENTS do
params do
2023-03-04 22:38:38 +05:30
requires :path , type : String , desc : 'Package path' , documentation : { example : 'foo/bar/mypkg/1.0-SNAPSHOT' }
requires :file_name , type : String , desc : 'Package file name' , documentation : { example : 'mypkg-1.0-SNAPSHOT.jar' }
2020-07-28 23:09:34 +05:30
end
route_setting :authentication , job_token_allowed : true , deploy_token_allowed : true
get ':id/-/packages/maven/*path/:file_name' , requirements : MAVEN_ENDPOINT_REQUIREMENTS do
2021-04-29 21:17:54 +05:30
# return a similar failure to group = find_group(params[:id])
2020-07-28 23:09:34 +05:30
group = find_group ( params [ :id ] )
2022-10-11 01:57:18 +05:30
if Feature . disabled? ( :maven_central_request_forwarding , group & . root_ancestor )
not_found! ( 'Group' ) unless path_exists? ( params [ :path ] )
end
2020-07-28 23:09:34 +05:30
not_found! ( 'Group' ) unless can? ( current_user , :read_group , group )
2022-10-11 01:57:18 +05:30
file_name , format = extract_format ( params [ :file_name ] )
2021-04-29 21:17:54 +05:30
package = fetch_package ( file_name : file_name , group : group )
2020-07-28 23:09:34 +05:30
2022-10-11 01:57:18 +05:30
authorize_read_package! ( package . project ) if package
2020-07-28 23:09:34 +05:30
2022-10-11 01:57:18 +05:30
find_and_present_package_file ( package , file_name , format , params . merge ( target : group ) )
2020-07-28 23:09:34 +05:30
end
end
params do
2023-01-13 00:05:48 +05:30
requires :id , types : [ String , Integer ] , desc : 'The ID or URL-encoded path of the project'
2020-07-28 23:09:34 +05:30
end
resource :projects , requirements : API :: NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Download the maven package file' do
detail 'This feature was introduced in GitLab 11.3'
2023-03-04 22:38:38 +05:30
success [
{ code : 200 } ,
{ code : 302 }
]
failure [
{ code : 401 , message : 'Unauthorized' } ,
{ code : 403 , message : 'Forbidden' } ,
{ code : 404 , message : 'Not Found' }
]
tags %w[ maven_packages ]
2020-07-28 23:09:34 +05:30
end
params do
2023-03-04 22:38:38 +05:30
requires :path , type : String , desc : 'Package path' , documentation : { example : 'foo/bar/mypkg/1.0-SNAPSHOT' }
requires :file_name , type : String , desc : 'Package file name' , documentation : { example : 'mypkg-1.0-SNAPSHOT.jar' }
2020-07-28 23:09:34 +05:30
end
route_setting :authentication , job_token_allowed : true , deploy_token_allowed : true
get ':id/packages/maven/*path/:file_name' , requirements : MAVEN_ENDPOINT_REQUIREMENTS do
2023-01-13 00:05:48 +05:30
project = user_project ( action : :read_package )
2021-04-29 21:17:54 +05:30
# return a similar failure to user_project
2023-01-13 00:05:48 +05:30
unless Feature . enabled? ( :maven_central_request_forwarding , project & . root_ancestor )
2022-10-11 01:57:18 +05:30
not_found! ( 'Project' ) unless path_exists? ( params [ :path ] )
end
2021-04-29 21:17:54 +05:30
2023-01-13 00:05:48 +05:30
authorize_read_package! ( project )
2020-07-28 23:09:34 +05:30
file_name , format = extract_format ( params [ :file_name ] )
2023-01-13 00:05:48 +05:30
package = fetch_package ( file_name : file_name , project : project )
2020-07-28 23:09:34 +05:30
2023-01-13 00:05:48 +05:30
find_and_present_package_file ( package , file_name , format , params . merge ( target : project ) )
2020-07-28 23:09:34 +05:30
end
desc 'Workhorse authorize the maven package file upload' do
detail 'This feature was introduced in GitLab 11.3'
2023-03-04 22:38:38 +05:30
success code : 200
failure [
{ code : 400 , message : 'Bad Request' } ,
{ code : 401 , message : 'Unauthorized' } ,
{ code : 403 , message : 'Forbidden' } ,
{ code : 404 , message : 'Not Found' }
]
tags %w[ maven_packages ]
2020-07-28 23:09:34 +05:30
end
params do
2023-03-04 22:38:38 +05:30
requires :path , type : String , desc : 'Package path' , documentation : { example : 'foo/bar/mypkg/1.0-SNAPSHOT' }
requires :file_name , type : String , desc : 'Package file name' , regexp : Gitlab :: Regex . maven_file_name_regex , documentation : { example : 'mypkg-1.0-SNAPSHOT.pom' }
2020-07-28 23:09:34 +05:30
end
route_setting :authentication , job_token_allowed : true , deploy_token_allowed : true
put ':id/packages/maven/*path/:file_name/authorize' , requirements : MAVEN_ENDPOINT_REQUIREMENTS do
authorize_upload!
status 200
content_type Gitlab :: Workhorse :: INTERNAL_API_CONTENT_TYPE
2020-11-24 15:15:51 +05:30
:: Packages :: PackageFileUploader . workhorse_authorize ( has_length : true , maximum_size : user_project . actual_limits . maven_max_file_size )
2020-07-28 23:09:34 +05:30
end
desc 'Upload the maven package file' do
detail 'This feature was introduced in GitLab 11.3'
2023-03-04 22:38:38 +05:30
success code : 200
failure [
{ code : 400 , message : 'Bad Request' } ,
{ code : 401 , message : 'Unauthorized' } ,
{ code : 403 , message : 'Forbidden' } ,
{ code : 404 , message : 'Not Found' } ,
{ code : 422 , message : 'Unprocessable Entity' }
]
tags %w[ maven_packages ]
2020-07-28 23:09:34 +05:30
end
params do
2023-03-04 22:38:38 +05:30
requires :path , type : String , desc : 'Package path' , documentation : { example : 'foo/bar/mypkg/1.0-SNAPSHOT' }
requires :file_name , type : String , desc : 'Package file name' , regexp : Gitlab :: Regex . maven_file_name_regex , documentation : { example : 'mypkg-1.0-SNAPSHOT.pom' }
2023-01-13 00:05:48 +05:30
requires :file , type : :: API :: Validations :: Types :: WorkhorseFile , desc : 'The package file to be published (generated by Multipart middleware)' , documentation : { type : 'file' }
2020-07-28 23:09:34 +05:30
end
route_setting :authentication , job_token_allowed : true , deploy_token_allowed : true
put ':id/packages/maven/*path/:file_name' , requirements : MAVEN_ENDPOINT_REQUIREMENTS do
2022-08-13 15:12:31 +05:30
unprocessable_entity! if Gitlab :: FIPS . enabled? && params [ 'file.md5' ]
2020-07-28 23:09:34 +05:30
authorize_upload!
2020-11-24 15:15:51 +05:30
bad_request! ( 'File is too large' ) if user_project . actual_limits . exceeded? ( :maven_max_file_size , params [ :file ] . size )
2020-07-28 23:09:34 +05:30
file_name , format = extract_format ( params [ :file_name ] )
2021-03-08 18:12:59 +05:30
result = :: Packages :: Maven :: FindOrCreatePackageService
2020-07-28 23:09:34 +05:30
. new ( user_project , current_user , params . merge ( build : current_authenticated_job ) ) . execute
2021-03-08 18:12:59 +05:30
bad_request! ( result . errors . first ) if result . error?
package = result . payload [ :package ]
2020-07-28 23:09:34 +05:30
case format
when 'sha1'
# After uploading a file, Maven tries to upload a sha1 and md5 version of it.
# Since we store md5/sha1 in database we simply need to validate our hash
# against one uploaded by Maven. We do this for `sha1` format.
package_file = :: Packages :: PackageFileFinder
. new ( package , file_name ) . execute!
verify_package_file ( package_file , params [ :file ] )
when 'md5'
2021-01-03 14:25:43 +05:30
''
2020-07-28 23:09:34 +05:30
else
file_params = {
2022-08-27 11:52:29 +05:30
file : params [ :file ] ,
size : params [ 'file.size' ] ,
2020-07-28 23:09:34 +05:30
file_name : file_name ,
file_type : params [ 'file.type' ] ,
file_sha1 : params [ 'file.sha1' ] ,
2022-08-27 11:52:29 +05:30
file_md5 : params [ 'file.md5' ]
2020-07-28 23:09:34 +05:30
}
2021-01-29 00:20:46 +05:30
:: Packages :: CreatePackageFileService . new ( package , file_params . merge ( build : current_authenticated_job ) ) . execute
2023-05-27 22:25:52 +05:30
track_package_event ( 'push_package' , :maven , project : user_project , namespace : user_project . namespace ) if jar_file? ( format )
2020-07-28 23:09:34 +05:30
end
end
end
end
end