1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169
|
# frozen_string_literal: true
module API
class NpmProjectPackages < ::API::Base
ERROR_REASON_TO_HTTP_STATUS_MAPPTING = {
::Packages::Npm::CreatePackageService::ERROR_REASON_INVALID_PARAMETER => 400,
::Packages::Npm::CreatePackageService::ERROR_REASON_PACKAGE_EXISTS => 403,
::Packages::Npm::CreatePackageService::ERROR_REASON_PACKAGE_LEASE_TAKEN => 400,
::Packages::Npm::CreatePackageService::ERROR_REASON_PACKAGE_PROTECTED => 403,
::Packages::Npm::CreatePackageService::ERROR_REASON_UNAUTHORIZED => 403
}.freeze
helpers ::API::Helpers::Packages::Npm
helpers ::API::Helpers::Packages::DependencyProxyHelpers
feature_category :package_registry
urgency :low
rescue_from ActiveRecord::RecordInvalid do |e|
render_structured_api_error!({ message: e.message, error: e.message }, 400)
end
helpers do
include Gitlab::Utils::StrongMemoize
def error_reason_to_http_status(reason)
ERROR_REASON_TO_HTTP_STATUS_MAPPTING.fetch(reason, 400)
end
def metadata_cache
::Packages::Npm::MetadataCache
.find_by_package_name_and_project_id(params[:package_name], project.id)
end
strong_memoize_attr :metadata_cache
def project
user_project(action: :read_package)
end
def project_id_or_nil
params[:id]
end
end
params do
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
end
namespace 'projects/:id/packages/npm' do
include ::API::Concerns::Packages::NpmEndpoints
desc 'Download the NPM tarball' do
detail 'This feature was introduced in GitLab 11.8'
success code: 200
failure [
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[npm_packages]
end
params do
requires :package_name, type: String, desc: 'Package name'
requires :file_name, type: String, desc: 'Package file name'
end
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
get '*package_name/-/*file_name', format: false do
authorize_read_package!(project)
package = project.packages.npm
.by_name_and_file_name(params[:package_name], params[:file_name])
not_found!('Package') unless package
package_file = ::Packages::PackageFileFinder
.new(package, params[:file_name]).execute!
track_package_event('pull_package', :npm, category: 'API::NpmPackages', project: project, namespace: project.namespace)
present_package_file!(package_file)
end
desc 'Create or deprecate NPM package' do
detail 'Create was introduced in GitLab 11.8 & deprecate suppport was added in 16.0'
success code: 200
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[npm_packages]
end
params do
requires :package_name, type: String, desc: 'Package name'
requires :versions, type: Hash, desc: 'Package version info'
end
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true
put ':package_name', requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do
if headers['Npm-Command'] == 'deprecate'
authorize_destroy_package!(project)
::Packages::Npm::DeprecatePackageService.new(project, declared(params)).execute(async: true)
else
authorize_create_package!(project)
service_response = ::Packages::Npm::CreatePackageService
.new(project, current_user, params.merge(build: current_authenticated_job)).execute
if service_response.error?
render_structured_api_error!({ message: service_response.message, error: service_response.message }, error_reason_to_http_status(service_response.reason))
end
track_package_event('push_package', :npm, category: 'API::NpmPackages', project: project, namespace: project.namespace)
service_response[:package]
end
end
# Caution: This is a globbing wildcard for GET requests
# Do not put other GET routes below this one
desc 'NPM registry metadata endpoint' do
detail 'This feature was introduced in GitLab 11.8'
success [
{ code: 200, model: ::API::Entities::NpmPackage, message: 'Ok' },
{ code: 302, message: 'Found (redirect)' }
]
failure [
{ code: 400, message: 'Bad Request' },
{ code: 401, message: 'Unauthorized' },
{ code: 403, message: 'Forbidden' },
{ code: 404, message: 'Not Found' }
]
tags %w[npm_packages]
end
params do
use :package_name
end
route_setting :authentication, job_token_allowed: true, deploy_token_allowed: true,
authenticate_non_public: true
get '*package_name', format: false, requirements: ::API::Helpers::Packages::Npm::NPM_ENDPOINT_REQUIREMENTS do
package_name = declared_params[:package_name]
packages = ::Packages::Npm::PackageFinder.new(project: project_or_nil, params: { package_name: package_name }).execute
# In order to redirect a request, packages should not exist (without taking the user into account).
redirect_request = project_or_nil.blank? || packages.empty?
redirect_registry_request(
forward_to_registry: redirect_request,
package_type: :npm,
target: project_or_nil,
package_name: package_name
) do
authorize_read_package!(project)
not_found!('Packages') if packages.empty?
if metadata_cache&.file&.exists?
metadata_cache.touch_last_downloaded_at
present_carrierwave_file!(metadata_cache.file)
break
end
enqueue_sync_metadata_cache_worker(project, package_name)
metadata = generate_metadata_service(packages).execute.payload
present metadata, with: ::API::Entities::NpmPackage
end
end
end
end
end
|