Commit 49480aa1 authored by Serena Fang's avatar Serena Fang Committed by Mark Chao

Project access token API

Not working yet
parent 44f55ba6
......@@ -30,6 +30,9 @@ class ProjectPolicy < BasePolicy
desc "User has maintainer access"
condition(:maintainer) { team_access_level >= Gitlab::Access::MAINTAINER }
desc "User is a project bot"
condition(:project_bot) { user.project_bot? && team_member? }
desc "Project is public"
condition(:public_project, scope: :subject, score: 0) { project.public? }
......@@ -616,10 +619,14 @@ class ProjectPolicy < BasePolicy
prevent :read_project
end
rule { project_bot }.enable :project_bot_access
rule { resource_access_token_available & can?(:admin_project) }.policy do
enable :admin_resource_access_tokens
end
rule { can?(:project_bot_access) }.prevent :admin_resource_access_tokens
rule { user_defined_variables_allowed | can?(:maintainer_access) }.policy do
enable :set_pipeline_variables
end
......
---
title: Project access token management via API
merge_request: 52139
author:
type: added
......@@ -25,6 +25,7 @@ The following API resources are available in the project context:
| Resource | Available endpoints |
|:--------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Access requests](access_requests.md) | `/projects/:id/access_requests` (also available for groups) |
| [Access tokens](resource_access_tokens.md) | `/projects/:id/access_tokens` |
| [Award emoji](award_emoji.md) | `/projects/:id/issues/.../award_emoji`, `/projects/:id/merge_requests/.../award_emoji`, `/projects/:id/snippets/.../award_emoji` |
| [Branches](branches.md) | `/projects/:id/repository/branches/`, `/projects/:id/repository/merged_branches` |
| [Commits](commits.md) | `/projects/:id/repository/commits`, `/projects/:id/statuses` |
......@@ -76,7 +77,7 @@ The following API resources are available in the project context:
| [Remote mirrors](remote_mirrors.md) | `/projects/:id/remote_mirrors` |
| [Repositories](repositories.md) | `/projects/:id/repository` |
| [Repository files](repository_files.md) | `/projects/:id/repository/files` |
| [Repository submodules](repository_submodules.md) | `/projects/:id/repository/submodules` |
| [Repository submodules](repository_submodules.md) | `/projects/:id/repository/submodules` |
| [Resource label events](resource_label_events.md) | `/projects/:id/issues/.../resource_label_events`, `/projects/:id/merge_requests/.../resource_label_events` (also available for groups) |
| [Runners](runners.md) | `/projects/:id/runners` (also available standalone) |
| [Search](search.md) | `/projects/:id/search` (also available for groups and standalone) |
......
---
stage: Manage
group: Access
info: To determine the technical writer assigned to the Stage/Group associated with this page, see https://about.gitlab.com/handbook/engineering/ux/technical-writing/#assignments
---
# Project access tokens API
You can read more about [project access tokens](../user/project/settings/project_access_tokens.md).
## List project access tokens
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/238991) in GitLab 13.9.
Get a list of project access tokens.
```plaintext
GET /:id/access_tokens
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer/string | yes | The ID of the project |
```shell
curl --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/<project_id>/access_tokens"
```
```json
[
{
"user_id" : 141,
"scopes" : [
"api"
],
"name" : "token",
"expires_at" : "2021-01-31",
"id" : 42,
"active" : true,
"created_at" : "2021-01-20T22:11:48.151Z",
"revoked" : false
}
]
```
## Create a project access token
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/238991) in GitLab 13.9.
Create a project access token.
```plaintext
POST /:id/access_tokens
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `name` | String | yes | The name of the project access token |
| `scopes` | Array[String] | yes | [List of scopes](../user/project/settings/project_access_tokens.md#limiting-scopes-of-a-project-access-token) |
| `expires_at` | Date | no | The token expires at midnight UTC on that date |
```shell
curl --request POST --header "PRIVATE-TOKEN: <your_access_token>" \
--header "Content-Type:application/json" \
--data '{ "name":"test_token", "scopes":["api", "read_repository"], "expires_at":"2021-01-31" }' \
"https://gitlab.example.com/api/v4/projects/<project_id>/access_tokens"
```
```json
{
"scopes" : [
"api",
"read_repository"
],
"active" : true,
"name" : "test",
"revoked" : false,
"created_at" : "2021-01-21T19:35:37.921Z",
"user_id" : 166,
"id" : 58,
"expires_at" : "2021-01-31"
}
```
## Revoke a project access token
> - [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/238991) in GitLab 13.9.
Revoke a project access token.
```plaintext
DELETE /:id/access_tokens/:token_id
```
| Attribute | Type | required | Description |
|-----------|---------|----------|---------------------|
| `id` | integer/string | yes | The ID of the project |
| `token_id` | integer/string | yes | The ID of the project access token |
```shell
curl --request DELETE --header "PRIVATE-TOKEN: <your_access_token>" "https://gitlab.example.com/api/v4/projects/<project_id>/access_tokens/<token_id>"
```
### Responses
- `204: No Content` if successfully revoked.
- `400 Bad Request` or `404 Not Found` if not revoked successfully.
......@@ -106,7 +106,7 @@ RSpec.describe ResourceAccessTokens::CreateService do
end
context "when access provisioning fails" do
let_it_be(:user) { create(:user, :project_bot) }
let_it_be(:user) { create(:user) }
let(:unpersisted_member) { build(:project_member, source: resource, user: user) }
before do
......
......@@ -268,6 +268,7 @@ module API
mount ::API::Release::Links
mount ::API::RemoteMirrors
mount ::API::Repositories
mount ::API::ResourceAccessTokens
mount ::API::Search
mount ::API::Services
mount ::API::Settings
......
# frozen_string_literal: true
module API
class ResourceAccessTokens < ::API::Base
include PaginationParams
before { authenticate! }
feature_category :authentication_and_authorization
%w[project].each do |source_type|
resource source_type.pluralize, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc 'Get list of all access tokens for the specified resource' do
detail 'This feature was introduced in GitLab 13.9.'
end
params do
requires :id, type: String, desc: "The #{source_type} ID"
end
get ":id/access_tokens" do
resource = find_source(source_type, params[:id])
next unauthorized! unless has_permission_to_read?(resource)
tokens = PersonalAccessTokensFinder.new({ user: resource.bots, impersonation: false }).execute
present paginate(tokens), with: Entities::PersonalAccessToken
end
desc 'Revoke a resource access token' do
detail 'This feature was introduced in GitLab 13.9.'
end
params do
requires :id, type: String, desc: "The #{source_type} ID"
requires :token_id, type: String, desc: "The ID of the token"
end
delete ':id/access_tokens/:token_id' do
resource = find_source(source_type, params[:id])
token = find_token(resource, params[:token_id])
if token.nil?
next not_found!("Could not find #{source_type} access token with token_id: #{params[:token_id]}")
end
service = ::ResourceAccessTokens::RevokeService.new(
current_user,
resource,
token
).execute
service.success? ? no_content! : bad_request!(service.message)
end
desc 'Create a resource access token' do
detail 'This feature was introduced in GitLab 13.9.'
end
params do
requires :id, type: String, desc: "The #{source_type} ID"
requires :name, type: String, desc: "Resource access token name"
requires :scopes, type: Array[String], desc: "The permissions of the token"
optional :expires_at, type: Date, desc: "The expiration date of the token"
end
post ':id/access_tokens' do
resource = find_source(source_type, params[:id])
token_response = ::ResourceAccessTokens::CreateService.new(
current_user,
resource,
declared_params
).execute
if token_response.success?
present token_response.payload[:access_token], with: Entities::PersonalAccessToken
else
bad_request!(token_response.message)
end
end
end
end
helpers do
def find_source(source_type, id)
public_send("find_#{source_type}!", id) # rubocop:disable GitlabSecurity/PublicSend
end
def find_token(resource, token_id)
PersonalAccessTokensFinder.new({ user: resource.bots, impersonation: false }).find_by_id(token_id)
end
def has_permission_to_read?(resource)
can?(current_user, :project_bot_access, resource) || can?(current_user, :admin_resource_access_tokens, resource)
end
end
end
end
......@@ -468,6 +468,49 @@ RSpec.describe ProjectPolicy do
end
end
context "project bots" do
let(:project_bot) { create(:user, :project_bot) }
let(:user) { create(:user) }
context "project_bot_access" do
context "when regular user and part of the project" do
let(:current_user) { user }
before do
project.add_developer(user)
end
it { is_expected.not_to be_allowed(:project_bot_access)}
end
context "when project bot and not part of the project" do
let(:current_user) { project_bot }
it { is_expected.not_to be_allowed(:project_bot_access)}
end
context "when project bot and part of the project" do
let(:current_user) { project_bot }
before do
project.add_developer(project_bot)
end
it { is_expected.to be_allowed(:project_bot_access)}
end
end
context 'with resource access tokens' do
let(:current_user) { project_bot }
before do
project.add_maintainer(project_bot)
end
it { is_expected.not_to be_allowed(:admin_resource_access_tokens)}
end
end
describe 'read_prometheus_alerts' do
context 'with admin' do
let(:current_user) { admin }
......
This diff is collapsed.
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