Commit da86b006 authored by Steve Abrams's avatar Steve Abrams Committed by Nikola Milojevic

Deploy token support for the Composer package registry

parent 8de5854f
......@@ -40,11 +40,15 @@ module Packages
# access to packages is ruled by:
# - project is public or the current user has access to it with at least the reporter level
# - the repository feature is available to the current_user
if current_user.is_a?(DeployToken)
current_user.accessible_projects
else
::Project
.in_namespace(groups)
.public_or_visible_to_user(current_user, Gitlab::Access::REPORTER)
.with_feature_available_for_user(:repository, current_user)
end
end
def groups
return [group] if exclude_subgroups?
......
......@@ -9,6 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/15886) in GitLab 13.2.
> - [Moved](https://gitlab.com/gitlab-org/gitlab/-/issues/221259) from GitLab Premium to GitLab Free in 13.3.
> - Support for Composer 2.0 [added](https://gitlab.com/gitlab-org/gitlab/-/issues/259840) in GitLab 13.10.
> - Deploy token support [added](https://gitlab.com/gitlab-org/gitlab/-/issues/240897) in GitLab 14.6.
WARNING:
The Composer package registry for GitLab is under development and isn't ready for production use due to
......@@ -88,13 +89,12 @@ Prerequisites:
- A valid `composer.json` file.
- The Packages feature is enabled in a GitLab repository.
- The project ID, which is on the project's home page.
- A [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to `api`.
- One of the following token types:
- A [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to `api`.
- A [deploy token](../../project/deploy_tokens/index.md)
with the scope set to `write_package_registry`.
NOTE:
[Deploy tokens](../../project/deploy_tokens/index.md) are
[not yet supported](https://gitlab.com/gitlab-org/gitlab/-/issues/240897) for use with Composer.
To publish the package:
To publish the package with a personal access token:
- Send a `POST` request to the [Packages API](../../../api/packages.md).
......@@ -109,6 +109,21 @@ To publish the package:
- `<tag>` is the Git tag name of the version you want to publish.
To publish a branch, use `branch=<branch>` instead of `tag=<tag>`.
To publish the package with a deploy token:
- Send a `POST` request to the [Packages API](../../../api/packages.md).
For example, you can use `curl`:
```shell
curl --data tag=<tag> --header "Deploy-Token: <deploy-token>" "https://gitlab.example.com/api/v4/projects/<project_id>/packages/composer"
```
- `<deploy-token>` is your deploy token
- `<project_id>` is your project ID.
- `<tag>` is the Git tag name of the version you want to publish.
To publish a branch, use `branch=<branch>` instead of `tag=<tag>`.
You can view the published package by going to **Packages & Registries > Package Registry** and
selecting the **Composer** tab.
......@@ -159,11 +174,11 @@ Prerequisites:
- A package in the Package Registry.
- The group ID, which is on the group's home page.
- A [personal access token](../../../user/profile/personal_access_tokens.md) with the scope set to, at minimum, `read_api`.
NOTE:
[Deploy tokens](../../project/deploy_tokens/index.md) are
[not yet supported](https://gitlab.com/gitlab-org/gitlab/-/issues/240897) for use with Composer.
- One of the following token types:
- A [personal access token](../../../user/profile/personal_access_tokens.md)
with the scope set to, at minimum, `api`.
- A [deploy token](../../project/deploy_tokens/index.md)
with the scope set to `read_package_registry`, `write_package_registry`, or both.
To install a package:
......@@ -213,6 +228,8 @@ To install a package:
1. Create an `auth.json` file with your GitLab credentials:
Using a personal access token:
```shell
composer config gitlab-token.<DOMAIN-NAME> <personal_access_token>
```
......@@ -229,6 +246,26 @@ To install a package:
}
```
Using a deploy token:
```shell
composer config gitlab-token.<DOMAIN-NAME> <deploy_token_username> <deploy_token>
```
Result in the `auth.json` file:
```json
{
...
"gitlab-token": {
"<DOMAIN-NAME>": {
"username": "<deploy_token_username>",
"token": "<deploy_token>",
...
}
}
```
You can unset this with the command:
```shell
......@@ -236,7 +273,8 @@ To install a package:
```
- `<DOMAIN-NAME>` is the GitLab instance URL `gitlab.com` or `gitlab.example.com`.
- `<personal_access_token>` with the scope set to `read_api`.
- `<personal_access_token>` with the scope set to `api`, or `<deploy_token>` with the scope set
to `read_package_registry` and/or `write_package_registry`.
1. If you are on a GitLab self-managed instance, add `gitlab-domains` to `composer.json`.
......@@ -298,10 +336,19 @@ To install a package:
WARNING:
Never commit the `auth.json` file to your repository. To install packages from a CI/CD job,
consider using the [`composer config`](https://getcomposer.org/doc/articles/handling-private-packages.md#satis) tool with your personal access token
consider using the [`composer config`](https://getcomposer.org/doc/articles/handling-private-packages.md#satis) tool with your access token
stored in a [GitLab CI/CD variable](../../../ci/variables/index.md) or in
[HashiCorp Vault](../../../ci/secrets/index.md).
### Working with Deploy Tokens
Although Composer packages are accessed at the group level, a group or project deploy token can be
used to access them:
- A group deploy token has access to all packages published to projects in that group or its
subgroups.
- A project deploy token only has access to packages published to that particular project.
## Supported CLI commands
The GitLab Composer repository supports the following Composer CLI commands:
......
......@@ -70,7 +70,7 @@ module API
end
desc 'Composer packages endpoint at group level'
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
get ':id/-/packages/composer/packages' do
presenter.root
end
......@@ -79,7 +79,7 @@ module API
params do
requires :sha, type: String, desc: 'Shasum of current json'
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
get ':id/-/packages/composer/p/:sha' do
presenter.provider
end
......@@ -88,7 +88,7 @@ module API
params do
requires :package_name, type: String, file_path: true, desc: 'The Composer package name'
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
get ':id/-/packages/composer/p2/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do
not_found! if packages.empty?
......@@ -99,7 +99,7 @@ module API
params do
requires :package_name, type: String, file_path: true, desc: 'The Composer package name'
end
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
get ':id/-/packages/composer/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do
not_found! if packages.empty?
not_found! if params[:sha].blank?
......@@ -119,7 +119,7 @@ module API
desc 'Composer packages endpoint for registering packages'
namespace ':id/packages/composer' do
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true
route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
params do
optional :branch, type: String, desc: 'The name of the branch'
......
......@@ -107,6 +107,28 @@ RSpec.describe Packages::GroupPackagesFinder do
end
end
context 'deploy tokens' do
let(:add_user_to_group) { false }
context 'group deploy token' do
let_it_be(:deploy_token_for_group) { create(:deploy_token, :group, read_package_registry: true) }
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token_for_group, group: group) }
let(:user) { deploy_token_for_group }
it { is_expected.to match_array([package1, package2, package4]) }
end
context 'project deploy token' do
let_it_be(:deploy_token_for_project) { create(:deploy_token, read_package_registry: true) }
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token_for_project, project: subproject) }
let(:user) { deploy_token_for_project }
it { is_expected.to match_array([package4]) }
end
end
context 'avoid N+1 query' do
it 'avoids N+1 database queries' do
count = ActiveRecord::QueryRecorder.new { subject }
......
......@@ -9,6 +9,10 @@ RSpec.describe API::ComposerPackages do
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let_it_be(:package_name) { 'package-name' }
let_it_be(:project, reload: true) { create(:project, :custom_repo, files: { 'composer.json' => { name: package_name }.to_json }, group: group) }
let_it_be(:deploy_token_for_project) { create(:deploy_token, read_package_registry: true, write_package_registry: true) }
let_it_be(:project_deploy_token) { create(:project_deploy_token, deploy_token: deploy_token_for_project, project: project) }
let_it_be(:deploy_token_for_group) { create(:deploy_token, :group, read_package_registry: true, write_package_registry: true) }
let_it_be(:group_deploy_token) { create(:group_deploy_token, deploy_token: deploy_token_for_group, group: group) }
let(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } }
let(:headers) { {} }
......@@ -92,6 +96,8 @@ RSpec.describe API::ComposerPackages do
group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
it_behaves_like 'Composer access with deploy tokens'
context 'with access to the api' do
where(:project_visibility_level, :user_role, :member, :user_token, :include_package) do
'PRIVATE' | :developer | true | true | :include_package
......@@ -162,6 +168,8 @@ RSpec.describe API::ComposerPackages do
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
it_behaves_like 'Composer access with deploy tokens'
end
it_behaves_like 'rejects Composer access with unknown group id'
......@@ -219,6 +227,8 @@ RSpec.describe API::ComposerPackages do
end
end
end
it_behaves_like 'Composer access with deploy tokens'
end
it_behaves_like 'rejects Composer access with unknown group id'
......@@ -265,6 +275,8 @@ RSpec.describe API::ComposerPackages do
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
it_behaves_like 'Composer access with deploy tokens'
end
it_behaves_like 'rejects Composer access with unknown group id'
......@@ -308,6 +320,8 @@ RSpec.describe API::ComposerPackages do
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end
end
it_behaves_like 'Composer publish with deploy tokens'
end
it_behaves_like 'rejects Composer access with unknown project id'
......
......@@ -173,3 +173,65 @@ RSpec.shared_examples 'rejects Composer access with unknown project id' do
end
end
end
RSpec.shared_examples 'Composer access with deploy tokens' do
shared_examples 'a deploy token for Composer GET requests' do
context 'with deploy token headers' do
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) }
before do
group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end
context 'valid token' do
it_behaves_like 'returning response status', :success
end
context 'invalid token' do
let(:headers) { basic_auth_header(deploy_token.username, 'bar') }
it_behaves_like 'returning response status', :not_found
end
end
end
context 'group deploy token' do
let(:deploy_token) { deploy_token_for_group }
it_behaves_like 'a deploy token for Composer GET requests'
end
context 'project deploy token' do
let(:deploy_token) { deploy_token_for_project }
it_behaves_like 'a deploy token for Composer GET requests'
end
end
RSpec.shared_examples 'Composer publish with deploy tokens' do
shared_examples 'a deploy token for Composer publish requests' do
let(:headers) { basic_auth_header(deploy_token.username, deploy_token.token) }
context 'valid token' do
it_behaves_like 'returning response status', :success
end
context 'invalid token' do
let(:headers) { basic_auth_header(deploy_token.username, 'bar') }
it_behaves_like 'returning response status', :unauthorized
end
end
context 'group deploy token' do
let(:deploy_token) { deploy_token_for_group }
it_behaves_like 'a deploy token for Composer publish requests'
end
context 'group deploy token' do
let(:deploy_token) { deploy_token_for_project }
it_behaves_like 'a deploy token for Composer publish requests'
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