Commit cae37bcd authored by Piotr Stankowski's avatar Piotr Stankowski Committed by Steve Abrams

Add authorization to composer package archive download

Changelog: security
parent 48983d7d
...@@ -262,6 +262,8 @@ Example response: ...@@ -262,6 +262,8 @@ Example response:
## Download a package archive ## Download a package archive
> Authorization for this endpoint was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/331601) in GitLab 14.10.
Download a Composer package. This URL is provided in the [v1](#v1-package-metadata) Download a Composer package. This URL is provided in the [v1](#v1-package-metadata)
or [v2 package metadata](#v2-package-metadata) or [v2 package metadata](#v2-package-metadata)
response. A `.zip` file extension must be in the request. response. A `.zip` file extension must be in the request.
...@@ -287,3 +289,6 @@ curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v ...@@ -287,3 +289,6 @@ curl --user <username>:<personal_access_token> "https://gitlab.example.com/api/v
``` ```
This writes the downloaded file to `package.tar.gz` in the current directory. This writes the downloaded file to `package.tar.gz` in the current directory.
NOTE:
This endpoint requires authorization in GitLab 14.10 and later. In GitLab 14.9 and earlier, it was publicly accessible.
...@@ -171,6 +171,8 @@ When you publish: ...@@ -171,6 +171,8 @@ When you publish:
## Install a Composer package ## Install a Composer package
> Authorization to [download a package archive](../../../api/packages/composer.md#download-a-package-archive) was [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/331601) in GitLab 14.10.
Install a package from the Package Registry so you can use it as a dependency. Install a package from the Package Registry so you can use it as a dependency.
Prerequisites: Prerequisites:
...@@ -354,6 +356,8 @@ used to access them: ...@@ -354,6 +356,8 @@ used to access them:
## Troubleshooting ## Troubleshooting
### Caching
To improve performance, Composer caches files related to a package. Note that Composer doesn't remove data by To improve performance, Composer caches files related to a package. Note that Composer doesn't remove data by
itself. The cache grows as new packages are installed. If you encounter issues, clear the cache with itself. The cache grows as new packages are installed. If you encounter issues, clear the cache with
this command: this command:
...@@ -362,6 +366,14 @@ this command: ...@@ -362,6 +366,14 @@ this command:
composer clearcache composer clearcache
``` ```
### Authorization requirement when using `composer install`
In GitLab 14.9 and earlier, you did not require authorization to use `composer install` if you already had a generated `composer.lock`.
If you committed your `composer.lock`, you could do a `composer install` in CI without setting up credentials.
In GitLab 14.10 and later, authorization is required for the [downloading a package archive](../../../api/packages/composer.md#download-a-package-archive) endpoint.
If you encounter a credentials prompt when you are using `composer install`, follow the instructions in the [install a composer package](#install-a-composer-package) section to create an `auth.json` file.
## Supported CLI commands ## Supported CLI commands
The GitLab Composer repository supports the following Composer CLI commands: The GitLab Composer repository supports the following Composer CLI commands:
......
...@@ -113,10 +113,6 @@ module API ...@@ -113,10 +113,6 @@ module API
end end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before do
unauthorized_user_project!
end
desc 'Composer packages endpoint for registering packages' desc 'Composer packages endpoint for registering packages'
namespace ':id/packages/composer' do namespace ':id/packages/composer' do
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
...@@ -150,8 +146,11 @@ module API ...@@ -150,8 +146,11 @@ module API
requires :sha, type: String, desc: 'Shasum of current json' requires :sha, type: String, desc: 'Shasum of current json'
requires :package_name, type: String, file_path: true, desc: 'The Composer package name' requires :package_name, type: String, file_path: true, desc: 'The Composer package name'
end end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
get 'archives/*package_name' do get 'archives/*package_name' do
metadata = unauthorized_user_project authorize_read_package!(authorized_user_project)
metadata = authorized_user_project
.packages .packages
.composer .composer
.with_name(params[:package_name]) .with_name(params[:package_name])
...@@ -161,9 +160,9 @@ module API ...@@ -161,9 +160,9 @@ module API
not_found! unless metadata not_found! unless metadata
track_package_event('pull_package', :composer, project: unauthorized_user_project, namespace: unauthorized_user_project.namespace) track_package_event('pull_package', :composer, project: authorized_user_project, namespace: authorized_user_project.namespace)
send_git_archive unauthorized_user_project.repository, ref: metadata.target_sha, format: 'zip', append_sha: true send_git_archive authorized_user_project.repository, ref: metadata.target_sha, format: 'zip', append_sha: true
end end
end end
end end
......
...@@ -430,11 +430,23 @@ RSpec.describe API::ComposerPackages do ...@@ -430,11 +430,23 @@ RSpec.describe API::ComposerPackages do
context 'with valid project' do context 'with valid project' do
let!(:package) { create(:composer_package, :with_metadatum, name: package_name, project: project) } let!(:package) { create(:composer_package, :with_metadatum, name: package_name, project: project) }
let(:headers) { basic_auth_header(user.username, personal_access_token.token) }
before do
project.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
context 'when the sha does not match the package name' do context 'when the sha does not match the package name' do
let(:sha) { '123' } let(:sha) { '123' }
let(:headers) { basic_auth_header(user.username, personal_access_token.token) }
it_behaves_like 'process Composer api request', :anonymous, :not_found context 'anonymous' do
let(:headers) { {} }
it_behaves_like 'process Composer api request', :anonymous, :unauthorized
end
it_behaves_like 'process Composer api request', :developer, :not_found
end end
context 'when the package name does not match the sha' do context 'when the package name does not match the sha' do
...@@ -442,7 +454,13 @@ RSpec.describe API::ComposerPackages do ...@@ -442,7 +454,13 @@ RSpec.describe API::ComposerPackages do
let(:sha) { branch.target } let(:sha) { branch.target }
let(:url) { "/projects/#{project.id}/packages/composer/archives/unexisting-package-name.zip" } let(:url) { "/projects/#{project.id}/packages/composer/archives/unexisting-package-name.zip" }
it_behaves_like 'process Composer api request', :anonymous, :not_found context 'anonymous' do
let(:headers) { {} }
it_behaves_like 'process Composer api request', :anonymous, :unauthorized
end
it_behaves_like 'process Composer api request', :developer, :not_found
end end
context 'with a match package name and sha' do context 'with a match package name and sha' do
...@@ -460,14 +478,14 @@ RSpec.describe API::ComposerPackages do ...@@ -460,14 +478,14 @@ RSpec.describe API::ComposerPackages do
'PUBLIC' | :guest | false | false | :success 'PUBLIC' | :guest | false | false | :success
'PUBLIC' | :anonymous | false | true | :success 'PUBLIC' | :anonymous | false | true | :success
'PRIVATE' | :developer | true | true | :success 'PRIVATE' | :developer | true | true | :success
'PRIVATE' | :developer | true | false | :success 'PRIVATE' | :developer | true | false | :unauthorized
'PRIVATE' | :developer | false | true | :success 'PRIVATE' | :developer | false | true | :not_found
'PRIVATE' | :developer | false | false | :success 'PRIVATE' | :developer | false | false | :unauthorized
'PRIVATE' | :guest | true | true | :success 'PRIVATE' | :guest | true | true | :forbidden
'PRIVATE' | :guest | true | false | :success 'PRIVATE' | :guest | true | false | :unauthorized
'PRIVATE' | :guest | false | true | :success 'PRIVATE' | :guest | false | true | :not_found
'PRIVATE' | :guest | false | false | :success 'PRIVATE' | :guest | false | false | :unauthorized
'PRIVATE' | :anonymous | false | true | :success 'PRIVATE' | :anonymous | false | true | :unauthorized
end end
with_them do with_them do
...@@ -480,8 +498,17 @@ RSpec.describe API::ComposerPackages do ...@@ -480,8 +498,17 @@ RSpec.describe API::ComposerPackages do
end end
it_behaves_like 'process Composer api request', params[:user_role], params[:expected_status], params[:member] it_behaves_like 'process Composer api request', params[:user_role], params[:expected_status], params[:member]
it_behaves_like 'a package tracking event', described_class.name, 'pull_package'
include_context 'Composer user type', params[:user_role], params[:member] do
if params[:expected_status] == :success
it_behaves_like 'a package tracking event', described_class.name, 'pull_package'
else
it_behaves_like 'not a package tracking event'
end
end
end end
it_behaves_like 'Composer publish with deploy tokens'
end end
end end
......
...@@ -163,11 +163,11 @@ RSpec.shared_examples 'rejects Composer access with unknown project id' do ...@@ -163,11 +163,11 @@ RSpec.shared_examples 'rejects Composer access with unknown project id' do
let(:project) { double(id: non_existing_record_id) } let(:project) { double(id: non_existing_record_id) }
context 'as anonymous' do context 'as anonymous' do
it_behaves_like 'process Composer api request', :anonymous, :not_found it_behaves_like 'process Composer api request', :anonymous, :unauthorized
end end
context 'as authenticated user' do context 'as authenticated user' do
subject { get api(url), headers: basic_auth_header(user.username, personal_access_token.token) } subject { get api(url), params: params, headers: basic_auth_header(user.username, personal_access_token.token) }
it_behaves_like 'process Composer api request', :anonymous, :not_found it_behaves_like 'process Composer api request', :anonymous, :not_found
end end
......
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