From 093b552432fa5e49fdee828f6ac8d182a14e7ce4 Mon Sep 17 00:00:00 2001 From: Alex Buijs Date: Mon, 18 Dec 2023 18:31:33 +0100 Subject: [PATCH] Add Manage group access tokens custom permission This adds "Manage group access tokens" as a customizable permission, so that it can be added onto any base role. Changelog: added EE: true --- ...age_group_access_tokens_to_member_roles.rb | 10 +++ db/schema_migrations/20231218172621 | 1 + db/structure.sql | 1 + doc/api/graphql/reference/index.md | 3 + doc/api/member_roles.md | 4 ++ doc/user/custom_roles.md | 2 + .../graphql/mutations/member_roles/create.rb | 4 ++ .../types/member_roles/member_role_type.rb | 6 ++ ee/app/policies/ee/group_policy.rb | 19 ++++++ .../manage_group_access_tokens.yml | 10 +++ .../ee/sidebars/groups/menus/settings_menu.rb | 5 ++ .../lib/ee/api/entities/member_role_spec.rb | 1 + .../groups/menus/settings_menu_spec.rb | 22 ++++++ ee/spec/models/ee/user_spec.rb | 1 + ee/spec/policies/group_policy_spec.rb | 31 +++++++++ ee/spec/requests/api/member_roles_spec.rb | 2 + .../request_spec.rb | 68 +++++++++++++++++++ lib/sidebars/groups/menus/settings_menu.rb | 4 ++ 18 files changed, 194 insertions(+) create mode 100644 db/migrate/20231218172621_add_manage_group_access_tokens_to_member_roles.rb create mode 100644 db/schema_migrations/20231218172621 create mode 100644 ee/config/custom_abilities/manage_group_access_tokens.yml create mode 100644 ee/spec/requests/custom_roles/manage_group_access_tokens/request_spec.rb diff --git a/db/migrate/20231218172621_add_manage_group_access_tokens_to_member_roles.rb b/db/migrate/20231218172621_add_manage_group_access_tokens_to_member_roles.rb new file mode 100644 index 00000000000000..58f9f94a880c3b --- /dev/null +++ b/db/migrate/20231218172621_add_manage_group_access_tokens_to_member_roles.rb @@ -0,0 +1,10 @@ +# frozen_string_literal: true + +class AddManageGroupAccessTokensToMemberRoles < Gitlab::Database::Migration[2.2] + milestone '16.8' + enable_lock_retries! + + def change + add_column :member_roles, :manage_group_access_tokens, :boolean, default: false, null: false + end +end diff --git a/db/schema_migrations/20231218172621 b/db/schema_migrations/20231218172621 new file mode 100644 index 00000000000000..c5fff4c0072389 --- /dev/null +++ b/db/schema_migrations/20231218172621 @@ -0,0 +1 @@ +9a5a5ecda3186fb4ef642ff56d5e3125bfe888e1a903bfdb8cfcef6827c41df6 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index 2ce0b1daa76c79..7d1cdf75cf1cd6 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -18621,6 +18621,7 @@ CREATE TABLE member_roles ( admin_group_member boolean DEFAULT false NOT NULL, manage_project_access_tokens boolean DEFAULT false NOT NULL, archive_project boolean DEFAULT false NOT NULL, + manage_group_access_tokens boolean DEFAULT false NOT NULL, remove_project boolean DEFAULT false NOT NULL, CONSTRAINT check_4364846f58 CHECK ((char_length(description) <= 255)), CONSTRAINT check_9907916995 CHECK ((char_length(name) <= 255)) diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 621ac03078362b..2b43d0a7c567ff 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -5377,6 +5377,7 @@ Input type: `MemberRoleCreateInput` | `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | `description` | [`String`](#string) | Description of the member role. | | `groupPath` | [`ID`](#id) | Group the member role to mutate is in. Required for SaaS. | +| `manageGroupAccessTokens` | [`Boolean`](#boolean) | Permission to admin group access tokens. | | `manageProjectAccessTokens` | [`Boolean`](#boolean) | Permission to admin project access tokens. | | `name` | [`String`](#string) | Name of the member role. | | `permissions` | [`[MemberRolePermission!]`](#memberrolepermission) | List of all customizable permissions. | @@ -21452,6 +21453,7 @@ Represents a member role. | `description` | [`String`](#string) | Description of the member role. | | `enabledPermissions` **{warning-solid}** | [`[MemberRolePermission!]`](#memberrolepermission) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Array of all permissions enabled for the custom role. | | `id` | [`MemberRoleID!`](#memberroleid) | ID of the member role. | +| `manageGroupAccessTokens` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 16.8. This feature is an Experiment. It can be changed or removed at any time. Permission to admin group access tokens. | | `manageProjectAccessTokens` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Permission to admin project access tokens. | | `membersCount` **{warning-solid}** | [`Int!`](#int) | **Introduced** in 16.7. This feature is an Experiment. It can be changed or removed at any time. Total number of members with the custom role. | | `name` | [`String!`](#string) | Name of the member role. | @@ -30807,6 +30809,7 @@ Member role permission. | `ADMIN_MERGE_REQUEST` | Allows to approve merge requests. | | `ADMIN_VULNERABILITY` | Allows admin access to the vulnerability reports. | | `ARCHIVE_PROJECT` | Allows to archive projects. | +| `MANAGE_GROUP_ACCESS_TOKENS` | Allows manage access to the group access tokens. | | `MANAGE_PROJECT_ACCESS_TOKENS` | Allows manage access to the project access tokens. | | `READ_CODE` | Allows read-only access to the source code. | | `READ_DEPENDENCY` | Allows read-only access to the dependencies. | diff --git a/doc/api/member_roles.md b/doc/api/member_roles.md index a482195b94e39f..2fd10d99fda89d 100644 --- a/doc/api/member_roles.md +++ b/doc/api/member_roles.md @@ -18,6 +18,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - [Manage project access tokens introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132342) in GitLab 16.5 in [with a flag](../administration/feature_flags.md) named `manage_project_access_tokens`. Disabled by default. > - [Archive project introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134998) in GitLab 16.7. > - [Delete project introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/139696) in GitLab 16.8. +> - [Manage group access tokens introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140115) in GitLab 16.8. FLAG: On self-managed GitLab, by default these features are not available. To make them available, an administrator can [enable the feature flags](../administration/feature_flags.md) named `admin_group_member` and `manage_project_access_tokens`. @@ -53,6 +54,7 @@ If successful, returns [`200`](rest/index.md#status-codes) and the following res | `[].manage_project_access_tokens` | boolean | Permission to manage project access tokens. | | `[].archive_project` | boolean | Permission to archive projects. | | `[].remove_project` | boolean | Permission to delete projects. | +| `[].manage_group_access_tokens` | boolean | Permission to manage group access tokens. | Example request: @@ -75,6 +77,7 @@ Example response: "read_code": true, "read_dependency": false, "read_vulnerability": false, + "manage_group_access_tokens": false, "manage_project_access_tokens": false, "archive_project": false, "remove_project": false @@ -90,6 +93,7 @@ Example response: "read_code": false, "read_dependency": true, "read_vulnerability": true, + "manage_group_access_tokens": false, "manage_project_access_tokens": false, "archive_project": false, "remove_project": false diff --git a/doc/user/custom_roles.md b/doc/user/custom_roles.md index ac04e7cca0f1c6..3ab4e31859c30e 100644 --- a/doc/user/custom_roles.md +++ b/doc/user/custom_roles.md @@ -18,6 +18,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - Ability to archive projects [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/425957) in GitLab 16.7. > - Ability to use the UI to add a user to your group with a custom role, change a user's custom role, or remove a custom role from a group member [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/393239) in GitLab 16.7. > - Ability to delete projects [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/425959) in GitLab 16.8. +> - Ability to manage group access tokens [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/428353) in GitLab 16.8. Custom roles allow group Owners or instance administrators to create roles specific to the needs of their organization. @@ -117,6 +118,7 @@ These requirements are documented in the `Required permission` column in the fol | `admin_group_member` | GitLab 16.5 and later | Not applicable | Add or remove [group members](group/manage.md). | | `archive_project` | GitLab 16.7 and later | Not applicable | [Archive and unarchive projects](project/settings/migrate_projects.md#archive-a-project). | | `remove_project` | GitLab 16.8 and later | Not applicable | [Delete projects](project/working_with_projects.md#delete-a-project). | +| `manage_group_access_tokens` | GitLab 16.8 and later | Not applicable | [Create, delete, and list group access tokens](group/settings/group_access_tokens.md). | ## Billing and seat usage diff --git a/ee/app/graphql/mutations/member_roles/create.rb b/ee/app/graphql/mutations/member_roles/create.rb index 46ee30ca84ea6c..45f2ae412fe16e 100644 --- a/ee/app/graphql/mutations/member_roles/create.rb +++ b/ee/app/graphql/mutations/member_roles/create.rb @@ -30,6 +30,10 @@ class Create < Base argument :group_path, GraphQL::Types::ID, required: ::Gitlab::Saas.feature_available?(:gitlab_saas_subscriptions), description: 'Group the member role to mutate is in. Required for SaaS.' + argument :manage_group_access_tokens, + GraphQL::Types::Boolean, + required: false, + description: 'Permission to admin group access tokens.' argument :manage_project_access_tokens, GraphQL::Types::Boolean, required: false, diff --git a/ee/app/graphql/types/member_roles/member_role_type.rb b/ee/app/graphql/types/member_roles/member_role_type.rb index 8d02826b1e70ef..809e320a4bca72 100644 --- a/ee/app/graphql/types/member_roles/member_role_type.rb +++ b/ee/app/graphql/types/member_roles/member_role_type.rb @@ -53,6 +53,12 @@ class MemberRoleType < BaseObject alpha: { milestone: '16.8' }, description: 'Permission to delete projects.' + field :manage_group_access_tokens, + GraphQL::Types::Boolean, + null: true, + alpha: { milestone: '16.8' }, + description: 'Permission to admin group access tokens.' + field :manage_project_access_tokens, GraphQL::Types::Boolean, null: true, diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb index 33f0c8dc91b5e8..26155b4c656048 100644 --- a/ee/app/policies/ee/group_policy.rb +++ b/ee/app/policies/ee/group_policy.rb @@ -227,6 +227,15 @@ module GroupPolicy ).has_ability? end + desc "Custom role on group that enables manage group access tokens" + condition(:role_enables_manage_group_access_tokens) do + ::Auth::MemberRoleAbilityLoader.new( + user: @user, + resource: @subject, + ability: :manage_group_access_tokens + ).has_ability? + end + rule { owner & unique_project_download_limit_enabled }.policy do enable :ban_group_member end @@ -497,6 +506,16 @@ module GroupPolicy enable :destroy_group_member end + rule { custom_roles_allowed & role_enables_manage_group_access_tokens & resource_access_token_feature_available }.policy do + enable :read_resource_access_tokens + enable :destroy_resource_access_tokens + end + + rule { custom_roles_allowed & role_enables_manage_group_access_tokens & resource_access_token_creation_allowed }.policy do + enable :create_resource_access_tokens + enable :manage_resource_access_tokens + end + rule { custom_roles_allowed & owner }.policy do enable :admin_member_role end diff --git a/ee/config/custom_abilities/manage_group_access_tokens.yml b/ee/config/custom_abilities/manage_group_access_tokens.yml new file mode 100644 index 00000000000000..5305ce1bae43f5 --- /dev/null +++ b/ee/config/custom_abilities/manage_group_access_tokens.yml @@ -0,0 +1,10 @@ +--- +name: manage_group_access_tokens +description: Allows manage access to the group access tokens. +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/428353 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/140115 +feature_category: system_access +milestone: '16.8' +group_ability: true +project_ability: false +requirement: '' diff --git a/ee/lib/ee/sidebars/groups/menus/settings_menu.rb b/ee/lib/ee/sidebars/groups/menus/settings_menu.rb index 1bacaf2b6e67b9..b0877743c6b641 100644 --- a/ee/lib/ee/sidebars/groups/menus/settings_menu.rb +++ b/ee/lib/ee/sidebars/groups/menus/settings_menu.rb @@ -21,6 +21,11 @@ def configure_menu_items add_item(billing_menu_item) add_item(reporting_menu_item) else + if can?(context.current_user, :read_resource_access_tokens, context.group) + # Managing group acccess tokens is a custom ability independent of the access level. + add_item(access_tokens_menu_item) + end + if can?(context.current_user, :change_push_rules, context.group) # Push Rules are the only group setting that can also be edited by maintainers. # They only get the Repository settings which only show the Push Rules section for maintainers. diff --git a/ee/spec/lib/ee/api/entities/member_role_spec.rb b/ee/spec/lib/ee/api/entities/member_role_spec.rb index 6b1fe24b342650..8bdc4f85897ad3 100644 --- a/ee/spec/lib/ee/api/entities/member_role_spec.rb +++ b/ee/spec/lib/ee/api/entities/member_role_spec.rb @@ -20,6 +20,7 @@ expect(subject[:read_code]).to eq member_role.read_code expect(subject[:read_vulnerability]).to eq member_role.read_vulnerability expect(subject[:admin_vulnerability]).to eq member_role.admin_vulnerability + expect(subject[:manage_group_access_tokens]).to eq member_role.manage_group_access_tokens expect(subject[:manage_project_access_tokens]).to eq member_role.manage_project_access_tokens expect(subject[:archive_project]).to eq member_role.archive_project expect(subject[:remove_project]).to eq member_role.remove_project diff --git a/ee/spec/lib/ee/sidebars/groups/menus/settings_menu_spec.rb b/ee/spec/lib/ee/sidebars/groups/menus/settings_menu_spec.rb index 23a456116822a2..82a25f8f657555 100644 --- a/ee/spec/lib/ee/sidebars/groups/menus/settings_menu_spec.rb +++ b/ee/spec/lib/ee/sidebars/groups/menus/settings_menu_spec.rb @@ -272,5 +272,27 @@ end end end + + context 'for user with `read_resource_access_tokens` custom permission', feature_category: :permissions do + let_it_be(:user) { create(:user) } + let_it_be(:role) { create(:member_role, :guest, namespace: group, manage_group_access_tokens: true) } + let_it_be(:member) { create(:group_member, :guest, member_role: role, user: user, group: group) } + + subject { menu.renderable_items.find { |e| e.item_id == item_id } } + + before do + stub_licensed_features(custom_roles: true) + end + + describe 'Access Tokens menu item' do + let(:item_id) { :access_tokens } + + it { is_expected.to be_present } + + it 'does not show any other menu items' do + expect(menu.renderable_items.length).to equal(1) + end + end + end end end diff --git a/ee/spec/models/ee/user_spec.rb b/ee/spec/models/ee/user_spec.rb index a70da766584c49..001a295da47d95 100644 --- a/ee/spec/models/ee/user_spec.rb +++ b/ee/spec/models/ee/user_spec.rb @@ -1224,6 +1224,7 @@ OR admin_merge_request = true OR admin_vulnerability = true OR archive_project = true + OR manage_group_access_tokens = true OR manage_project_access_tokens = true OR read_dependency = true OR read_vulnerability = true diff --git a/ee/spec/policies/group_policy_spec.rb b/ee/spec/policies/group_policy_spec.rb index bc126e75b5934a..dcb84e5949d7f2 100644 --- a/ee/spec/policies/group_policy_spec.rb +++ b/ee/spec/policies/group_policy_spec.rb @@ -2979,6 +2979,37 @@ def create_member_role(member, abilities = member_role_abilities) it_behaves_like 'custom roles abilities' end + + context 'for a member role with manage_group_access_tokens true' do + let(:member_role_abilities) { { manage_group_access_tokens: true } } + let(:allowed_abilities) do + [:read_resource_access_tokens, :destroy_resource_access_tokens, + :create_resource_access_tokens, :manage_resource_access_tokens] + end + + it_behaves_like 'custom roles abilities' + + context 'when resource access token creation is not allowed' do + before do + create_member_role(group_member_guest) + stub_licensed_features(custom_roles: true) + group.root_ancestor.namespace_settings.update_column(:resource_access_token_creation_allowed, false) + end + + it { is_expected.to be_allowed(:read_resource_access_tokens, :destroy_resource_access_tokens) } + it { is_expected.to be_disallowed(:create_resource_access_tokens, :manage_resource_access_tokens) } + end + + context 'when resource access tokens feature is unavailable' do + before do + create_member_role(group_member_guest) + stub_licensed_features(custom_roles: true) + stub_ee_application_setting(personal_access_tokens_disabled?: true) + end + + it { is_expected.to be_disallowed(*allowed_abilities) } + end + end end context 'for :read_limit_alert' do diff --git a/ee/spec/requests/api/member_roles_spec.rb b/ee/spec/requests/api/member_roles_spec.rb index d451f3b125ab26..cacfa8f9b5e1b0 100644 --- a/ee/spec/requests/api/member_roles_spec.rb +++ b/ee/spec/requests/api/member_roles_spec.rb @@ -105,6 +105,7 @@ "admin_group_member" => false, "admin_merge_request" => false, "admin_vulnerability" => false, + "manage_group_access_tokens" => false, "manage_project_access_tokens" => false, "archive_project" => false, "remove_project" => false, @@ -121,6 +122,7 @@ "admin_group_member" => false, "admin_merge_request" => true, "admin_vulnerability" => false, + "manage_group_access_tokens" => false, "manage_project_access_tokens" => false, "archive_project" => false, "remove_project" => false, diff --git a/ee/spec/requests/custom_roles/manage_group_access_tokens/request_spec.rb b/ee/spec/requests/custom_roles/manage_group_access_tokens/request_spec.rb new file mode 100644 index 00000000000000..eb606a902c373e --- /dev/null +++ b/ee/spec/requests/custom_roles/manage_group_access_tokens/request_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User with manage_group_access_tokens custom role', feature_category: :permissions do + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + + before do + stub_licensed_features(custom_roles: true) + sign_in(user) + end + + describe Groups::Settings::AccessTokensController do + let_it_be(:role) { create(:member_role, :guest, namespace: group, manage_group_access_tokens: true) } + let_it_be(:member) { create(:group_member, :guest, member_role: role, user: user, group: group) } + + describe '#index' do + it 'user has access via custom role' do + get group_settings_access_tokens_path(group) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:index) + end + end + + describe '#create' do + let_it_be(:access_token_params) { { name: 'TestToken', scopes: ['api'], expires_at: Date.today + 1.month } } + let_it_be(:resource) { group } + + subject(:request) do + post group_settings_access_tokens_path(group, params: { resource_access_token: access_token_params }) + end + + it 'user has access via a custom role' do + request + + expect(response).to have_gitlab_http_status(:ok) + end + + it_behaves_like 'POST resource access tokens available' + end + + describe '#revoke' do + let_it_be(:access_token_user) { create(:user, :project_bot) } + let_it_be(:group_member) { create(:group_member, user: access_token_user, group: group) } + let_it_be(:resource_access_token) { create(:personal_access_token, user: access_token_user) } + + subject(:request) { put revoke_group_settings_access_token_path(group, resource_access_token) } + + it 'user has access via a custom role' do + request + + expect(resource_access_token.reload).to be_revoked + expect(response).to have_gitlab_http_status(:redirect) + end + end + + describe GroupsController do + it 'user has access via custom role' do + get group_path(group) + + expect(response).to have_gitlab_http_status(:ok) + expect(response.body).to include('Access Tokens') + end + end + end +end diff --git a/lib/sidebars/groups/menus/settings_menu.rb b/lib/sidebars/groups/menus/settings_menu.rb index ece6460bb89525..293383f8eacec7 100644 --- a/lib/sidebars/groups/menus/settings_menu.rb +++ b/lib/sidebars/groups/menus/settings_menu.rb @@ -25,6 +25,10 @@ def configure_menu_items # Billing is the only group setting that is visible to auditors. # Create an empty sub-menu here and EE adds Settings menu item (with only Billing). return true + elsif Gitlab.ee? && can?(context.current_user, :read_resource_access_tokens, context.group) + # Managing group acccess tokens is a custom ability independent of the access level. + # Create an empty sub-menu here and EE adds Settings menu item (with only Billing). + return true end false -- GitLab