252 lines
8.6 KiB
Ruby
252 lines
8.6 KiB
Ruby
|
# frozen_string_literal: true
|
||
|
module API
|
||
|
class MavenPackages < Grape::API::Instance
|
||
|
MAVEN_ENDPOINT_REQUIREMENTS = {
|
||
|
file_name: API::NO_SLASH_URL_PART_REGEX
|
||
|
}.freeze
|
||
|
|
||
|
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
|
||
|
|
||
|
helpers do
|
||
|
def extract_format(file_name)
|
||
|
name, _, format = file_name.rpartition('.')
|
||
|
|
||
|
if %w(md5 sha1).include?(format)
|
||
|
[name, format]
|
||
|
else
|
||
|
[file_name, format]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def verify_package_file(package_file, uploaded_file)
|
||
|
stored_sha1 = Digest::SHA256.hexdigest(package_file.file_sha1)
|
||
|
expected_sha1 = uploaded_file.sha256
|
||
|
|
||
|
if stored_sha1 == expected_sha1
|
||
|
no_content!
|
||
|
else
|
||
|
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
|
||
|
|
||
|
def present_carrierwave_file_with_head_support!(file, supports_direct_download: true)
|
||
|
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
|
||
|
end
|
||
|
|
||
|
desc 'Download the maven package file at instance level' do
|
||
|
detail 'This feature was introduced in GitLab 11.6'
|
||
|
end
|
||
|
params do
|
||
|
requires :path, type: String, desc: 'Package path'
|
||
|
requires :file_name, type: String, desc: 'Package file name'
|
||
|
end
|
||
|
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
|
||
|
get 'packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
|
||
|
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)
|
||
|
|
||
|
package = ::Packages::Maven::PackageFinder
|
||
|
.new(params[:path], current_user, project: project).execute!
|
||
|
|
||
|
package_file = ::Packages::PackageFileFinder
|
||
|
.new(package, file_name).execute!
|
||
|
|
||
|
case format
|
||
|
when 'md5'
|
||
|
package_file.file_md5
|
||
|
when 'sha1'
|
||
|
package_file.file_sha1
|
||
|
else
|
||
|
track_event('pull_package') if jar_file?(format)
|
||
|
present_carrierwave_file_with_head_support!(package_file.file)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
desc 'Download the maven package file at a group level' do
|
||
|
detail 'This feature was introduced in GitLab 11.7'
|
||
|
end
|
||
|
params do
|
||
|
requires :id, type: String, desc: 'The ID of a group'
|
||
|
end
|
||
|
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
|
||
|
params do
|
||
|
requires :path, type: String, desc: 'Package path'
|
||
|
requires :file_name, type: String, desc: 'Package file name'
|
||
|
end
|
||
|
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
|
||
|
get ':id/-/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
|
||
|
file_name, format = extract_format(params[:file_name])
|
||
|
|
||
|
group = find_group(params[:id])
|
||
|
|
||
|
not_found!('Group') unless can?(current_user, :read_group, group)
|
||
|
|
||
|
package = ::Packages::Maven::PackageFinder
|
||
|
.new(params[:path], current_user, group: group).execute!
|
||
|
|
||
|
authorize_read_package!(package.project)
|
||
|
|
||
|
package_file = ::Packages::PackageFileFinder
|
||
|
.new(package, file_name).execute!
|
||
|
|
||
|
case format
|
||
|
when 'md5'
|
||
|
package_file.file_md5
|
||
|
when 'sha1'
|
||
|
package_file.file_sha1
|
||
|
else
|
||
|
track_event('pull_package') if jar_file?(format)
|
||
|
|
||
|
present_carrierwave_file_with_head_support!(package_file.file)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
params do
|
||
|
requires :id, type: String, desc: 'The ID of a project'
|
||
|
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'
|
||
|
end
|
||
|
params do
|
||
|
requires :path, type: String, desc: 'Package path'
|
||
|
requires :file_name, type: String, desc: 'Package file name'
|
||
|
end
|
||
|
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
|
||
|
get ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
|
||
|
authorize_read_package!(user_project)
|
||
|
|
||
|
file_name, format = extract_format(params[:file_name])
|
||
|
|
||
|
package = ::Packages::Maven::PackageFinder
|
||
|
.new(params[:path], current_user, project: user_project).execute!
|
||
|
|
||
|
package_file = ::Packages::PackageFileFinder
|
||
|
.new(package, file_name).execute!
|
||
|
|
||
|
case format
|
||
|
when 'md5'
|
||
|
package_file.file_md5
|
||
|
when 'sha1'
|
||
|
package_file.file_sha1
|
||
|
else
|
||
|
track_event('pull_package') if jar_file?(format)
|
||
|
|
||
|
present_carrierwave_file_with_head_support!(package_file.file)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
desc 'Workhorse authorize the maven package file upload' do
|
||
|
detail 'This feature was introduced in GitLab 11.3'
|
||
|
end
|
||
|
params do
|
||
|
requires :path, type: String, desc: 'Package path'
|
||
|
requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.maven_file_name_regex
|
||
|
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
|
||
|
::Packages::PackageFileUploader.workhorse_authorize(has_length: true)
|
||
|
end
|
||
|
|
||
|
desc 'Upload the maven package file' do
|
||
|
detail 'This feature was introduced in GitLab 11.3'
|
||
|
end
|
||
|
params do
|
||
|
requires :path, type: String, desc: 'Package path'
|
||
|
requires :file_name, type: String, desc: 'Package file name', regexp: Gitlab::Regex.maven_file_name_regex
|
||
|
requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)'
|
||
|
end
|
||
|
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
|
||
|
put ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
|
||
|
authorize_upload!
|
||
|
|
||
|
file_name, format = extract_format(params[:file_name])
|
||
|
|
||
|
package = ::Packages::Maven::FindOrCreatePackageService
|
||
|
.new(user_project, current_user, params.merge(build: current_authenticated_job)).execute
|
||
|
|
||
|
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'
|
||
|
nil
|
||
|
else
|
||
|
track_event('push_package') if jar_file?(format)
|
||
|
|
||
|
file_params = {
|
||
|
file: params[:file],
|
||
|
size: params['file.size'],
|
||
|
file_name: file_name,
|
||
|
file_type: params['file.type'],
|
||
|
file_sha1: params['file.sha1'],
|
||
|
file_md5: params['file.md5']
|
||
|
}
|
||
|
|
||
|
::Packages::CreatePackageFileService.new(package, file_params).execute
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|