Commit f65f21c3 authored by sfang97's avatar sfang97

Enable project access tokens on .com

Rebase with master
Fix sidebar spec
Remove another feature_enabled blocker
Change double quote to single quote

Created by isn't available yet

Add note to Core badge

Remove merge conflict

Set feature_enabled to true

Remove feature disabled shared example

Address MR review comments

Address TW comments

Add EE specs

Free vs paid .com

Don't need parantheses

Add self-managed specs to ee

Master version of ee access token spec

Add space to readme

Stub should check namespace plan

Add more ee specs

Fix indentation of shared example

Remove .com reference

Fix ee spec

Apply some MR suggestions

Change date format

Project access token shared example

Stub non ee application setting

Shared example cleanup- still failing

Change shared example nam

Fix feature unavailable specs

Remove stub namespace check

Add back stub check namespace plan

Stub application setting

Change test back to context

Add back stub ee app setting

Restructure shared texamples file

Use multiple Rspec.shared_examples, one for each method. Also test
feature unavailable

Restructure shared examples

Test feature unavailable

Add changelog entry

Add changelog entry

Revert "Add changelog entry"

This reverts commit 17c94b23043af6c37350cf0aab7c917123ffb02d.
parent 15205430
......@@ -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