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 ...@@ -782,8 +782,6 @@ module ProjectsHelper
end end
def project_access_token_available?(project) def project_access_token_available?(project)
return false if ::Gitlab.com?
can?(current_user, :admin_resource_access_tokens, project) can?(current_user, :admin_resource_access_tokens, project)
end end
end end
......
...@@ -10,7 +10,6 @@ module ResourceAccessTokens ...@@ -10,7 +10,6 @@ module ResourceAccessTokens
end end
def execute def execute
return unless feature_enabled?
return error("User does not have permission to create #{resource_type} Access Token") unless has_permission_to_create? return error("User does not have permission to create #{resource_type} Access Token") unless has_permission_to_create?
user = create_user user = create_user
...@@ -31,10 +30,6 @@ module ResourceAccessTokens ...@@ -31,10 +30,6 @@ module ResourceAccessTokens
attr_reader :resource_type, :resource attr_reader :resource_type, :resource
def feature_enabled?
return true unless ::Gitlab.com?
end
def has_permission_to_create? def has_permission_to_create?
%w(project group).include?(resource_type) && can?(current_user, :admin_resource_access_tokens, resource) %w(project group).include?(resource_type) && can?(current_user, :admin_resource_access_tokens, resource)
end 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: ...@@ -83,7 +83,11 @@ There are several ways to authenticate with the GitLab API:
1. [OAuth2 tokens](#oauth2-tokens) 1. [OAuth2 tokens](#oauth2-tokens)
1. [Personal access tokens](../user/profile/personal_access_tokens.md) 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. [Session cookie](#session-cookie)
1. [GitLab CI/CD job token](#gitlab-ci-job-token) **(Specific endpoints only)** 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 ...@@ -5,15 +5,16 @@ info: "To determine the technical writer assigned to the Stage/Group associated
type: reference, howto 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. > - [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. > - 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. > - [Became enabled by default](https://gitlab.com/gitlab-org/gitlab/-/issues/218722) in GitLab 13.3.
> - It's disabled on GitLab.com. > - [Became available on GitLab.com](https://gitlab.com/gitlab-org/gitlab/-/issues/235765) in 13.5.
> - It can be enabled or disabled by project.
> - It's recommended for production use. > - 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. 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. ...@@ -74,33 +75,3 @@ the following table.
| `write_registry` | Allows write-access (push) to [container registry](../../packages/container_registry/index.md). | | `write_registry` | Allows write-access (push) to [container registry](../../packages/container_registry/index.md). |
| `read_repository` | Allows read-only access (pull) to the repository. | | `read_repository` | Allows read-only access (pull) to the repository. |
| `write_repository` | Allows read-write access (pull, push) 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' ...@@ -4,36 +4,35 @@ require 'spec_helper'
RSpec.describe Projects::Settings::AccessTokensController do RSpec.describe Projects::Settings::AccessTokensController do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) } let_it_be(:group) { create(:group_with_plan, plan: :bronze_plan) }
let_it_be(:project) { create(:project, group: group) }
before_all do let_it_be(:bot_user) { create(:user, :project_bot) }
project.add_maintainer(user)
end
before do before do
allow(Gitlab).to receive(:com?).and_return(true)
stub_ee_application_setting(should_check_namespace_plan: true)
sign_in(user) sign_in(user)
end end
shared_examples 'feature unavailable' do before_all do
context 'on GitLab.com' do project.add_maintainer(bot_user)
before do project.add_maintainer(user)
allow(Gitlab).to receive(:com?).and_return(true) end
end
context 'with a free plan' do
it { is_expected.to have_gitlab_http_status(:not_found) }
end
context 'with a paid group plan' do shared_examples 'feature unavailable' do
let_it_be(:group) { create(:group_with_plan, plan: :bronze_plan) } context 'with a free plan' do
let_it_be(:project) { create(:project, group: group) } let(:group) { create(:group_with_plan, plan: :free_plan) }
let(:project) { create(:project, group: group) }
before do it { is_expected.to have_gitlab_http_status(:not_found) }
project.add_developer(user) end
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 end
it { is_expected.to have_gitlab_http_status(:not_found) }
end end
end end
...@@ -41,26 +40,24 @@ RSpec.describe Projects::Settings::AccessTokensController do ...@@ -41,26 +40,24 @@ RSpec.describe Projects::Settings::AccessTokensController do
subject { get :index, params: { namespace_id: project.namespace, project_id: project } } subject { get :index, params: { namespace_id: project.namespace, project_id: project } }
it_behaves_like 'feature unavailable' it_behaves_like 'feature unavailable'
it_behaves_like 'project access tokens available #index'
end end
describe '#create', :clean_gitlab_redis_shared_state do describe '#create' do
subject { post :create, params: { namespace_id: project.namespace, project_id: project }.merge(project_access_token: access_token_params) } 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 'feature unavailable'
it_behaves_like 'project access tokens available #create'
end end
describe '#revoke' do describe '#revoke', :sidekiq_inline do
let_it_be(:bot_user) { create(:user, :project_bot) } let(:project_access_token) { create(:personal_access_token, user: bot_user) }
let_it_be(: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 } } 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 'feature unavailable'
it_behaves_like 'project access tokens available #revoke'
end end
end end
...@@ -5,9 +5,11 @@ require('spec_helper') ...@@ -5,9 +5,11 @@ require('spec_helper')
RSpec.describe Projects::Settings::AccessTokensController do RSpec.describe Projects::Settings::AccessTokensController do
let_it_be(:user) { create(:user) } let_it_be(:user) { create(:user) }
let_it_be(:project) { create(:project) } let_it_be(:project) { create(:project) }
let_it_be(:bot_user) { create(:user, :project_bot) }
before_all do before_all do
project.add_maintainer(user) project.add_maintainer(user)
project.add_maintainer(bot_user)
end end
before do before do
...@@ -15,168 +17,37 @@ RSpec.describe Projects::Settings::AccessTokensController do ...@@ -15,168 +17,37 @@ RSpec.describe Projects::Settings::AccessTokensController do
end end
shared_examples 'feature unavailable' do 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 it { is_expected.to have_gitlab_http_status(:not_found) }
allow(Gitlab).to receive(:com?).and_return(false)
project.add_developer(user)
end end
it { is_expected.to have_gitlab_http_status(:not_found) }
end end
describe '#index' do describe '#index' do
subject { get :index, params: { namespace_id: project.namespace, project_id: project } } subject { get :index, params: { namespace_id: project.namespace, project_id: project } }
it_behaves_like 'feature unavailable' it_behaves_like 'feature unavailable'
it_behaves_like 'project access tokens available #index'
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
end end
describe '#create', :clean_gitlab_redis_shared_state do describe '#create' do
subject { post :create, params: { namespace_id: project.namespace, project_id: project }.merge(project_access_token: access_token_params) } 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' it_behaves_like 'feature unavailable'
it_behaves_like 'project access tokens available #create'
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
end end
describe '#revoke' do describe '#revoke', :sidekiq_inline do
subject { put :revoke, params: { namespace_id: project.namespace, project_id: project, id: project_access_token } } let(:project_access_token) { create(:personal_access_token, user: bot_user) }
let_it_be(:bot_user) { create(:user, :project_bot) }
let_it_be(:project_access_token) { create(:personal_access_token, user: bot_user) }
before_all do subject { put :revoke, params: { namespace_id: project.namespace, project_id: project, id: project_access_token } }
project.add_maintainer(bot_user)
end
it_behaves_like 'feature unavailable' it_behaves_like 'feature unavailable'
it_behaves_like 'project access tokens available #revoke'
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)
end end
end end
...@@ -24,17 +24,6 @@ RSpec.describe ResourceAccessTokens::CreateService do ...@@ -24,17 +24,6 @@ RSpec.describe ResourceAccessTokens::CreateService do
end end
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 shared_examples 'allows creation of bot with valid params' do
it { expect { subject }.to change { User.count }.by(1) } it { expect { subject }.to change { User.count }.by(1) }
...@@ -192,7 +181,6 @@ RSpec.describe ResourceAccessTokens::CreateService do ...@@ -192,7 +181,6 @@ RSpec.describe ResourceAccessTokens::CreateService do
let_it_be(:resource) { project } 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 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 context 'user with valid permission' do
before_all 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 ...@@ -323,10 +323,10 @@ RSpec.describe 'layouts/nav/sidebar/_project' do
allow(Gitlab).to receive(:com?).and_return(true) allow(Gitlab).to receive(:com?).and_return(true)
end end
it 'does not display "Access Tokens" nav item' do it 'displays "Access Tokens" nav item' do
render 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 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