Commit f3bf1bac authored by Nikola Milojevic's avatar Nikola Milojevic

Merge branch 'feature/deploy-tokens-for-composer_#240897' into 'master'

Deploy token support for the Composer package registry

See merge request gitlab-org/gitlab!75798
parents bcba2d69 da86b006
...@@ -40,10 +40,14 @@ module Packages ...@@ -40,10 +40,14 @@ module Packages
# access to packages is ruled by: # access to packages is ruled by:
# - project is public or the current user has access to it with at least the reporter level # - 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 # - the repository feature is available to the current_user
::Project if current_user.is_a?(DeployToken)
.in_namespace(groups) current_user.accessible_projects
.public_or_visible_to_user(current_user, Gitlab::Access::REPORTER) else
.with_feature_available_for_user(:repository, current_user) ::Project
.in_namespace(groups)
.public_or_visible_to_user(current_user, Gitlab::Access::REPORTER)
.with_feature_available_for_user(:repository, current_user)
end
end end
def groups def groups
......
...@@ -9,6 +9,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w ...@@ -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. > - [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. > - [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. > - 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: WARNING:
The Composer package registry for GitLab is under development and isn't ready for production use due to The Composer package registry for GitLab is under development and isn't ready for production use due to
...@@ -88,13 +89,12 @@ Prerequisites: ...@@ -88,13 +89,12 @@ Prerequisites:
- A valid `composer.json` file. - A valid `composer.json` file.
- The Packages feature is enabled in a GitLab repository. - The Packages feature is enabled in a GitLab repository.
- The project ID, which is on the project's home page. - 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: To publish the package with a personal access token:
[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:
- Send a `POST` request to the [Packages API](../../../api/packages.md). - Send a `POST` request to the [Packages API](../../../api/packages.md).
...@@ -109,6 +109,21 @@ To publish the package: ...@@ -109,6 +109,21 @@ To publish the package:
- `<tag>` is the Git tag name of the version you want to publish. - `<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 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 You can view the published package by going to **Packages & Registries > Package Registry** and
selecting the **Composer** tab. selecting the **Composer** tab.
...@@ -159,11 +174,11 @@ Prerequisites: ...@@ -159,11 +174,11 @@ Prerequisites:
- A package in the Package Registry. - A package in the Package Registry.
- The group ID, which is on the group's home page. - 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`. - One of the following token types:
- A [personal access token](../../../user/profile/personal_access_tokens.md)
NOTE: with the scope set to, at minimum, `api`.
[Deploy tokens](../../project/deploy_tokens/index.md) are - A [deploy token](../../project/deploy_tokens/index.md)
[not yet supported](https://gitlab.com/gitlab-org/gitlab/-/issues/240897) for use with Composer. with the scope set to `read_package_registry`, `write_package_registry`, or both.
To install a package: To install a package:
...@@ -213,6 +228,8 @@ To install a package: ...@@ -213,6 +228,8 @@ To install a package:
1. Create an `auth.json` file with your GitLab credentials: 1. Create an `auth.json` file with your GitLab credentials:
Using a personal access token:
```shell ```shell
composer config gitlab-token.<DOMAIN-NAME> <personal_access_token> composer config gitlab-token.<DOMAIN-NAME> <personal_access_token>
``` ```
...@@ -229,6 +246,26 @@ To install a package: ...@@ -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: You can unset this with the command:
```shell ```shell
...@@ -236,7 +273,8 @@ To install a package: ...@@ -236,7 +273,8 @@ To install a package:
``` ```
- `<DOMAIN-NAME>` is the GitLab instance URL `gitlab.com` or `gitlab.example.com`. - `<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`. 1. If you are on a GitLab self-managed instance, add `gitlab-domains` to `composer.json`.
...@@ -298,10 +336,19 @@ To install a package: ...@@ -298,10 +336,19 @@ To install a package:
WARNING: WARNING:
Never commit the `auth.json` file to your repository. To install packages from a CI/CD job, 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 stored in a [GitLab CI/CD variable](../../../ci/variables/index.md) or in
[HashiCorp Vault](../../../ci/secrets/index.md). [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 ## Supported CLI commands
The GitLab Composer repository supports the following Composer CLI commands: The GitLab Composer repository supports the following Composer CLI commands:
......
...@@ -70,7 +70,7 @@ module API ...@@ -70,7 +70,7 @@ module API
end end
desc 'Composer packages endpoint at group level' 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 get ':id/-/packages/composer/packages' do
presenter.root presenter.root
end end
...@@ -79,7 +79,7 @@ module API ...@@ -79,7 +79,7 @@ module API
params do params do
requires :sha, type: String, desc: 'Shasum of current json' requires :sha, type: String, desc: 'Shasum of current json'
end 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 get ':id/-/packages/composer/p/:sha' do
presenter.provider presenter.provider
end end
...@@ -88,7 +88,7 @@ module API ...@@ -88,7 +88,7 @@ module API
params do params do
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 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 get ':id/-/packages/composer/p2/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do
not_found! if packages.empty? not_found! if packages.empty?
...@@ -99,7 +99,7 @@ module API ...@@ -99,7 +99,7 @@ module API
params do params do
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 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 get ':id/-/packages/composer/*package_name', requirements: COMPOSER_ENDPOINT_REQUIREMENTS, file_path: true do
not_found! if packages.empty? not_found! if packages.empty?
not_found! if params[:sha].blank? not_found! if params[:sha].blank?
...@@ -119,7 +119,7 @@ module API ...@@ -119,7 +119,7 @@ module API
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 route_setting :authentication, job_token_allowed: true, basic_auth_personal_access_token: true, deploy_token_allowed: true
params do params do
optional :branch, type: String, desc: 'The name of the branch' optional :branch, type: String, desc: 'The name of the branch'
......
...@@ -107,6 +107,28 @@ RSpec.describe Packages::GroupPackagesFinder do ...@@ -107,6 +107,28 @@ RSpec.describe Packages::GroupPackagesFinder do
end end
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 context 'avoid N+1 query' do
it 'avoids N+1 database queries' do it 'avoids N+1 database queries' do
count = ActiveRecord::QueryRecorder.new { subject } count = ActiveRecord::QueryRecorder.new { subject }
......
...@@ -9,6 +9,10 @@ RSpec.describe API::ComposerPackages do ...@@ -9,6 +9,10 @@ RSpec.describe API::ComposerPackages do
let_it_be(:personal_access_token) { create(:personal_access_token, user: user) } let_it_be(:personal_access_token) { create(:personal_access_token, user: user) }
let_it_be(:package_name) { 'package-name' } 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(: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(:snowplow_gitlab_standard_context) { { project: project, namespace: project.namespace, user: user } }
let(:headers) { {} } let(:headers) { {} }
...@@ -92,6 +96,8 @@ RSpec.describe API::ComposerPackages do ...@@ -92,6 +96,8 @@ RSpec.describe API::ComposerPackages do
group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE) group.update!(visibility_level: Gitlab::VisibilityLevel::PRIVATE)
end end
it_behaves_like 'Composer access with deploy tokens'
context 'with access to the api' do context 'with access to the api' do
where(:project_visibility_level, :user_role, :member, :user_token, :include_package) do where(:project_visibility_level, :user_role, :member, :user_token, :include_package) do
'PRIVATE' | :developer | true | true | :include_package 'PRIVATE' | :developer | true | true | :include_package
...@@ -162,6 +168,8 @@ RSpec.describe API::ComposerPackages do ...@@ -162,6 +168,8 @@ RSpec.describe API::ComposerPackages do
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end end
end end
it_behaves_like 'Composer access with deploy tokens'
end end
it_behaves_like 'rejects Composer access with unknown group id' it_behaves_like 'rejects Composer access with unknown group id'
...@@ -219,6 +227,8 @@ RSpec.describe API::ComposerPackages do ...@@ -219,6 +227,8 @@ RSpec.describe API::ComposerPackages do
end end
end end
end end
it_behaves_like 'Composer access with deploy tokens'
end end
it_behaves_like 'rejects Composer access with unknown group id' it_behaves_like 'rejects Composer access with unknown group id'
...@@ -265,6 +275,8 @@ RSpec.describe API::ComposerPackages do ...@@ -265,6 +275,8 @@ RSpec.describe API::ComposerPackages do
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end end
end end
it_behaves_like 'Composer access with deploy tokens'
end end
it_behaves_like 'rejects Composer access with unknown group id' it_behaves_like 'rejects Composer access with unknown group id'
...@@ -308,6 +320,8 @@ RSpec.describe API::ComposerPackages do ...@@ -308,6 +320,8 @@ RSpec.describe API::ComposerPackages do
it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member] it_behaves_like params[:shared_examples_name], params[:user_role], params[:expected_status], params[:member]
end end
end end
it_behaves_like 'Composer publish with deploy tokens'
end end
it_behaves_like 'rejects Composer access with unknown project id' 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 ...@@ -173,3 +173,65 @@ RSpec.shared_examples 'rejects Composer access with unknown project id' do
end end
end 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