debian-mirror-gitlab/lib/api/maven_packages.rb

367 lines
14 KiB
Ruby
Raw Normal View History

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
2023-07-09 08:55:56 +05:30
helpers ::API::Helpers::Packages::Maven::BasicAuthHelpers
2020-07-28 23:09:34 +05:30
helpers do
2023-07-09 08:55:56 +05:30
params :path_and_file_name do
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' }
end
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-07-09 08:55:56 +05:30
use :path_and_file_name
2020-07-28 23:09:34 +05:30
end
2023-07-09 08:55:56 +05:30
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true, basic_auth_personal_access_token: true
2020-07-28 23:09:34 +05:30
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-07-09 08:55:56 +05:30
use :path_and_file_name
2020-07-28 23:09:34 +05:30
end
2023-07-09 08:55:56 +05:30
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true, basic_auth_personal_access_token: true
2020-07-28 23:09:34 +05:30
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])
2023-07-09 08:55:56 +05:30
group = find_authorized_group!
2020-07-28 23:09:34 +05:30
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
2023-07-09 08:55:56 +05:30
desc 'Download the maven package file at a project level' do
2020-07-28 23:09:34 +05:30
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-07-09 08:55:56 +05:30
use :path_and_file_name
2020-07-28 23:09:34 +05:30
end
2023-07-09 08:55:56 +05:30
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true, basic_auth_personal_access_token: true
2020-07-28 23:09:34 +05:30
get ':id/packages/maven/*path/:file_name', requirements: MAVEN_ENDPOINT_REQUIREMENTS do
2023-07-09 08:55:56 +05:30
project = authorized_user_project(action: :read_package)
2023-01-13 00:05:48 +05:30
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
2023-07-09 08:55:56 +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])
2023-06-20 00:43:36 +05:30
::Gitlab::Database::LoadBalancing::Session.current.use_primary do
result = ::Packages::Maven::FindOrCreatePackageService
.new(user_project, current_user, params.merge(build: current_authenticated_job)).execute
2020-07-28 23:09:34 +05:30
2023-06-20 00:43:36 +05:30
bad_request!(result.errors.first) if result.error?
2021-03-08 18:12:59 +05:30
2023-06-20 00:43:36 +05:30
package = result.payload[:package]
2021-03-08 18:12:59 +05:30
2023-06-20 00:43:36 +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!
2020-07-28 23:09:34 +05:30
2023-06-20 00:43:36 +05:30
verify_package_file(package_file, params[:file])
when 'md5'
''
else
file_params = {
file: params[:file],
2023-07-09 08:55:56 +05:30
size: params[:file].size,
2023-06-20 00:43:36 +05:30
file_name: file_name,
2023-07-09 08:55:56 +05:30
file_sha1: params[:file].sha1,
file_md5: params[:file].md5
2023-06-20 00:43:36 +05:30
}
::Packages::CreatePackageFileService.new(package, file_params.merge(build: current_authenticated_job)).execute
track_package_event('push_package', :maven, project: user_project, namespace: user_project.namespace) if jar_file?(format)
end
2020-07-28 23:09:34 +05:30
end
end
end
end
end