Commit 505adfc2 authored by Dmitriy Zaporozhets's avatar Dmitriy Zaporozhets

Restrict npm packages feature to scoped packages only

Signed-off-by: default avatarDmitriy Zaporozhets <dmitriy.zaporozhets@gmail.com>
parent fadd54bb
...@@ -8,7 +8,7 @@ project can have its own space to store its NPM packages. ...@@ -8,7 +8,7 @@ project can have its own space to store its NPM packages.
![GitLab NPM Registry](img/npm_package_view.png) ![GitLab NPM Registry](img/npm_package_view.png)
NOTE: **Note:** NOTE: **Note:**
Only scoped packages are supported. Only [scoped](https://docs.npmjs.com/misc/scope) packages are supported.
## Enabling NPM Registry ## Enabling NPM Registry
...@@ -71,11 +71,13 @@ To do this, you need to add next section to the bottom of `package.json`: ...@@ -71,11 +71,13 @@ To do this, you need to add next section to the bottom of `package.json`:
```json ```json
"publishConfig": { "publishConfig": {
"registry":"https://gitlab.com/api/v4/projects/YOUR_PROJECT_ID/packages/npm/" "@foo:registry":"https://gitlab.com/api/v4/projects/YOUR_PROJECT_ID/packages/npm/"
} }
``` ```
Replace `YOUR_PROJECT_ID` with a project you want your package uploaded to.
And replace `@foo` with your own scope.
Once you added it and have set up the [authorization](#authorizing-with-the-gitlab-npm-registry), Once you did it and have set up the [authorization](#authorizing-with-the-gitlab-npm-registry),
test to upload an NPM package from a project of yours: test to upload an NPM package from a project of yours:
```sh ```sh
......
...@@ -16,6 +16,14 @@ module API ...@@ -16,6 +16,14 @@ module API
def find_project_by_package_name(name) def find_project_by_package_name(name)
Project.find_by_full_path(name.sub('@', '')) Project.find_by_full_path(name.sub('@', ''))
end end
def project_package_name_match?
"@#{user_project.full_path}" == params[:package_name]
end
def ensure_project_package_match!
bad_request!(:package_name) unless project_package_name_match?
end
end end
desc 'NPM registry endpoint at instance level' do desc 'NPM registry endpoint at instance level' do
...@@ -24,13 +32,10 @@ module API ...@@ -24,13 +32,10 @@ module API
params do params do
requires :package_name, type: String, desc: 'Package name' requires :package_name, type: String, desc: 'Package name'
end end
route_setting :authentication, job_token_allowed: true
get 'packages/npm/*package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do get 'packages/npm/*package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do
package_name = params[:package_name] package_name = params[:package_name]
# To avoid name collision we require project path and project package be the same. # 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_package_name(package_name) project = find_project_by_package_name(package_name)
authorize!(:read_package, project) authorize!(:read_package, project)
...@@ -51,6 +56,7 @@ module API ...@@ -51,6 +56,7 @@ module API
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before do before do
authorize_packages_feature! authorize_packages_feature!
ensure_project_package_match!
end end
desc 'Download the NPM tarball' do desc 'Download the NPM tarball' do
...@@ -60,7 +66,6 @@ module API ...@@ -60,7 +66,6 @@ module API
requires :package_name, type: String, desc: 'Package name' requires :package_name, type: String, desc: 'Package name'
requires :file_name, type: String, desc: 'Package file name' requires :file_name, type: String, desc: 'Package file name'
end end
route_setting :authentication, job_token_allowed: true
get ':id/packages/npm/*package_name/-/*file_name', format: false do get ':id/packages/npm/*package_name/-/*file_name', format: false do
authorize_download_package! authorize_download_package!
...@@ -79,7 +84,6 @@ module API ...@@ -79,7 +84,6 @@ module API
params do params do
requires :package_name, type: String, desc: 'Package name' requires :package_name, type: String, desc: 'Package name'
end end
route_setting :authentication, job_token_allowed: true
put ':id/packages/npm/:package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do put ':id/packages/npm/:package_name', requirements: NPM_ENDPOINT_REQUIREMENTS do
authorize_create_package! authorize_create_package!
......
...@@ -150,22 +150,23 @@ describe API::NpmPackages do ...@@ -150,22 +150,23 @@ describe API::NpmPackages do
describe 'PUT /api/v4/projects/:id/packages/npm/:package_name' do describe 'PUT /api/v4/projects/:id/packages/npm/:package_name' do
context 'when params are correct' do context 'when params are correct' do
context 'unscoped package' do context 'unscoped package' do
let(:params) { upload_params('foo') } let(:package_name) { project.path }
let(:params) { upload_params(package_name) }
it 'creates npm package with file' do it 'denies the request with 400 error' do
expect { upload_package_with_token('foo', params) } expect { upload_package_with_token(package_name, params) }
.to change { project.packages.count }.by(1) .not_to change { project.packages.count }
.and change { Packages::PackageFile.count }.by(1)
expect(response).to have_gitlab_http_status(200) expect(response).to have_gitlab_http_status(400)
end end
end end
context 'scoped package' do context 'scoped package' do
let(:params) { upload_params('@foo/bar') } let(:package_name) { "@#{project.full_path}" }
let(:params) { upload_params(package_name) }
it 'creates npm package with file' do it 'creates npm package with file' do
expect { upload_package_with_token('@bar%2Ffoo', params) } expect { upload_package_with_token(package_name, params) }
.to change { project.packages.count }.by(1) .to change { project.packages.count }.by(1)
.and change { Packages::PackageFile.count }.by(1) .and change { Packages::PackageFile.count }.by(1)
...@@ -175,7 +176,7 @@ describe API::NpmPackages do ...@@ -175,7 +176,7 @@ describe API::NpmPackages do
end end
def upload_package(package_name, params = {}) def upload_package(package_name, params = {})
put api("/projects/#{project.id}/packages/npm/#{package_name}"), params put api("/projects/#{project.id}/packages/npm/#{package_name.sub('/', '%2f')}"), params
end end
def upload_package_with_token(package_name, params = {}) def upload_package_with_token(package_name, params = {})
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment