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

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

330 lines
12 KiB
Ruby
Raw Normal View History

2020-07-28 23:09:34 +05:30
# frozen_string_literal: true
# PyPI Package Manager Client API
#
# These API endpoints are not meant to be consumed directly by users. They are
# called by the PyPI package manager client when users run commands
# like `pip install` or `twine upload`.
module API
2021-01-03 14:25:43 +05:30
class PypiPackages < ::API::Base
2020-07-28 23:09:34 +05:30
helpers ::API::Helpers::PackagesManagerClientsHelpers
helpers ::API::Helpers::RelatedResourcesHelpers
helpers ::API::Helpers::Packages::BasicAuthHelpers
2021-10-27 15:23:28 +05:30
helpers ::API::Helpers::Packages::DependencyProxyHelpers
2020-07-28 23:09:34 +05:30
include ::API::Helpers::Packages::BasicAuthHelpers::Constants
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
default_format :json
rescue_from ArgumentError do |e|
render_api_error!(e.message, 400)
end
rescue_from ActiveRecord::RecordInvalid do |e|
render_api_error!(e.message, 400)
end
before do
require_packages_enabled!
end
2021-09-04 01:27:46 +05:30
helpers do
params :package_download do
2023-03-04 22:38:38 +05:30
requires :file_identifier, type: String, desc: 'The PyPi package file identifier', file_path: true, documentation: { example: 'my.pypi.package-0.0.1.tar.gz' }
requires :sha256, type: String, desc: 'The PyPi package sha256 check sum', documentation: { example: '5y57017232013c8ac80647f4ca153k3726f6cba62d055cd747844ed95b3c65ff' }
2021-09-04 01:27:46 +05:30
end
params :package_name do
2023-03-04 22:38:38 +05:30
requires :package_name, type: String, file_path: true, desc: 'The PyPi package name', documentation: { example: 'my.pypi.package' }
2021-09-04 01:27:46 +05:30
end
2022-07-23 23:45:48 +05:30
def present_simple_index(group_or_project)
authorize_read_package!(group_or_project)
packages = Packages::Pypi::PackagesFinder.new(current_user, group_or_project).execute
presenter = ::Packages::Pypi::SimpleIndexPresenter.new(packages, group_or_project)
present_html(presenter.body)
end
def present_simple_package(group_or_project)
authorize_read_package!(group_or_project)
track_simple_event(group_or_project, 'list_package')
packages = Packages::Pypi::PackagesFinder.new(current_user, group_or_project, { package_name: params[:package_name] }).execute
empty_packages = packages.empty?
2022-11-25 23:54:43 +05:30
redirect_registry_request(
forward_to_registry: empty_packages,
package_type: :pypi,
target: group_or_project,
package_name: params[:package_name]
) do
2022-07-23 23:45:48 +05:30
not_found!('Package') if empty_packages
presenter = ::Packages::Pypi::SimplePackageVersionsPresenter.new(packages, group_or_project)
present_html(presenter.body)
end
end
def track_simple_event(group_or_project, event_name)
if group_or_project.is_a?(Project)
project = group_or_project
namespace = group_or_project.namespace
else
project = nil
namespace = group_or_project
end
track_package_event(event_name, :pypi, project: project, namespace: namespace)
end
def present_html(content)
# Adjusts grape output format
# to be HTML
content_type "text/html; charset=utf-8"
env['api.format'] = :binary
body content
end
2022-09-01 20:07:04 +05:30
def ensure_group!
find_group(params[:id]) || not_found!
find_authorized_group!
end
2023-01-13 00:05:48 +05:30
def project!(action: :read_package)
2022-09-01 20:07:04 +05:30
find_project(params[:id]) || not_found!
2023-01-13 00:05:48 +05:30
authorized_user_project(action: action)
2022-09-01 20:07:04 +05:30
end
2023-03-04 22:38:38 +05:30
def validate_fips!
unprocessable_entity! if declared_params[:sha256_digest].blank?
true
end
2021-09-04 01:27:46 +05:30
end
params do
2023-03-04 22:38:38 +05:30
requires :id, types: [Integer, String], desc: 'The ID or full path of the group.'
2021-09-04 01:27:46 +05:30
end
resource :groups, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
after_validation do
2022-09-01 20:07:04 +05:30
ensure_group!
2021-09-04 01:27:46 +05:30
end
namespace ':id/-/packages/pypi' do
2023-03-04 22:38:38 +05:30
desc 'Download a package file from a group' do
detail 'This feature was introduced in GitLab 13.12'
success code: 200
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[pypi_packages]
end
2021-09-04 01:27:46 +05:30
params do
use :package_download
end
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
get 'files/:sha256/*file_identifier' do
2022-09-01 20:07:04 +05:30
group = find_authorized_group!
authorize_read_package!(group)
2021-09-04 01:27:46 +05:30
filename = "#{params[:file_identifier]}.#{params[:format]}"
package = Packages::Pypi::PackageFinder.new(current_user, group, { filename: filename, sha256: params[:sha256] }).execute
package_file = ::Packages::PackageFileFinder.new(package, filename, with_file_name_like: false).execute
2023-03-04 22:38:38 +05:30
track_package_event('pull_package', :pypi, namespace: group, project: package.project)
2021-09-04 01:27:46 +05:30
2022-10-11 01:57:18 +05:30
present_package_file!(package_file, supports_direct_download: true)
2021-09-04 01:27:46 +05:30
end
2022-07-23 23:45:48 +05:30
desc 'The PyPi Simple Group Index Endpoint' do
detail 'This feature was introduced in GitLab 15.1'
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[pypi_packages]
2022-07-23 23:45:48 +05:30
end
# An API entry point but returns an HTML file instead of JSON.
# PyPi simple API returns a list of packages as a simple HTML file.
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
get 'simple', format: :txt do
present_simple_index(find_authorized_group!)
end
desc 'The PyPi Simple Group Package Endpoint' do
2021-09-04 01:27:46 +05:30
detail 'This feature was introduced in GitLab 12.10'
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[pypi_packages]
2021-09-04 01:27:46 +05:30
end
params do
use :package_name
end
2022-07-23 23:45:48 +05:30
# An API entry point but returns an HTML file instead of JSON.
2021-09-04 01:27:46 +05:30
# PyPi simple API returns the package descriptor as a simple HTML file.
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
get 'simple/*package_name', format: :txt do
2022-07-23 23:45:48 +05:30
present_simple_package(find_authorized_group!)
2021-09-04 01:27:46 +05:30
end
end
end
2020-07-28 23:09:34 +05:30
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
namespace ':id/packages/pypi' do
desc 'The PyPi package download endpoint' do
detail 'This feature was introduced in GitLab 12.10'
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[pypi_packages]
2020-07-28 23:09:34 +05:30
end
params do
2021-09-04 01:27:46 +05:30
use :package_download
2020-07-28 23:09:34 +05:30
end
2020-11-24 15:15:51 +05:30
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
2020-07-28 23:09:34 +05:30
get 'files/:sha256/*file_identifier' do
2023-01-13 00:05:48 +05:30
project = project!
2020-07-28 23:09:34 +05:30
filename = "#{params[:file_identifier]}.#{params[:format]}"
2021-06-08 01:23:25 +05:30
package = Packages::Pypi::PackageFinder.new(current_user, project, { filename: filename, sha256: params[:sha256] }).execute
2020-07-28 23:09:34 +05:30
package_file = ::Packages::PackageFileFinder.new(package, filename, with_file_name_like: false).execute
2021-09-04 01:27:46 +05:30
track_package_event('pull_package', :pypi, project: project, namespace: project.namespace)
2020-07-28 23:09:34 +05:30
2022-10-11 01:57:18 +05:30
present_package_file!(package_file, supports_direct_download: true)
2020-07-28 23:09:34 +05:30
end
2022-07-23 23:45:48 +05:30
desc 'The PyPi Simple Project Index Endpoint' do
detail 'This feature was introduced in GitLab 15.1'
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[pypi_packages]
2022-07-23 23:45:48 +05:30
end
# An API entry point but returns an HTML file instead of JSON.
# PyPi simple API returns a list of packages as a simple HTML file.
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
get 'simple', format: :txt do
2023-01-13 00:05:48 +05:30
present_simple_index(project!)
2022-07-23 23:45:48 +05:30
end
desc 'The PyPi Simple Project Package Endpoint' do
2020-07-28 23:09:34 +05:30
detail 'This feature was introduced in GitLab 12.10'
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[pypi_packages]
2020-07-28 23:09:34 +05:30
end
params do
2021-09-04 01:27:46 +05:30
use :package_name
2020-07-28 23:09:34 +05:30
end
2022-07-23 23:45:48 +05:30
# An API entry point but returns an HTML file instead of JSON.
2020-07-28 23:09:34 +05:30
# PyPi simple API returns the package descriptor as a simple HTML file.
2020-11-24 15:15:51 +05:30
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
2020-07-28 23:09:34 +05:30
get 'simple/*package_name', format: :txt do
2023-01-13 00:05:48 +05:30
present_simple_package(project!)
2020-07-28 23:09:34 +05:30
end
desc 'The PyPi Package upload endpoint' do
detail 'This feature was introduced in GitLab 12.10'
2023-03-04 22:38:38 +05:30
success code: 201
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[pypi_packages]
2020-07-28 23:09:34 +05:30
end
params do
2023-01-13 00:05:48 +05:30
requires :content, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' }
2023-03-04 22:38:38 +05:30
requires :name, type: String, documentation: { example: 'my.pypi.package' }
requires :version, type: String, documentation: { example: '1.3.7' }
optional :requires_python, type: String, documentation: { example: '>=3.7' }
optional :md5_digest, type: String, documentation: { example: '900150983cd24fb0d6963f7d28e17f72' }
optional :sha256_digest, type: String, regexp: Gitlab::Regex.sha256_regex, documentation: { example: 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad' }
2020-07-28 23:09:34 +05:30
end
2020-11-24 15:15:51 +05:30
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
2020-07-28 23:09:34 +05:30
post do
2023-01-13 00:05:48 +05:30
project = project!(action: :read_project)
authorize_upload!(project)
bad_request!('File is too large') if project.actual_limits.exceeded?(:pypi_max_file_size, params[:content].size)
2020-07-28 23:09:34 +05:30
2023-01-13 00:05:48 +05:30
track_package_event('push_package', :pypi, project: project, user: current_user, namespace: project.namespace)
2020-07-28 23:09:34 +05:30
2023-03-04 22:38:38 +05:30
validate_fips! if Gitlab::FIPS.enabled?
2022-08-13 15:12:31 +05:30
2020-07-28 23:09:34 +05:30
::Packages::Pypi::CreatePackageService
2023-01-13 00:05:48 +05:30
.new(project, current_user, declared_params.merge(build: current_authenticated_job))
2020-07-28 23:09:34 +05:30
.execute
created!
rescue ObjectStorage::RemoteStoreError => e
Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:name], project_id: authorized_user_project.id })
forbidden!
end
2023-03-04 22:38:38 +05:30
desc 'Authorize the PyPi package upload from workhorse' do
detail 'This feature was introduced in GitLab 12.10'
success code: 200
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[pypi_packages]
end
2020-11-24 15:15:51 +05:30
route_setting :authentication, deploy_token_allowed: true, basic_auth_personal_access_token: true, job_token_allowed: :basic_auth
2020-07-28 23:09:34 +05:30
post 'authorize' do
2023-01-13 00:05:48 +05:30
project = project!(action: :read_project)
2020-11-24 15:15:51 +05:30
authorize_workhorse!(
2023-01-13 00:05:48 +05:30
subject: project,
2020-11-24 15:15:51 +05:30
has_length: false,
2023-01-13 00:05:48 +05:30
maximum_size: project.actual_limits.pypi_max_file_size
2020-11-24 15:15:51 +05:30
)
2020-07-28 23:09:34 +05:30
end
end
end
end
end