summaryrefslogtreecommitdiff
path: root/lib/api/maven_packages.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/api/maven_packages.rb')
-rw-r--r--lib/api/maven_packages.rb251
1 files changed, 251 insertions, 0 deletions
diff --git a/lib/api/maven_packages.rb b/lib/api/maven_packages.rb
new file mode 100644
index 00000000000..32a45c59cfa
--- /dev/null
+++ b/lib/api/maven_packages.rb
@@ -0,0 +1,251 @@
+# 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