Commit 630018c5 authored by Igor Drozdov's avatar Igor Drozdov

Merge branch '235765-enable-project-access-tokens-gitlab-com' into 'master'

Enable project access tokens on GitLab.com

See merge request gitlab-org/gitlab!43190
parents 6900b532 f65f21c3
......@@ -782,8 +782,6 @@ module ProjectsHelper
end
def project_access_token_available?(project)
return false if ::Gitlab.com?
can?(current_user, :admin_resource_access_tokens, project)
end
end
......
......@@ -10,7 +10,6 @@ module ResourceAccessTokens
end
def execute
return unless feature_enabled?
return error("User does not have permission to create #{resource_type} Access Token") unless has_permission_to_create?
user = create_user
......@@ -31,10 +30,6 @@ module ResourceAccessTokens
attr_reader :resource_type, :resource
def feature_enabled?
return true unless ::Gitlab.com?
end
def has_permission_to_create?
%w(project group).include?(resource_type) && can?(current_user, :admin_resource_access_tokens, resource)
end
......
---
title: Enable project access tokens on GitLab.com
merge_request: 43190
author:
type: changed
......@@ -83,7 +83,11 @@ There are several ways to authenticate with the GitLab API:
1. [OAuth2 tokens](#oauth2-tokens)
1. [Personal access tokens](../user/profile/personal_access_tokens.md)
1. [Project access tokens](../user/project/settings/project_access_tokens.md) **(CORE ONLY)**
1. [Project access tokens](../user/project/settings/project_access_tokens.md)
NOTE: **Note:**
Project access tokens are supported for self-managed instances on Core and above. They are also supported on GitLab.com Bronze and above.
1. [Session cookie](#session-cookie)
1. [GitLab CI/CD job token](#gitlab-ci-job-token) **(Specific endpoints only)**
......
......@@ -5,15 +5,16 @@ info: "To determine the technical writer assigned to the Stage/Group associated
type: reference, howto
---
# Project access tokens **(CORE ONLY)**
# Project access tokens
NOTE: **Note:**
Project access tokens are supported for self-managed instances on Core and above. They are also supported on GitLab.com Bronze and above.
> - [Introduced](https://gitlab.com/groups/gitlab-org/-/epics/2587) in GitLab 13.0.
> - It was [deployed](https://gitlab.com/groups/gitlab-org/-/epics/2587) behind a feature flag, disabled by default.
> - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/218722) in GitLab 13.3.
> - It's disabled on GitLab.com.
> - It can be enabled or disabled by project.
> - [Became available on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/235765) in 13.5.
> - It's recommended for production use.
> - For GitLab self-managed instances, GitLab administrators can [disable it](#enable-or-disable-project-access-tokens).
Project access tokens are scoped to a project and can be used to authenticate with the [GitLab API](../../../api/README.md#personalproject-access-tokens). You can also use project access tokens with Git to authenticate over HTTP.
......@@ -74,33 +75,3 @@ the following table.
| `write_registry` | Allows write-access (push) to [container registry](../../packages/container_registry/index.md). |
| `read_repository` | Allows read-only access (pull) to the repository. |
| `write_repository` | Allows read-write access (pull, push) to the repository. |
### Enable or disable project access tokens
Project access tokens are deployed behind a feature flag that is **enabled by default**.
[GitLab administrators with access to the GitLab Rails console](../../../administration/feature_flags.md)
can disable it for your instance, globally or by project.
To disable it globally:
```ruby
Feature.disable(:resource_access_token)
```
To disable it for a specific project:
```ruby
Feature.disable(:resource_access_token, project)
```
To enable it globally:
```ruby
Feature.enable(:resource_access_token)
```
To enable it for a specific project:
```ruby
Feature.enable(:resource_access_token, project)
```
......@@ -4,36 +4,35 @@ require 'spec_helper'
RSpec.describe Projects::Settings::AccessTokensController do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
before_all do
project.add_maintainer(user)
end
let_it_be(:group) { create(:group_with_plan, plan: :bronze_plan) }
let_it_be(:project) { create(:project, group: group) }
let_it_be(:bot_user) { create(:user, :project_bot) }
before do
allow(Gitlab).to receive(:com?).and_return(true)
stub_ee_application_setting(should_check_namespace_plan: true)
sign_in(user)
end
shared_examples 'feature unavailable' do
context 'on GitLab.com' do
before do
allow(Gitlab).to receive(:com?).and_return(true)
end
context 'with a free plan' do
it { is_expected.to have_gitlab_http_status(:not_found) }
end
before_all do
project.add_maintainer(bot_user)
project.add_maintainer(user)
end
context 'with a paid group plan' do
let_it_be(:group) { create(:group_with_plan, plan: :bronze_plan) }
let_it_be(:project) { create(:project, group: group) }
shared_examples 'feature unavailable' do
context 'with a free plan' do
let(:group) { create(:group_with_plan, plan: :free_plan) }
let(:project) { create(:project, group: group) }
before do
project.add_developer(user)
end
it { is_expected.to have_gitlab_http_status(:not_found) }
end
it { is_expected.to have_gitlab_http_status(:not_found) }
context 'when user is not a maintainer with a paid group plan' do
before do
project.add_developer(user)
end
it { is_expected.to have_gitlab_http_status(:not_found) }
end
end
......@@ -41,26 +40,24 @@ RSpec.describe Projects::Settings::AccessTokensController do
subject { get :index, params: { namespace_id: project.namespace, project_id: project } }
it_behaves_like 'feature unavailable'
it_behaves_like 'project access tokens available #index'
end
describe '#create', :clean_gitlab_redis_shared_state do
subject { post :create, params: { namespace_id: project.namespace, project_id: project }.merge(project_access_token: access_token_params) }
describe '#create' do
let_it_be(:access_token_params) { { name: 'Nerd bot', scopes: ["api"], expires_at: Date.today + 1.month } }
let_it_be(:access_token_params) { {} }
subject { post :create, params: { namespace_id: project.namespace, project_id: project }.merge(project_access_token: access_token_params) }
it_behaves_like 'feature unavailable'
it_behaves_like 'project access tokens available #create'
end
describe '#revoke' do
let_it_be(:bot_user) { create(:user, :project_bot) }
let_it_be(:project_access_token) { create(:personal_access_token, user: bot_user) }
describe '#revoke', :sidekiq_inline do
let(:project_access_token) { create(:personal_access_token, user: bot_user) }
subject { put :revoke, params: { namespace_id: project.namespace, project_id: project, id: project_access_token } }
before_all do
project.add_maintainer(bot_user)
end
it_behaves_like 'feature unavailable'
it_behaves_like 'project access tokens available #revoke'
end
end
......@@ -5,9 +5,11 @@ require('spec_helper')
RSpec.describe Projects::Settings::AccessTokensController do
let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) }
let_it_be(:bot_user) { create(:user, :project_bot) }
before_all do
project.add_maintainer(user)
project.add_maintainer(bot_user)
end
before do
......@@ -15,168 +17,37 @@ RSpec.describe Projects::Settings::AccessTokensController do
end
shared_examples 'feature unavailable' do
let_it_be(:project) { create(:project) }
context 'user is not a maintainer' do
before do
project.add_developer(user)
end
before do
allow(Gitlab).to receive(:com?).and_return(false)
project.add_developer(user)
it { is_expected.to have_gitlab_http_status(:not_found) }
end
it { is_expected.to have_gitlab_http_status(:not_found) }
end
describe '#index' do
subject { get :index, params: { namespace_id: project.namespace, project_id: project } }
it_behaves_like 'feature unavailable'
context 'when feature is available' do
let_it_be(:bot_user) { create(:user, :project_bot) }
let_it_be(:active_project_access_token) { create(:personal_access_token, user: bot_user) }
let_it_be(:inactive_project_access_token) { create(:personal_access_token, :revoked, user: bot_user) }
before_all do
project.add_maintainer(bot_user)
end
before do
enable_feature
end
it 'retrieves active project access tokens' do
subject
expect(assigns(:active_project_access_tokens)).to contain_exactly(active_project_access_token)
end
it 'retrieves inactive project access tokens' do
subject
expect(assigns(:inactive_project_access_tokens)).to contain_exactly(inactive_project_access_token)
end
it 'lists all available scopes' do
subject
expect(assigns(:scopes)).to eq(Gitlab::Auth.resource_bot_scopes)
end
it 'retrieves newly created personal access token value' do
token_value = 'random-value'
allow(PersonalAccessToken).to receive(:redis_getdel).with("#{user.id}:#{project.id}").and_return(token_value)
subject
expect(assigns(:new_project_access_token)).to eq(token_value)
end
end
it_behaves_like 'project access tokens available #index'
end
describe '#create', :clean_gitlab_redis_shared_state do
subject { post :create, params: { namespace_id: project.namespace, project_id: project }.merge(project_access_token: access_token_params) }
describe '#create' do
let(:access_token_params) { { name: 'Nerd bot', scopes: ["api"], expires_at: Date.today + 1.month } }
let_it_be(:access_token_params) { {} }
subject { post :create, params: { namespace_id: project.namespace, project_id: project }.merge(project_access_token: access_token_params) }
it_behaves_like 'feature unavailable'
context 'when feature is available' do
let_it_be(:access_token_params) { { name: 'Nerd bot', scopes: ["api"], expires_at: 1.month.since.to_date } }
before do
enable_feature
end
def created_token
PersonalAccessToken.order(:created_at).last
end
it 'returns success message' do
subject
expect(response.flash[:notice]).to match(/\AYour new project access token has been created./i)
end
it 'creates project access token' do
subject
expect(created_token.name).to eq(access_token_params[:name])
expect(created_token.scopes).to eq(access_token_params[:scopes])
expect(created_token.expires_at).to eq(access_token_params[:expires_at])
end
it 'creates project bot user' do
subject
expect(created_token.user).to be_project_bot
end
it 'stores newly created token redis store' do
expect(PersonalAccessToken).to receive(:redis_store!)
subject
end
it { expect { subject }.to change { User.count }.by(1) }
it { expect { subject }.to change { PersonalAccessToken.count }.by(1) }
context 'when unsuccessful' do
before do
allow_next_instance_of(ResourceAccessTokens::CreateService) do |service|
allow(service).to receive(:execute).and_return ServiceResponse.error(message: 'Failed!')
end
end
it { expect(subject).to render_template(:index) }
end
end
it_behaves_like 'project access tokens available #create'
end
describe '#revoke' do
subject { put :revoke, params: { namespace_id: project.namespace, project_id: project, id: project_access_token } }
let_it_be(:bot_user) { create(:user, :project_bot) }
let_it_be(:project_access_token) { create(:personal_access_token, user: bot_user) }
describe '#revoke', :sidekiq_inline do
let(:project_access_token) { create(:personal_access_token, user: bot_user) }
before_all do
project.add_maintainer(bot_user)
end
subject { put :revoke, params: { namespace_id: project.namespace, project_id: project, id: project_access_token } }
it_behaves_like 'feature unavailable'
context 'when feature is available', :sidekiq_inline do
before do
enable_feature
end
it 'calls delete user worker' do
expect(DeleteUserWorker).to receive(:perform_async).with(user.id, bot_user.id, skip_authorization: true)
subject
end
it 'removed membership of bot user' do
subject
expect(project.reload.bots).not_to include(bot_user)
end
it 'converts issuables of the bot user to ghost user' do
issue = create(:issue, author: bot_user)
subject
expect(issue.reload.author.ghost?).to be true
end
it 'deletes project bot user' do
subject
expect(User.exists?(bot_user.id)).to be_falsy
end
end
end
def enable_feature
allow(Gitlab).to receive(:com?).and_return(false)
it_behaves_like 'project access tokens available #revoke'
end
end
......@@ -24,17 +24,6 @@ RSpec.describe ResourceAccessTokens::CreateService do
end
end
# Remove this shared example when https://gitlab.com/gitlab-org/gitlab/-/merge_requests/43190 merges
shared_examples 'fails on gitlab.com' do
before do
allow(Gitlab).to receive(:com?) { true }
end
it 'returns nil' do
expect(subject).to be nil
end
end
shared_examples 'allows creation of bot with valid params' do
it { expect { subject }.to change { User.count }.by(1) }
......@@ -192,7 +181,6 @@ RSpec.describe ResourceAccessTokens::CreateService do
let_it_be(:resource) { project }
it_behaves_like 'fails when user does not have the permission to create a Resource Bot'
it_behaves_like 'fails on gitlab.com'
context 'user with valid permission' do
before_all do
......
# frozen_string_literal: true
RSpec.shared_examples 'project access tokens available #index' do
let_it_be(:active_project_access_token) { create(:personal_access_token, user: bot_user) }
let_it_be(:inactive_project_access_token) { create(:personal_access_token, :revoked, user: bot_user) }
it 'retrieves active project access tokens' do
subject
expect(assigns(:active_project_access_tokens)).to contain_exactly(active_project_access_token)
end
it 'retrieves inactive project access tokens' do
subject
expect(assigns(:inactive_project_access_tokens)).to contain_exactly(inactive_project_access_token)
end
it 'lists all available scopes' do
subject
expect(assigns(:scopes)).to eq(Gitlab::Auth.resource_bot_scopes)
end
it 'retrieves newly created personal access token value' do
token_value = 'random-value'
allow(PersonalAccessToken).to receive(:redis_getdel).with("#{user.id}:#{project.id}").and_return(token_value)
subject
expect(assigns(:new_project_access_token)).to eq(token_value)
end
end
RSpec.shared_examples 'project access tokens available #create' do
def created_token
PersonalAccessToken.order(:created_at).last
end
it 'returns success message' do
subject
expect(response.flash[:notice]).to match('Your new project access token has been created.')
end
it 'creates project access token' do
subject
expect(created_token.name).to eq(access_token_params[:name])
expect(created_token.scopes).to eq(access_token_params[:scopes])
expect(created_token.expires_at).to eq(access_token_params[:expires_at])
end
it 'creates project bot user' do
subject
expect(created_token.user).to be_project_bot
end
it 'stores newly created token redis store' do
expect(PersonalAccessToken).to receive(:redis_store!)
subject
end
it { expect { subject }.to change { User.count }.by(1) }
it { expect { subject }.to change { PersonalAccessToken.count }.by(1) }
context 'when unsuccessful' do
before do
allow_next_instance_of(ResourceAccessTokens::CreateService) do |service|
allow(service).to receive(:execute).and_return ServiceResponse.error(message: 'Failed!')
end
end
it { expect(subject).to render_template(:index) }
end
end
RSpec.shared_examples 'project access tokens available #revoke' do
it 'calls delete user worker' do
expect(DeleteUserWorker).to receive(:perform_async).with(user.id, bot_user.id, skip_authorization: true)
subject
end
it 'removes membership of bot user' do
subject
expect(project.reload.bots).not_to include(bot_user)
end
it 'converts issuables of the bot user to ghost user' do
issue = create(:issue, author: bot_user)
subject
expect(issue.reload.author.ghost?).to be true
end
it 'deletes project bot user' do
subject
expect(User.exists?(bot_user.id)).to be_falsy
end
end
......@@ -323,10 +323,10 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
allow(Gitlab).to receive(:com?).and_return(true)
end
it 'does not display "Access Tokens" nav item' do
it 'displays "Access Tokens" nav item' do
render
expect(rendered).not_to have_link('Access Tokens', href: project_settings_access_tokens_path(project))
expect(rendered).to have_link('Access Tokens', href: project_settings_access_tokens_path(project))
end
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