# frozen_string_literal: true ### # API endpoints for the RubyGem package registry module API class RubygemPackages < ::API::Base include ::API::Helpers::Authentication helpers ::API::Helpers::PackagesHelpers feature_category :package_registry urgency :low # The Marshal version can be found by "#{Marshal::MAJOR_VERSION}.#{Marshal::MINOR_VERSION}" # Updating the version should require a GitLab API version change. MARSHAL_VERSION = '4.8' PACKAGE_FILENAME = 'package.gem' FILE_NAME_REQUIREMENTS = { file_name: API::NO_SLASH_URL_PART_REGEX }.freeze content_type :binary, 'application/octet-stream' authenticate_with do |accept| accept.token_types(:personal_access_token, :deploy_token, :job_token) .sent_through(:http_token) end helpers do def project user_project(action: :read_package) end end before do require_packages_enabled! authenticate_non_get! end after_validation do not_found! unless Feature.enabled?(:rubygem_packages, project) end params do requires :id, types: [Integer, String], desc: 'The ID or URL-encoded path of the project' end resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do namespace ':id/packages/rubygems' do desc 'Download the spec index file' do detail 'This feature was introduced in GitLab 13.9' failure [ { code: 401, message: 'Unauthorized' }, { code: 404, message: 'Not Found' } ] tags %w[rubygem_packages] end params do requires :file_name, type: String, desc: 'Spec file name', documentation: { type: 'file' } end get ":file_name", requirements: FILE_NAME_REQUIREMENTS do # To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299267 not_found! end desc 'Download the gemspec file' do detail 'This feature was introduced in GitLab 13.9' failure [ { code: 401, message: 'Unauthorized' }, { code: 404, message: 'Not Found' } ] tags %w[rubygem_packages] end params do requires :file_name, type: String, desc: 'Gemspec file name', documentation: { type: 'file' } end get "quick/Marshal.#{MARSHAL_VERSION}/:file_name", requirements: FILE_NAME_REQUIREMENTS do # To be implemented in https://gitlab.com/gitlab-org/gitlab/-/issues/299284 not_found! end desc 'Download the .gem package' do detail 'This feature was introduced in GitLab 13.9' success code: 200 failure [ { code: 401, message: 'Unauthorized' }, { code: 403, message: 'Forbidden' }, { code: 404, message: 'Not Found' } ] tags %w[rubygem_packages] end params do requires :file_name, type: String, desc: 'Package file name', documentation: { type: 'file' } end get "gems/:file_name", requirements: FILE_NAME_REQUIREMENTS do authorize_read_package!(project) package_files = ::Packages::PackageFile .for_rubygem_with_file_name(project, params[:file_name]) package_file = package_files.installable.last! track_package_event('pull_package', :rubygems, project: project, namespace: project.namespace) present_package_file!(package_file) end namespace 'api/v1' do desc 'Authorize a gem upload from workhorse' do detail 'This feature was introduced in GitLab 13.9' success code: 200 failure [ { code: 401, message: 'Unauthorized' }, { code: 403, message: 'Forbidden' } ] tags %w[rubygem_packages] end post 'gems/authorize' do authorize_workhorse!( subject: project, has_length: false, maximum_size: project.actual_limits.rubygems_max_file_size ) end desc 'Upload a gem' do detail 'This feature was introduced in GitLab 13.9' success code: 201 failure [ { code: 401, message: 'Unauthorized' }, { code: 403, message: 'Forbidden' }, { code: 404, message: 'Not Found' } ] tags %w[rubygem_packages] end params do requires :file, type: ::API::Validations::Types::WorkhorseFile, desc: 'The package file to be published (generated by Multipart middleware)', documentation: { type: 'file' } end post 'gems' do authorize_upload!(project) bad_request!('File is too large') if project.actual_limits.exceeded?(:rubygems_max_file_size, params[:file].size) track_package_event('push_package', :rubygems, project: project, namespace: project.namespace) package_file = nil ApplicationRecord.transaction do package = ::Packages::CreateTemporaryPackageService.new( project, current_user, declared_params.merge(build: current_authenticated_job) ).execute(:rubygems, name: ::Packages::Rubygems::TEMPORARY_PACKAGE_NAME) file_params = { file: params[:file], file_name: PACKAGE_FILENAME } package_file = ::Packages::CreatePackageFileService.new( package, file_params.merge(build: current_authenticated_job) ).execute end if package_file ::Packages::Rubygems::ExtractionWorker.perform_async(package_file.id) # rubocop:disable CodeReuse/Worker created! else bad_request!('Package creation failed') end rescue ObjectStorage::RemoteStoreError => e Gitlab::ErrorTracking.track_exception(e, extra: { file_name: params[:file_name], project_id: project.id }) forbidden! end desc 'Fetch a list of dependencies' do detail 'This feature was introduced in GitLab 13.9' success code: 200 failure [ { code: 401, message: 'Unauthorized' }, { code: 403, message: 'Forbidden' }, { code: 404, message: 'Not Found' } ] is_array true tags %w[rubygem_packages] end params do optional :gems, type: Array[String], coerce_with: ::API::Validations::Types::CommaSeparatedToArray.coerce, desc: 'Comma delimited gem names' end get 'dependencies' do authorize_read_package!(project) if params[:gems].blank? status :ok else results = params[:gems].map do |gem_name| service_result = Packages::Rubygems::DependencyResolverService.new(project, current_user, gem_name: gem_name).execute render_api_error!(service_result.message, service_result.http_status) if service_result.error? service_result.payload end content_type 'application/octet-stream' Marshal.dump(results.flatten) end end end end end end end