Commit 1ff359dc authored by Raphaël Gomès's avatar Raphaël Gomès

tokens: add trigger_api scope on projects and user tokens

This new scope allows for projects and maintainers to create tokens that are
only valid for triggering existing resources.

For now, this can only be used to trigger Mercurial pull mirrors, but other
use-cases (like triggering pipelines, etc.) could be added in the future.

--HG--
branch : heptapod-stable
parent 6ab7bed5746b
......@@ -76,6 +76,8 @@ en:
scope_desc:
api:
Grants complete read/write access to the API, including all groups and projects, the container registry, and the package registry.
trigger_api:
Grants permission to trigger pre-defined actions. It is only used for Mercurial pull mirrors for now, but could be used for pipeline triggers, etc.
read_api:
Grants read access to the API, including all groups and projects, the container registry, and the package registry.
read_user:
......@@ -99,6 +101,8 @@ en:
project_access_token_scope_desc:
api:
Grants complete read/write access to the scoped project API.
trigger_api:
Grants permission to trigger pre-defined actions. It is only used for Mercurial pull mirrors for now, but could be used for pipeline triggers, etc.
read_api:
Grants read access to the scoped project API.
read_repository:
......
......@@ -3,6 +3,7 @@
module API
class RemoteMirrors < ::API::Base
include PaginationParams
include APIGuard
before do
unauthorized! unless can?(current_user, :admin_remote_mirror, user_project)
......@@ -74,17 +75,23 @@ class RemoteMirrors < ::API::Base
end
end
desc 'Trigger a single remote mirror' do
success Entities::RemoteMirror
end
params do
requires :mirror_id, type: String, desc: 'The ID of a remote mirror'
end
put ':id/remote_mirrors/:mirror_id/trigger' do
mirror = user_project.remote_mirrors.find(params[:mirror_id])
namespace ':id/remote_mirrors/:mirror_id/trigger' do
# Granularity of api scopes appears to be at class level, so all other
# routes in this class will also be accessible. This is tolerable, but
# less than ideal, so we keep this here in case this ever gets fixed.
allow_access_with_scope :trigger_api
desc 'Trigger a single remote mirror' do
success Entities::RemoteMirror
end
params do
requires :mirror_id, type: String, desc: 'The ID of a remote mirror'
end
put '' do
mirror = user_project.remote_mirrors.find(params[:mirror_id])
mirror.sync
no_content!
mirror.sync
no_content!
end
end
end
end
......
......@@ -6,7 +6,7 @@ module Auth
IpBlacklisted = Class.new(StandardError)
# Scopes used for GitLab API access
API_SCOPES = [:api, :read_user, :read_api].freeze
API_SCOPES = [:api, :read_user, :read_api, :trigger_api].freeze
# Scopes used for GitLab Repository access
REPOSITORY_SCOPES = [:read_repository, :write_repository].freeze
......
......@@ -8,7 +8,7 @@
describe 'constants' do
it 'API_SCOPES contains all scopes for API access' do
expect(subject::API_SCOPES).to eq %i[api read_user read_api]
expect(subject::API_SCOPES).to eq %i[api read_user read_api api_trigger]
end
it 'ADMIN_SCOPES contains all scopes for ADMIN access' do
......@@ -30,7 +30,7 @@
it 'optional_scopes contains all non-default scopes' do
stub_container_registry_config(enabled: true)
expect(subject.optional_scopes).to eq %i[read_user read_api read_repository write_repository read_registry write_registry sudo openid profile email]
expect(subject.optional_scopes).to eq %i[trigger_api read_user read_api read_repository write_repository read_registry write_registry sudo openid profile email]
end
end
......@@ -38,21 +38,21 @@
it 'contains all non-default scopes' do
stub_container_registry_config(enabled: true)
expect(subject.all_available_scopes).to eq %i[api read_user read_api read_repository write_repository read_registry write_registry sudo]
expect(subject.all_available_scopes).to eq %i[api trigger_api read_user read_api read_repository write_repository read_registry write_registry sudo]
end
it 'contains for non-admin user all non-default scopes without ADMIN access' do
stub_container_registry_config(enabled: true)
user = create(:user, admin: false)
expect(subject.available_scopes_for(user)).to eq %i[api read_user read_api read_repository write_repository read_registry write_registry]
expect(subject.available_scopes_for(user)).to eq %i[api trigger_api read_user read_api read_repository write_repository read_registry write_registry]
end
it 'contains for admin user all non-default scopes with ADMIN access' do
stub_container_registry_config(enabled: true)
user = create(:user, admin: true)
expect(subject.available_scopes_for(user)).to eq %i[api read_user read_api read_repository write_repository read_registry write_registry sudo]
expect(subject.available_scopes_for(user)).to eq %i[api trigger_api read_user read_api read_repository write_repository read_registry write_registry sudo]
end
context 'registry_scopes' do
......
......@@ -92,4 +92,47 @@
end
end
end
describe 'when project access token has only api_trigger scope' do
let_it_be(:project) { create(:hg_git_project, :hg_repository) }
let_it_be(:mirror) { create(:remote_mirror, project: project, enabled: true, hg_mirror_type: 'hg-pull') }
let_it_be(:project_bot_user) { create(:user, :project_bot) }
let_it_be(:token) { create(:personal_access_token, user: project_bot_user, scopes: [:trigger_api]) }
before_all do
# This is what the creation of a project token sets
project.add_maintainer(project_bot_user)
end
it 'does authorize user for mirrors listing' do
# Granularity of api scopes appears to be at class level, so mirrors
# are still reachable
get api("/projects/#{project.id}/remote_mirrors", personal_access_token: token)
expect(response).to have_gitlab_http_status(:ok)
end
it 'does not authorize user for members listing' do
get api("/projects/#{project.id}/members/all", personal_access_token: token)
expect(response).to have_gitlab_http_status(:forbidden)
end
it 'does not authorize user for hgrc editing' do
put api("/projects/#{project.id}/hgrc", personal_access_token: token), params: {allow_bookmarks: true}
expect(response).to have_gitlab_http_status(:forbidden)
end
it 'does not authorize user for project delete' do
delete api("/projects/#{project.id}", personal_access_token: token)
expect(response).to have_gitlab_http_status(:forbidden)
end
it 'does authorize user for mirror trigger put' do
put api("/projects/#{project.id}/remote_mirrors/#{mirror.id}/trigger", personal_access_token: token)
expect(response).to have_gitlab_http_status(:no_content)
end
end
end
......@@ -189,7 +189,7 @@ def request_user_info!
expect(response).to have_gitlab_http_status(:ok)
expect(json_response['issuer']).to eq('http://localhost')
expect(json_response['jwks_uri']).to eq('http://www.example.com/oauth/discovery/keys')
expect(json_response['scopes_supported']).to eq(%w[api read_user read_api read_repository write_repository sudo openid profile email])
expect(json_response['scopes_supported']).to eq(%w[api read_user read_api trigger_api read_repository write_repository sudo openid profile email])
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