From 78dea256896fd1336ea21b4c105e6f946e51f6d8 Mon Sep 17 00:00:00 2001 From: Alex Buijs Date: Tue, 24 Oct 2023 16:06:10 +0200 Subject: [PATCH 1/3] Add Archive Project custom permission This commit adds "Archive Project" as a customizable permission, so that it can be added onto any base role. Changelog: added EE: true --- app/controllers/projects_controller.rb | 8 +- app/views/projects/edit.html.haml | 215 +++++++++--------- ...444_add_archive_project_to_member_roles.rb | 9 + db/schema_migrations/20231024123444 | 1 + db/structure.sql | 1 + doc/api/graphql/reference/index.md | 3 + doc/api/member_roles.md | 13 +- doc/user/custom_roles.md | 2 + .../components/create_member_role.vue | 13 +- .../components/list_member_roles.vue | 2 +- ee/app/controllers/ee/projects_controller.rb | 4 + .../roles_and_permissions_controller.rb | 1 + .../graphql/mutations/member_roles/create.rb | 4 + .../types/member_roles/member_role_type.rb | 6 + ee/app/models/members/member_role.rb | 6 +- ee/app/policies/ee/project_policy.rb | 17 ++ .../archive_project_custom_permission.yml | 8 + .../sidebars/projects/menus/settings_menu.rb | 27 +++ .../controllers/projects_controller_spec.rb | 25 +- .../archive_project_custom_permission_spec.rb | 89 ++++++++ .../components/create_member_role_spec.js | 15 ++ .../components/list_member_roles_spec.js | 30 ++- .../lib/ee/api/entities/member_role_spec.rb | 3 +- .../projects/menus/settings_menu_spec.rb | 32 +++ ee/spec/models/ee/user_spec.rb | 3 +- ee/spec/policies/project_policy_spec.rb | 23 ++ ee/spec/requests/api/member_roles_spec.rb | 2 + ee/spec/views/projects/edit.html.haml_spec.rb | 23 ++ lib/sidebars/projects/menus/settings_menu.rb | 4 - .../projects/menus/settings_menu_spec.rb | 12 - 30 files changed, 454 insertions(+), 147 deletions(-) create mode 100644 db/migrate/20231024123444_add_archive_project_to_member_roles.rb create mode 100644 db/schema_migrations/20231024123444 create mode 100644 ee/config/feature_flags/development/archive_project_custom_permission.yml create mode 100644 ee/spec/features/projects/custom_roles/archive_project_custom_permission_spec.rb diff --git a/app/controllers/projects_controller.rb b/app/controllers/projects_controller.rb index 6f15bc553bfd4c..cee56dca538045 100644 --- a/app/controllers/projects_controller.rb +++ b/app/controllers/projects_controller.rb @@ -29,7 +29,8 @@ class ProjectsController < Projects::ApplicationController before_action :authorize_read_code!, only: [:refs] # Authorize - before_action :authorize_admin_project!, only: [:edit, :update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] + before_action :authorize_admin_project_or_custom_permissions!, only: :edit + before_action :authorize_admin_project!, only: [:update, :housekeeping, :download_export, :export, :remove_export, :generate_new_export] before_action :authorize_archive_project!, only: [:archive, :unarchive] before_action :event_filter, only: [:show, :activity] @@ -598,6 +599,11 @@ def check_export_rate_limit! def render_edit render 'edit' end + + # Overridden in EE + def authorize_admin_project_or_custom_permissions! + authorize_admin_project! + end end ProjectsController.prepend_mod_with('ProjectsController') diff --git a/app/views/projects/edit.html.haml b/app/views/projects/edit.html.haml index 4e84a6ef7e75c6..fd0dc1178f7754 100644 --- a/app/views/projects/edit.html.haml +++ b/app/views/projects/edit.html.haml @@ -5,116 +5,119 @@ - reduce_visibility_form_id = 'reduce-visibility-form' - @force_desktop_expanded_sidebar = true -= render Pajamas::AlertComponent.new(title: _('GitLab Pages has moved'), +- if can?(current_user, :admin_project, @project) + = render Pajamas::AlertComponent.new(title: _('GitLab Pages has moved'), alert_options: { class: 'gl-my-5', data: { feature_id: Users::CalloutsHelper::PAGES_MOVED_CALLOUT, dismiss_endpoint: callouts_path, defer_links: 'true' } }) do |c| - - c.with_body do - = _('To go to GitLab Pages, on the left sidebar, select %{pages_link}.').html_safe % {pages_link: link_to('Deploy > Pages', project_pages_path(@project)).html_safe} - -%section.settings.general-settings.no-animate.expanded#js-general-settings - .settings-header - %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Naming, topics, avatar') - = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do - = _('Collapse') - %p.gl-text-secondary= _('Update your project name, topics, description, and avatar.') - .settings-content= render 'projects/settings/general' - -%section.settings.sharing-permissions.no-animate#js-shared-permissions{ class: ('expanded' if expanded), data: { testid: 'visibility-features-permissions-content' } } - .settings-header - %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Visibility, project features, permissions') - = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do - = expanded ? _('Collapse') : _('Expand') - %p.gl-text-secondary= _('Choose visibility level, enable/disable project features and their permissions, disable email notifications, and show default emoji reactions.') - - .settings-content - = form_for @project, html: { multipart: true, class: "sharing-permissions-form", id: reduce_visibility_form_id }, authenticity_token: true do |f| - %input{ name: 'update_section', type: 'hidden', value: 'js-shared-permissions' } - %template.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data(@project).to_json.html_safe - .js-project-permissions-form{ data: visibility_confirm_modal_data(@project, reduce_visibility_form_id) } -- if show_merge_request_settings_callout?(@project) - %section.settings.expanded - = render Pajamas::AlertComponent.new(variant: :info, + - c.with_body do + = _('To go to GitLab Pages, on the left sidebar, select %{pages_link}.').html_safe % {pages_link: link_to('Deploy > Pages', project_pages_path(@project)).html_safe} + + %section.settings.general-settings.no-animate.expanded#js-general-settings + .settings-header + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Naming, topics, avatar') + = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do + = _('Collapse') + %p.gl-text-secondary= _('Update your project name, topics, description, and avatar.') + .settings-content= render 'projects/settings/general' + + %section.settings.sharing-permissions.no-animate#js-shared-permissions{ class: ('expanded' if expanded), data: { testid: 'visibility-features-permissions-content' } } + .settings-header + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Visibility, project features, permissions') + = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do + = expanded ? _('Collapse') : _('Expand') + %p.gl-text-secondary= _('Choose visibility level, enable/disable project features and their permissions, disable email notifications, and show default emoji reactions.') + + .settings-content + = form_for @project, html: { multipart: true, class: "sharing-permissions-form", id: reduce_visibility_form_id }, authenticity_token: true do |f| + %input{ name: 'update_section', type: 'hidden', value: 'js-shared-permissions' } + %template.js-project-permissions-form-data{ type: "application/json" }= project_permissions_panel_data(@project).to_json.html_safe + .js-project-permissions-form{ data: visibility_confirm_modal_data(@project, reduce_visibility_form_id) } + - if show_merge_request_settings_callout?(@project) + %section.settings.expanded + = render Pajamas::AlertComponent.new(variant: :info, title: _('Merge requests and approvals settings have moved.'), alert_options: { class: 'js-merge-request-settings-callout gl-my-5', data: { feature_id: Users::CalloutsHelper::MERGE_REQUEST_SETTINGS_MOVED_CALLOUT, dismiss_endpoint: callouts_path, defer_links: 'true' } }) do |c| - - c.with_body do - = _('On the left sidebar, select %{merge_requests_link} to view them.').html_safe % { merge_requests_link: link_to('Settings > Merge requests', project_settings_merge_requests_path(@project)).html_safe } - -%section.settings.no-animate{ class: ('expanded' if expanded), data: { testid: 'badges-settings-content' } } - .settings-header - %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only - = s_('ProjectSettings|Badges') - = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do - = expanded ? _('Collapse') : _('Expand') - %p.gl-text-secondary - = s_('ProjectSettings|Customize this project\'s badges.') - = link_to s_('ProjectSettings|What are badges?'), help_page_path('user/project/badges') - .settings-content - = render 'shared/badges/badge_settings' - -= render_if_exists 'compliance_management/compliance_framework/project_settings', expanded: expanded - -= render_if_exists 'projects/settings/default_issue_template' - -= render 'projects/service_desk_settings' - -%section.settings.advanced-settings.no-animate#js-project-advanced-settings{ class: ('expanded' if expanded), data: { testid: 'advanced-settings-content' } } - .settings-header - %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Advanced') - = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do - = expanded ? _('Collapse') : _('Expand') - %p.gl-text-secondary= s_('ProjectSettings|Housekeeping, export, archive, change path, transfer, and delete.') - - .settings-content - = render_if_exists 'projects/settings/restore', project: @project - - = render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card gl-mt-0' }, header_options: { class: 'gl-new-card-header gl-flex-direction-column' }, body_options: { class: 'gl-new-card-body gl-px-5 gl-py-4' }) do |c| - - c.with_header do - .gl-new-card-title-wrapper - %h4.gl-new-card-title= _('Housekeeping') - %p.gl-new-card-description - = _('Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects.') - = link_to _('Learn more.'), help_page_path('administration/housekeeping'), target: '_blank', rel: 'noopener noreferrer' - - - c.with_body do - .gl-display-flex.gl-flex-wrap.gl-gap-3 - = render Pajamas::ButtonComponent.new(method: :post, href: housekeeping_project_path(@project)) do - = _('Run housekeeping') - #js-project-prune-unreachable-objects-button{ data: { prune_objects_path: housekeeping_project_path(@project, prune: true), prune_objects_doc_path: help_page_path('administration/housekeeping', anchor: 'prune-unreachable-objects') } } - - = render 'export', project: @project - - = render_if_exists 'projects/settings/archive' - - = render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card rename-repository' }, header_options: { class: 'gl-new-card-header gl-flex-direction-column' }, body_options: { class: 'gl-new-card-body gl-px-5 gl-py-4' }) do |c| - - c.with_header do - .gl-new-card-title-wrapper - %h4.gl-new-card-title.warning-title= _('Change path') - %p.gl-new-card-description - - link = link_to('', help_page_path('user/project/settings/index', anchor: 'rename-a-repository'), target: '_blank', rel: 'noopener noreferrer') - = safe_format(_("A project’s repository name defines its URL (the one you use to access the project via a browser) and its place on the file disk where GitLab is installed. %{link_start}Learn more.%{link_end}"), tag_pair(link, :link_start, :link_end)) - - - c.with_body do - = render 'projects/errors' - = gitlab_ui_form_for @project do |f| - .form-group - %p - %span.gl-font-weight-bold= _("Be careful. Renaming a project's repository can have unintended side effects.") - = _('You will need to update your local repositories to point to the new location.') - - if @project.deployment_platform.present? - %p= _('Your deployment services will be broken, you will need to manually fix the services after renaming.') - = f.label :path, _('Path'), class: 'label-bold' + - c.with_body do + = _('On the left sidebar, select %{merge_requests_link} to view them.').html_safe % { merge_requests_link: link_to('Settings > Merge requests', project_settings_merge_requests_path(@project)).html_safe } + + %section.settings.no-animate{ class: ('expanded' if expanded), data: { testid: 'badges-settings-content' } } + .settings-header + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only + = s_('ProjectSettings|Badges') + = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do + = expanded ? _('Collapse') : _('Expand') + %p.gl-text-secondary + = s_('ProjectSettings|Customize this project\'s badges.') + = link_to s_('ProjectSettings|What are badges?'), help_page_path('user/project/badges') + .settings-content + = render 'shared/badges/badge_settings' + + = render_if_exists 'compliance_management/compliance_framework/project_settings', expanded: expanded + + = render_if_exists 'projects/settings/default_issue_template' + + = render 'projects/service_desk_settings' + + %section.settings.advanced-settings.no-animate#js-project-advanced-settings{ class: ('expanded' if expanded), data: { testid: 'advanced-settings-content' } } + .settings-header + %h4.settings-title.js-settings-toggle.js-settings-toggle-trigger-only= _('Advanced') + = render Pajamas::ButtonComponent.new(button_options: { class: 'js-settings-toggle' }) do + = expanded ? _('Collapse') : _('Expand') + %p.gl-text-secondary= s_('ProjectSettings|Housekeeping, export, archive, change path, transfer, and delete.') + + .settings-content + = render_if_exists 'projects/settings/restore', project: @project + + = render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card gl-mt-0' }, header_options: { class: 'gl-new-card-header gl-flex-direction-column' }, body_options: { class: 'gl-new-card-body gl-px-5 gl-py-4' }) do |c| + - c.with_header do + .gl-new-card-title-wrapper + %h4.gl-new-card-title= _('Housekeeping') + %p.gl-new-card-description + = _('Runs a number of housekeeping tasks within the current repository, such as compressing file revisions and removing unreachable objects.') + = link_to _('Learn more.'), help_page_path('administration/housekeeping'), target: '_blank', rel: 'noopener noreferrer' + + - c.with_body do + .gl-display-flex.gl-flex-wrap.gl-gap-3 + = render Pajamas::ButtonComponent.new(method: :post, href: housekeeping_project_path(@project)) do + = _('Run housekeeping') + #js-project-prune-unreachable-objects-button{ data: { prune_objects_path: housekeeping_project_path(@project, prune: true), prune_objects_doc_path: help_page_path('administration/housekeeping', anchor: 'prune-unreachable-objects') } } + + = render 'export', project: @project + + = render_if_exists 'projects/settings/archive' + + = render Pajamas::CardComponent.new(card_options: { class: 'gl-new-card rename-repository' }, header_options: { class: 'gl-new-card-header gl-flex-direction-column' }, body_options: { class: 'gl-new-card-body gl-px-5 gl-py-4' }) do |c| + - c.with_header do + .gl-new-card-title-wrapper + %h4.gl-new-card-title.warning-title= _('Change path') + %p.gl-new-card-description + - link = link_to('', help_page_path('user/project/settings/index', anchor: 'rename-a-repository'), target: '_blank', rel: 'noopener noreferrer') + = safe_format(_("A project’s repository name defines its URL (the one you use to access the project via a browser) and its place on the file disk where GitLab is installed. %{link_start}Learn more.%{link_end}"), tag_pair(link, :link_start, :link_end)) + + - c.with_body do + = render 'projects/errors' + = gitlab_ui_form_for @project do |f| .form-group - .input-group - .input-group-prepend - .input-group-text - #{Gitlab::Utils.append_path(root_url, @project.namespace.full_path)}/ - = f.text_field :path, class: 'form-control gl-form-input-xl', data: { testid: 'project-path-field' } - = f.submit _('Change path'), class: "btn-danger", data: { testid: 'change-path-button' }, pajamas_button: true - - = render 'transfer', project: @project - - = render 'remove_fork', project: @project - - = render 'remove', project: @project + %p + %span.gl-font-weight-bold= _("Be careful. Renaming a project's repository can have unintended side effects.") + = _('You will need to update your local repositories to point to the new location.') + - if @project.deployment_platform.present? + %p= _('Your deployment services will be broken, you will need to manually fix the services after renaming.') + = f.label :path, _('Path'), class: 'label-bold' + .form-group + .input-group + .input-group-prepend + .input-group-text + #{Gitlab::Utils.append_path(root_url, @project.namespace.full_path)}/ + = f.text_field :path, class: 'form-control gl-form-input-xl', data: { testid: 'project-path-field' } + = f.submit _('Change path'), class: "btn-danger", data: { testid: 'change-path-button' }, pajamas_button: true + + = render 'transfer', project: @project + + = render 'remove_fork', project: @project + + = render 'remove', project: @project +- elsif can?(current_user, :archive_project, @project) + = render_if_exists 'projects/settings/archive' .save-project-loader.hide .center diff --git a/db/migrate/20231024123444_add_archive_project_to_member_roles.rb b/db/migrate/20231024123444_add_archive_project_to_member_roles.rb new file mode 100644 index 00000000000000..27ff86450e83cf --- /dev/null +++ b/db/migrate/20231024123444_add_archive_project_to_member_roles.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +class AddArchiveProjectToMemberRoles < Gitlab::Database::Migration[2.1] + enable_lock_retries! + + def change + add_column :member_roles, :archive_project, :boolean, default: false, null: false + end +end diff --git a/db/schema_migrations/20231024123444 b/db/schema_migrations/20231024123444 new file mode 100644 index 00000000000000..578f1cef1bdca9 --- /dev/null +++ b/db/schema_migrations/20231024123444 @@ -0,0 +1 @@ +db84d40c9afd9121aa24617167fa82b86cabc98bf274e61057eef02e1fafd7c3 \ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index ab1bf35fc90dce..a75b1bbe8f8b82 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -18525,6 +18525,7 @@ CREATE TABLE member_roles ( admin_merge_request boolean DEFAULT false NOT NULL, admin_group_member boolean DEFAULT false NOT NULL, manage_project_access_tokens boolean DEFAULT false NOT NULL, + archive_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 1b83954a794322..9600020e8036b7 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -5133,6 +5133,7 @@ Input type: `MemberRoleCreateInput` | `adminGroupMember` | [`Boolean`](#boolean) | Permission to admin group members. | | `adminMergeRequest` | [`Boolean`](#boolean) | Permission to admin merge requests. | | `adminVulnerability` | [`Boolean`](#boolean) | Permission to admin vulnerability. | +| `archiveProject` | [`Boolean`](#boolean) | Permission to archive projects. | | `baseAccessLevel` | [`MemberAccessLevel!`](#memberaccesslevel) | Base access level for the custom role. | | `clientMutationId` | [`String`](#string) | A unique identifier for the client performing the mutation. | | `description` | [`String`](#string) | Description of the member role. | @@ -20321,6 +20322,7 @@ Represents a member role. | `adminGroupMember` **{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 group members. | | `adminMergeRequest` **{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 merge requests. | | `adminVulnerability` **{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 vulnerability. | +| `archiveProject` **{warning-solid}** | [`Boolean`](#boolean) | **Introduced** in 16.6. This feature is an Experiment. It can be changed or removed at any time. Permission to archive projects. | | `baseAccessLevel` **{warning-solid}** | [`AccessLevel!`](#accesslevel) | **Introduced** in 16.5. This feature is an Experiment. It can be changed or removed at any time. Base access level for the custom 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. | @@ -29405,6 +29407,7 @@ Member role permission. | `ADMIN_GROUP_MEMBER` | Allows admin access to group members. | | `ADMIN_MERGE_REQUEST` | Allows admin access to the merge requests. | | `ADMIN_VULNERABILITY` | Allows admin access to the vulnerability reports. | +| `ARCHIVE_PROJECT` | Allows to archive projects. | | `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 cc50a8e225a339..a1e19df24707d3 100644 --- a/doc/api/member_roles.md +++ b/doc/api/member_roles.md @@ -13,12 +13,14 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - [Read dependency added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/126247) in GitLab 16.3. > - [Name and description fields added](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/126423) in GitLab 16.3. > - [Admin merge request introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/128302) in GitLab 16.4 [with a flag](../administration/feature_flags.md) named `admin_merge_request`. Disabled by default. +> - [Feature flag `admin_merge_request` removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132578) in GitLab 16.5. > - [Admin group members introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131914) in GitLab 16.5 [with a flag](../administration/feature_flags.md) named `admin_group_member`. Disabled by default. The feature flag has been removed in GitLab 16.6. > - [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.6 in [with a flag](../administration/feature_flags.md) named `archive_project_custom_permission`. Disabled by default. FLAG: -On self-managed GitLab, by default these two features are not available. To make them available, an administrator can [enable the feature flags](../administration/feature_flags.md) named `admin_merge_request` and `admin_member_custom_role`. -On GitLab.com, this feature is not available. +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`, `manage_project_access_tokens` and `archive_project_custom_permission`. +On GitLab.com, these features are not available. ## List all member roles of a group @@ -48,6 +50,7 @@ If successful, returns [`200`](rest/index.md#status-codes) and the following res | `[].read_vulnerability` | boolean | Permission to read project vulnerabilities. | | `[].admin_group_member` | boolean | Permission to admin members of a group. | | `[].manage_project_access_tokens` | boolean | Permission to manage project access tokens. | +| `[].archive_project` | boolean | Permission to archive projects. | Example request: @@ -70,7 +73,8 @@ Example response: "read_code": true, "read_dependency": false, "read_vulnerability": false, - "manage_project_access_tokens": false + "manage_project_access_tokens": false, + "archive_project": false }, { "id": 3, @@ -83,7 +87,8 @@ Example response: "read_code": false, "read_dependency": true, "read_vulnerability": true, - "manage_project_access_tokens": false + "manage_project_access_tokens": false, + "archive_project": false } ] ``` diff --git a/doc/user/custom_roles.md b/doc/user/custom_roles.md index 1b827d8279296a..71cde591e52337 100644 --- a/doc/user/custom_roles.md +++ b/doc/user/custom_roles.md @@ -15,6 +15,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - Ability to create and remove a custom role with the UI [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/393235) in GitLab 16.4. > - Ability to manage group members [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17364) in GitLab 16.5. > - Ability to manage project access tokens [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/421778) in GitLab 16.5 [with a flag](../administration/feature_flags.md) named `manage_project_access_tokens`. +> - Ability to archive projects [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/425957) in GitLab 16.6 in [with a flag](../administration/feature_flags.md) named `archive_project_custom_permission`. Disabled by default. Custom roles allow group Owners or instance administrators to create roles specific to the needs of their organization. @@ -97,6 +98,7 @@ These requirements are documented in the `Required permission` column in the fol | `admin_merge_request` | GitLab 16.4 and later | Not applicable | View and approve [merge requests](project/merge_requests/index.md), and view the associated merge request code.
Does not allow users to view or change merge request approval rules. | | `manage_project_access_tokens` | GitLab 16.5 and later | Not applicable | Create, delete, and list [project access tokens](project/settings/project_access_tokens.md). | | `admin_group_member` | GitLab 16.5 and later | Not applicable | Add or remove [group members](group/manage.md). | +| `archive_project` | GitLab 16.6 and later | Not applicable | Archive and unarchive [projects](project/settings/index.md#archive-a-project). | ## Billing and seat usage diff --git a/ee/app/assets/javascripts/roles_and_permissions/components/create_member_role.vue b/ee/app/assets/javascripts/roles_and_permissions/components/create_member_role.vue index 0a37c86bf6886a..3291a504122c57 100644 --- a/ee/app/assets/javascripts/roles_and_permissions/components/create_member_role.vue +++ b/ee/app/assets/javascripts/roles_and_permissions/components/create_member_role.vue @@ -62,9 +62,16 @@ export default { }, computed: { selectablePermissions() { - return gon.features.manageProjectAccessTokens - ? this.availablePermissions - : this.availablePermissions.filter(({ value }) => value !== 'manage_project_access_tokens'); + return this.availablePermissions.filter(({ value }) => { + switch (value) { + case 'manage_project_access_tokens': + return Boolean(gon.features.manageProjectAccessTokens); + case 'archive_project': + return Boolean(gon.features.archiveProjectCustomPermission); + default: + return true; + } + }); }, }, methods: { diff --git a/ee/app/assets/javascripts/roles_and_permissions/components/list_member_roles.vue b/ee/app/assets/javascripts/roles_and_permissions/components/list_member_roles.vue index 08420e117443f3..80a483255198ae 100644 --- a/ee/app/assets/javascripts/roles_and_permissions/components/list_member_roles.vue +++ b/ee/app/assets/javascripts/roles_and_permissions/components/list_member_roles.vue @@ -218,7 +218,7 @@ export default { diff --git a/ee/app/controllers/ee/projects_controller.rb b/ee/app/controllers/ee/projects_controller.rb index b9c2c63c5d5aaa..a66458da13ac31 100644 --- a/ee/app/controllers/ee/projects_controller.rb +++ b/ee/app/controllers/ee/projects_controller.rb @@ -222,5 +222,9 @@ def log_archive_audit_event def log_unarchive_audit_event log_audit_event(message: 'Project unarchived', event_type: 'project_unarchived') end + + def authorize_admin_project_or_custom_permissions! + can?(current_user, :archive_project, project) || super + end end end diff --git a/ee/app/controllers/groups/settings/roles_and_permissions_controller.rb b/ee/app/controllers/groups/settings/roles_and_permissions_controller.rb index 6ed3c81fdeba74..6bdce08ed50c52 100644 --- a/ee/app/controllers/groups/settings/roles_and_permissions_controller.rb +++ b/ee/app/controllers/groups/settings/roles_and_permissions_controller.rb @@ -10,6 +10,7 @@ class RolesAndPermissionsController < Groups::ApplicationController before_action :ensure_custom_roles_available! before_action do push_frontend_feature_flag(:manage_project_access_tokens, group) + push_frontend_feature_flag(:archive_project_custom_permission, group) end private diff --git a/ee/app/graphql/mutations/member_roles/create.rb b/ee/app/graphql/mutations/member_roles/create.rb index c6c8a62218acad..297d2f868bb174 100644 --- a/ee/app/graphql/mutations/member_roles/create.rb +++ b/ee/app/graphql/mutations/member_roles/create.rb @@ -21,6 +21,10 @@ class Create < Base GraphQL::Types::Boolean, required: false, description: 'Permission to admin vulnerability.' + argument :archive_project, + GraphQL::Types::Boolean, + required: false, + description: 'Permission to archive projects.' argument :base_access_level, ::Types::MemberAccessLevelEnum, required: true, 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 7fed4e8b651685..de5fecfdfb0956 100644 --- a/ee/app/graphql/types/member_roles/member_role_type.rb +++ b/ee/app/graphql/types/member_roles/member_role_type.rb @@ -41,6 +41,12 @@ class MemberRoleType < BaseObject alpha: { milestone: '16.5' }, description: 'Permission to admin group members.' + field :archive_project, + GraphQL::Types::Boolean, + null: true, + alpha: { milestone: '16.6' }, + description: 'Permission to archive projects.' + field :manage_project_access_tokens, GraphQL::Types::Boolean, null: true, diff --git a/ee/app/models/members/member_role.rb b/ee/app/models/members/member_role.rb index 2c4819f31dff9e..d5ee2ddddd479b 100644 --- a/ee/app/models/members/member_role.rb +++ b/ee/app/models/members/member_role.rb @@ -24,6 +24,9 @@ class MemberRole < ApplicationRecord # rubocop:disable Gitlab/NamespacedClass }, manage_project_access_tokens: { description: 'Allows manage access to the project access tokens' + }, + archive_project: { + description: 'Allows to archive projects' } }.freeze ALL_CUSTOMIZABLE_PROJECT_PERMISSIONS = [ @@ -32,7 +35,8 @@ class MemberRole < ApplicationRecord # rubocop:disable Gitlab/NamespacedClass :read_vulnerability, :admin_merge_request, :admin_vulnerability, - :manage_project_access_tokens + :manage_project_access_tokens, + :archive_project ].freeze ALL_CUSTOMIZABLE_GROUP_PERMISSIONS = [ :read_dependency, diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb index c84f722fcd0bbd..f2834f5b9e71f7 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -637,6 +637,23 @@ module ProjectPolicy ).has_ability? end + condition(:archive_project_custom_permission_enabled) do + ::Feature.enabled?(:archive_project_custom_permission, @subject.root_ancestor) + end + + desc "Custom role on project that enables archiving projects" + condition(:custom_role_enables_archive_projects) do + ::Auth::MemberRoleAbilityLoader.new( + user: @user, + resource: @subject, + ability: :archive_project + ).has_ability? + end + + rule { custom_roles_allowed & archive_project_custom_permission_enabled & custom_role_enables_archive_projects }.policy do + enable :archive_project + end + rule { needs_new_sso_session }.policy do prevent :read_project end diff --git a/ee/config/feature_flags/development/archive_project_custom_permission.yml b/ee/config/feature_flags/development/archive_project_custom_permission.yml new file mode 100644 index 00000000000000..73b9822618aef4 --- /dev/null +++ b/ee/config/feature_flags/development/archive_project_custom_permission.yml @@ -0,0 +1,8 @@ +--- +name: archive_project_custom_permission +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134998 +rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/429239 +milestone: '16.6' +type: development +group: group::authorization +default_enabled: false diff --git a/ee/lib/ee/sidebars/projects/menus/settings_menu.rb b/ee/lib/ee/sidebars/projects/menus/settings_menu.rb index 5b931a1e0960d1..56cc28e7d1faaf 100644 --- a/ee/lib/ee/sidebars/projects/menus/settings_menu.rb +++ b/ee/lib/ee/sidebars/projects/menus/settings_menu.rb @@ -28,6 +28,33 @@ def analytics_menu_item item_id: :analytics ) end + + private + + override :enabled_menu_items + def enabled_menu_items + return super if can?(context.current_user, :admin_project, context.project) + + custom_roles_menu_items + end + + def custom_roles_menu_items + items = [] + return items unless context.current_user + + items << general_menu_item if custom_roles_general_menu_item? + items << access_tokens_menu_item if custom_roles_access_token_menu_item? + + items + end + + def custom_roles_general_menu_item? + can?(context.current_user, :archive_project, context.project) + end + + def custom_roles_access_token_menu_item? + can?(context.current_user, :manage_resource_access_tokens, context.project) + end end end end diff --git a/ee/spec/controllers/projects_controller_spec.rb b/ee/spec/controllers/projects_controller_spec.rb index 613d1a02c4d0ca..21ebd428f55661 100644 --- a/ee/spec/controllers/projects_controller_spec.rb +++ b/ee/spec/controllers/projects_controller_spec.rb @@ -170,16 +170,33 @@ end describe 'GET edit', feature_category: :groups_and_projects do + subject(:request) { get :edit, params: { namespace_id: project.namespace.path, id: project.path } } + it 'does not allow an auditor user to access the page' do sign_in(create(:user, :auditor)) - get :edit, params: { - namespace_id: project.namespace.path, - id: project.path - } + request expect(response).to have_gitlab_http_status(:not_found) end + + context 'when the user can archive projects' do + let_it_be(:guest) { create(:user) } + + before do + project.add_guest(guest) + allow(controller).to receive(:can?).and_call_original + allow(controller).to receive(:can?).with(guest, :archive_project, anything).and_return(true) + + sign_in(guest) + request + end + + it 'allows the user to access the page' do + expect(response).to have_gitlab_http_status(:ok) + expect(response).to render_template(:edit) + end + end end describe 'POST create', feature_category: :groups_and_projects do diff --git a/ee/spec/features/projects/custom_roles/archive_project_custom_permission_spec.rb b/ee/spec/features/projects/custom_roles/archive_project_custom_permission_spec.rb new file mode 100644 index 00000000000000..a33857696be94e --- /dev/null +++ b/ee/spec/features/projects/custom_roles/archive_project_custom_permission_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Archive Project Custom Permission', feature_category: :groups_and_projects do + let_it_be(:project) { create(:project, :in_group) } + let_it_be(:user) { create(:user) } + let_it_be(:custom_role) { create(:member_role, :guest, namespace: project.namespace, archive_project: true) } + + before do + stub_licensed_features(custom_roles: true) + create(:project_member, :guest, member_role: custom_role, user: user, project: project) + sign_in(user) + end + + context 'when the project is not archived', :js, :aggregate_failures do + it 'allows a guest user with custom `archive_project` permissions to archive it' do + visit project_path(project) + + within_testid('super-sidebar') do + click_button('Settings') + click_link('General') + end + + click_link('Archive project') + click_button('Archive project') + + expect(page).to have_current_path(project_path(project)) + expect(project.reload.archived?).to eq(true) + end + end + + context 'when the project is archived', :js, :aggregate_failures do + let_it_be(:project) { create(:project, :archived, namespace: project.namespace) } + + it 'allows a guest user with custom `archive_project` permissions to unarchive it' do + visit project_path(project) + + within_testid('super-sidebar') do + click_button('Settings') + click_link('General') + end + + click_link('Unarchive project') + click_button('Unarchive project') + + expect(page).to have_current_path(project_path(project)) + expect(project.reload.archived?).to eq(false) + end + end + + shared_examples 'does not allow the user to archive the project' do + it 'does not show the `Settings` sidebar item', :js do + visit project_path(project) + + within_testid('super-sidebar') do + expect(page).not_to have_button('Settings') + end + end + + it 'does not allow access to the edit page' do + visit edit_project_path(project) + + expect(page).to have_gitlab_http_status(:not_found) + end + end + + context 'when the `archive_project_custom_permission` feature flag is disabled' do + before do + stub_feature_flags(archive_project_custom_permission: false) + end + + it_behaves_like 'does not allow the user to archive the project' + end + + context 'when the `custom_roles` licensed feature is not available' do + before do + stub_licensed_features(custom_roles: false) + end + + it_behaves_like 'does not allow the user to archive the project' + end + + context 'when the user does not have the custom `archive_project` permission' do + let_it_be(:custom_role) { create(:member_role, :guest, namespace: project.namespace, archive_project: false) } + + it_behaves_like 'does not allow the user to archive the project' + end +end diff --git a/ee/spec/frontend/roles_and_permissions/components/create_member_role_spec.js b/ee/spec/frontend/roles_and_permissions/components/create_member_role_spec.js index 2e87d5321c1189..cf1ec871f3be54 100644 --- a/ee/spec/frontend/roles_and_permissions/components/create_member_role_spec.js +++ b/ee/spec/frontend/roles_and_permissions/components/create_member_role_spec.js @@ -95,6 +95,21 @@ describe('CreateMemberRole', () => { }); }); + describe('archive_project_custom_permission feature flag is on', () => { + beforeEach(() => { + window.gon.features = { archiveProjectCustomPermission: true }; + }); + + it('renders archive project permission', () => { + const permission = { name: 'Permission E', description: 'Description E' }; + createComponent({ availablePermissions: [permission] }); + const checkbox = findCheckboxes().at(0); + + expect(checkbox.text()).toContain(permission.name); + expect(checkbox.text()).toContain(permission.description); + }); + }); + it('emits cancel event', () => { expect(wrapper.emitted('cancel')).toBeUndefined(); diff --git a/ee/spec/frontend/roles_and_permissions/components/list_member_roles_spec.js b/ee/spec/frontend/roles_and_permissions/components/list_member_roles_spec.js index 634574006e76f1..ec49910debd305 100644 --- a/ee/spec/frontend/roles_and_permissions/components/list_member_roles_spec.js +++ b/ee/spec/frontend/roles_and_permissions/components/list_member_roles_spec.js @@ -96,16 +96,28 @@ describe('ListMemberRoles', () => { getMemberRoles.mockResolvedValue({ data: mockResponse }); }); - it('shows empty state', () => { - createComponent({ groupId: null }); - expect(wrapper.findByTestId('card-title').text()).toMatch(ListMemberRoles.i18n.cardTitle); - expect(findCounter().text()).toBe('0'); - expect(findAddRoleButton().props('disabled')).toBe(true); - expect(findEmptyState().props()).toMatchObject({ - description: emptyText, - title: ListMemberRoles.i18n.emptyTitle, + describe('empty state', () => { + beforeEach(() => { + getMemberRoles.mockResolvedValue({ data: [] }); + createComponent({ groupId }); + }); + + it('shows empty state', () => { + expect(wrapper.findByTestId('card-title').text()).toMatch(ListMemberRoles.i18n.cardTitle); + expect(findCounter().text()).toBe('0'); + expect(findAddRoleButton().props('disabled')).toBe(false); + expect(findEmptyState().props()).toMatchObject({ + description: emptyText, + title: ListMemberRoles.i18n.emptyTitle, + }); + expect(findCreateMemberRole().exists()).toBe(false); + }); + + it('hides empty state when toggling the form', async () => { + findAddRoleButton().vm.$emit('click'); + await waitForPromises(); + expect(findEmptyState().exists()).toBe(false); }); - expect(findCreateMemberRole().exists()).toBe(false); }); describe('fetching roles', () => { 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 9fe99b298de877..83ee63da11f01a 100644 --- a/ee/spec/lib/ee/api/entities/member_role_spec.rb +++ b/ee/spec/lib/ee/api/entities/member_role_spec.rb @@ -20,7 +20,8 @@ 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_project_access_tokens]).to eq member_role.admin_vulnerability + 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[:group_id]).to eq(member_role.namespace.id) end end diff --git a/ee/spec/lib/ee/sidebars/projects/menus/settings_menu_spec.rb b/ee/spec/lib/ee/sidebars/projects/menus/settings_menu_spec.rb index 4f0b754f230bda..e439bc7ea8290f 100644 --- a/ee/spec/lib/ee/sidebars/projects/menus/settings_menu_spec.rb +++ b/ee/spec/lib/ee/sidebars/projects/menus/settings_menu_spec.rb @@ -37,5 +37,37 @@ expect(subject).to be_nil end end + + describe 'General' do + let(:item_id) { :general } + + describe 'when the user is not an admin but has archive_project custom permission' do + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :admin_project, project).and_return(false) + allow(Ability).to receive(:allowed?).with(user, :archive_project, project).and_return(true) + end + + it 'includes general menu item' do + expect(subject.title).to eql('General') + end + end + end + + describe 'Access Tokens' do + let(:item_id) { :access_tokens } + + describe 'when the user is not an admin but has manage_resource_access_tokens custom permission' do + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :admin_project, project).and_return(false) + allow(Ability).to receive(:allowed?).with(user, :manage_resource_access_tokens, project).and_return(true) + end + + it 'includes access token menu item' do + expect(subject.title).to eql('Access Tokens') + end + end + end end end diff --git a/ee/spec/models/ee/user_spec.rb b/ee/spec/models/ee/user_spec.rb index 74f53c3ec850c2..1d20cb0723539a 100644 --- a/ee/spec/models/ee/user_spec.rb +++ b/ee/spec/models/ee/user_spec.rb @@ -1182,7 +1182,8 @@ OR admin_vulnerability = true OR read_dependency = true OR read_vulnerability = true - OR manage_project_access_tokens = true\)\)\)\)'.squish # allow_cross_joins_across_databases + OR manage_project_access_tokens = true + OR archive_project = true\)\)\)\)'.squish # allow_cross_joins_across_databases end before do diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb index b2ce51ac62dc20..aca80ff0d09bd7 100644 --- a/ee/spec/policies/project_policy_spec.rb +++ b/ee/spec/policies/project_policy_spec.rb @@ -2761,6 +2761,29 @@ def create_member_role(member, abilities = member_role_abilities) it { is_expected.to be_disallowed(*disallowed_abilities) } end end + + context 'for a member role with archive_project true' do + let(:member_role_abilities) { { archive_project: true } } + let(:allowed_abilities) { [:archive_project] } + + context 'with archive_project_custom_permission FF enabled' do + before do + stub_feature_flags(archive_project_custom_permission: [project.group]) + end + + it_behaves_like 'custom roles abilities' + end + + context 'with archive_project_custom_permission FF disabled' do + before do + stub_feature_flags(archive_project_custom_permission: false) + end + + let(:disallowed_abilities) { [:archive_project] } + + it { is_expected.to be_disallowed(*disallowed_abilities) } + end + end end describe 'permissions for suggested reviewers bot', :saas do diff --git a/ee/spec/requests/api/member_roles_spec.rb b/ee/spec/requests/api/member_roles_spec.rb index 06ef35355f0b36..fff5e48f90bfac 100644 --- a/ee/spec/requests/api/member_roles_spec.rb +++ b/ee/spec/requests/api/member_roles_spec.rb @@ -106,6 +106,7 @@ "admin_merge_request" => false, "admin_vulnerability" => false, "manage_project_access_tokens" => false, + "archive_project" => false, "group_id" => group_id }, { @@ -120,6 +121,7 @@ "admin_merge_request" => true, "admin_vulnerability" => false, "manage_project_access_tokens" => false, + "archive_project" => false, "group_id" => group_id } ] diff --git a/ee/spec/views/projects/edit.html.haml_spec.rb b/ee/spec/views/projects/edit.html.haml_spec.rb index 5c7ec8341d3ac0..1ff41577815c54 100644 --- a/ee/spec/views/projects/edit.html.haml_spec.rb +++ b/ee/spec/views/projects/edit.html.haml_spec.rb @@ -38,4 +38,27 @@ it_behaves_like 'does not render registration features prompt', :project_disabled_repository_size_limit end end + + context 'when rendering for a user that is not an owner' do + let_it_be(:user) { create(:user) } + + before do + allow(view).to receive(:can?).with(user, :archive_project, project).and_return(can_archive_projects) + render + end + + subject { rendered } + + context 'when the user can archive projects' do + let(:can_archive_projects) { true } + + it { is_expected.to have_link(_('Archive project')) } + end + + context 'when the user cannot archive projects' do + let(:can_archive_projects) { false } + + it { is_expected.not_to have_link(_('Archive project')) } + end + end end diff --git a/lib/sidebars/projects/menus/settings_menu.rb b/lib/sidebars/projects/menus/settings_menu.rb index 8fed1c46425eac..077eebf58b9e1d 100644 --- a/lib/sidebars/projects/menus/settings_menu.rb +++ b/lib/sidebars/projects/menus/settings_menu.rb @@ -57,10 +57,6 @@ def enabled_menu_items monitor_menu_item, usage_quotas_menu_item ] - elsif context.current_user && can?(context.current_user, :manage_resource_access_tokens, context.project) - [ - access_tokens_menu_item - ] else [] end diff --git a/spec/lib/sidebars/projects/menus/settings_menu_spec.rb b/spec/lib/sidebars/projects/menus/settings_menu_spec.rb index 81ca9670ac6f15..605cec8be5e300 100644 --- a/spec/lib/sidebars/projects/menus/settings_menu_spec.rb +++ b/spec/lib/sidebars/projects/menus/settings_menu_spec.rb @@ -59,18 +59,6 @@ let(:item_id) { :access_tokens } it_behaves_like 'access rights checks' - - describe 'when the user is not an admin but has manage_resource_access_tokens' do - before do - allow(Ability).to receive(:allowed?).and_call_original - allow(Ability).to receive(:allowed?).with(user, :admin_project, project).and_return(false) - allow(Ability).to receive(:allowed?).with(user, :manage_resource_access_tokens, project).and_return(true) - end - - it 'includes access token menu item' do - expect(subject.title).to eql('Access Tokens') - end - end end describe 'Repository' do -- GitLab From d104f85fcb3ee56092f7385c52957bd12ee3b903 Mon Sep 17 00:00:00 2001 From: Alex Buijs Date: Thu, 2 Nov 2023 09:59:55 +0100 Subject: [PATCH 2/3] Implement backend reviewer feedback --- .../custom_roles/archive_project_custom_permission_spec.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/ee/spec/features/projects/custom_roles/archive_project_custom_permission_spec.rb b/ee/spec/features/projects/custom_roles/archive_project_custom_permission_spec.rb index a33857696be94e..03a8241fdac162 100644 --- a/ee/spec/features/projects/custom_roles/archive_project_custom_permission_spec.rb +++ b/ee/spec/features/projects/custom_roles/archive_project_custom_permission_spec.rb @@ -5,7 +5,7 @@ RSpec.describe 'Archive Project Custom Permission', feature_category: :groups_and_projects do let_it_be(:project) { create(:project, :in_group) } let_it_be(:user) { create(:user) } - let_it_be(:custom_role) { create(:member_role, :guest, namespace: project.namespace, archive_project: true) } + let_it_be(:custom_role) { create(:member_role, :guest, namespace: project.root_namespace, archive_project: true) } before do stub_licensed_features(custom_roles: true) @@ -45,7 +45,7 @@ click_button('Unarchive project') expect(page).to have_current_path(project_path(project)) - expect(project.reload.archived?).to eq(false) + expect(project.reload).not_to be_archived end end @@ -82,7 +82,7 @@ end context 'when the user does not have the custom `archive_project` permission' do - let_it_be(:custom_role) { create(:member_role, :guest, namespace: project.namespace, archive_project: false) } + let_it_be(:custom_role) { create(:member_role, :guest, namespace: project.root_namespace, archive_project: false) } it_behaves_like 'does not allow the user to archive the project' end -- GitLab From a591a3bd50bdc08abe894bfaac7a5f2b154b9fa7 Mon Sep 17 00:00:00 2001 From: Alex Buijs Date: Tue, 7 Nov 2023 21:04:23 +0100 Subject: [PATCH 3/3] Rename feature flag --- doc/api/member_roles.md | 4 ++-- doc/user/custom_roles.md | 2 +- .../components/create_member_role.vue | 2 +- .../groups/settings/roles_and_permissions_controller.rb | 2 +- ee/app/policies/ee/project_policy.rb | 6 +++--- ..._project_custom_permission.yml => archive_project.yml} | 4 ++-- .../archive_project_custom_permission_spec.rb | 4 ++-- .../components/create_member_role_spec.js | 4 ++-- ee/spec/policies/project_policy_spec.rb | 8 ++++---- 9 files changed, 18 insertions(+), 18 deletions(-) rename ee/config/feature_flags/development/{archive_project_custom_permission.yml => archive_project.yml} (79%) diff --git a/doc/api/member_roles.md b/doc/api/member_roles.md index a1e19df24707d3..63de583de25614 100644 --- a/doc/api/member_roles.md +++ b/doc/api/member_roles.md @@ -16,10 +16,10 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - [Feature flag `admin_merge_request` removed](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/132578) in GitLab 16.5. > - [Admin group members introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/131914) in GitLab 16.5 [with a flag](../administration/feature_flags.md) named `admin_group_member`. Disabled by default. The feature flag has been removed in GitLab 16.6. > - [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.6 in [with a flag](../administration/feature_flags.md) named `archive_project_custom_permission`. Disabled by default. +> - [Archive project introduced](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134998) in GitLab 16.6 in [with a flag](../administration/feature_flags.md) named `archive_project`. Disabled by default. 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`, `manage_project_access_tokens` and `archive_project_custom_permission`. +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`, `manage_project_access_tokens` and `archive_project`. On GitLab.com, these features are not available. ## List all member roles of a group diff --git a/doc/user/custom_roles.md b/doc/user/custom_roles.md index 71cde591e52337..bbb487240782ef 100644 --- a/doc/user/custom_roles.md +++ b/doc/user/custom_roles.md @@ -15,7 +15,7 @@ info: To determine the technical writer assigned to the Stage/Group associated w > - Ability to create and remove a custom role with the UI [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/393235) in GitLab 16.4. > - Ability to manage group members [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/17364) in GitLab 16.5. > - Ability to manage project access tokens [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/421778) in GitLab 16.5 [with a flag](../administration/feature_flags.md) named `manage_project_access_tokens`. -> - Ability to archive projects [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/425957) in GitLab 16.6 in [with a flag](../administration/feature_flags.md) named `archive_project_custom_permission`. Disabled by default. +> - Ability to archive projects [introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/425957) in GitLab 16.6 in [with a flag](../administration/feature_flags.md) named `archive_project`. Disabled by default. Custom roles allow group Owners or instance administrators to create roles specific to the needs of their organization. diff --git a/ee/app/assets/javascripts/roles_and_permissions/components/create_member_role.vue b/ee/app/assets/javascripts/roles_and_permissions/components/create_member_role.vue index 3291a504122c57..3ca33a4759c943 100644 --- a/ee/app/assets/javascripts/roles_and_permissions/components/create_member_role.vue +++ b/ee/app/assets/javascripts/roles_and_permissions/components/create_member_role.vue @@ -67,7 +67,7 @@ export default { case 'manage_project_access_tokens': return Boolean(gon.features.manageProjectAccessTokens); case 'archive_project': - return Boolean(gon.features.archiveProjectCustomPermission); + return Boolean(gon.features.archiveProject); default: return true; } diff --git a/ee/app/controllers/groups/settings/roles_and_permissions_controller.rb b/ee/app/controllers/groups/settings/roles_and_permissions_controller.rb index 6bdce08ed50c52..ec5d95865f16f4 100644 --- a/ee/app/controllers/groups/settings/roles_and_permissions_controller.rb +++ b/ee/app/controllers/groups/settings/roles_and_permissions_controller.rb @@ -10,7 +10,7 @@ class RolesAndPermissionsController < Groups::ApplicationController before_action :ensure_custom_roles_available! before_action do push_frontend_feature_flag(:manage_project_access_tokens, group) - push_frontend_feature_flag(:archive_project_custom_permission, group) + push_frontend_feature_flag(:archive_project, group) end private diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb index f2834f5b9e71f7..63c952b7a6df9a 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -637,8 +637,8 @@ module ProjectPolicy ).has_ability? end - condition(:archive_project_custom_permission_enabled) do - ::Feature.enabled?(:archive_project_custom_permission, @subject.root_ancestor) + condition(:archive_project_enabled) do + ::Feature.enabled?(:archive_project, @subject.root_ancestor) end desc "Custom role on project that enables archiving projects" @@ -650,7 +650,7 @@ module ProjectPolicy ).has_ability? end - rule { custom_roles_allowed & archive_project_custom_permission_enabled & custom_role_enables_archive_projects }.policy do + rule { custom_roles_allowed & archive_project_enabled & custom_role_enables_archive_projects }.policy do enable :archive_project end diff --git a/ee/config/feature_flags/development/archive_project_custom_permission.yml b/ee/config/feature_flags/development/archive_project.yml similarity index 79% rename from ee/config/feature_flags/development/archive_project_custom_permission.yml rename to ee/config/feature_flags/development/archive_project.yml index 73b9822618aef4..74b4c6b4a86faf 100644 --- a/ee/config/feature_flags/development/archive_project_custom_permission.yml +++ b/ee/config/feature_flags/development/archive_project.yml @@ -1,8 +1,8 @@ --- -name: archive_project_custom_permission +name: archive_project introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/134998 rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/429239 -milestone: '16.6' +milestone: "16.6" type: development group: group::authorization default_enabled: false diff --git a/ee/spec/features/projects/custom_roles/archive_project_custom_permission_spec.rb b/ee/spec/features/projects/custom_roles/archive_project_custom_permission_spec.rb index 03a8241fdac162..5050be48604202 100644 --- a/ee/spec/features/projects/custom_roles/archive_project_custom_permission_spec.rb +++ b/ee/spec/features/projects/custom_roles/archive_project_custom_permission_spec.rb @@ -65,9 +65,9 @@ end end - context 'when the `archive_project_custom_permission` feature flag is disabled' do + context 'when the `archive_project` feature flag is disabled' do before do - stub_feature_flags(archive_project_custom_permission: false) + stub_feature_flags(archive_project: false) end it_behaves_like 'does not allow the user to archive the project' diff --git a/ee/spec/frontend/roles_and_permissions/components/create_member_role_spec.js b/ee/spec/frontend/roles_and_permissions/components/create_member_role_spec.js index cf1ec871f3be54..76600c80463626 100644 --- a/ee/spec/frontend/roles_and_permissions/components/create_member_role_spec.js +++ b/ee/spec/frontend/roles_and_permissions/components/create_member_role_spec.js @@ -95,9 +95,9 @@ describe('CreateMemberRole', () => { }); }); - describe('archive_project_custom_permission feature flag is on', () => { + describe('archive_project feature flag is on', () => { beforeEach(() => { - window.gon.features = { archiveProjectCustomPermission: true }; + window.gon.features = { archiveProject: true }; }); it('renders archive project permission', () => { diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb index aca80ff0d09bd7..e360bf39cb8089 100644 --- a/ee/spec/policies/project_policy_spec.rb +++ b/ee/spec/policies/project_policy_spec.rb @@ -2766,17 +2766,17 @@ def create_member_role(member, abilities = member_role_abilities) let(:member_role_abilities) { { archive_project: true } } let(:allowed_abilities) { [:archive_project] } - context 'with archive_project_custom_permission FF enabled' do + context 'with archive_project FF enabled' do before do - stub_feature_flags(archive_project_custom_permission: [project.group]) + stub_feature_flags(archive_project: [project.group]) end it_behaves_like 'custom roles abilities' end - context 'with archive_project_custom_permission FF disabled' do + context 'with archive_project FF disabled' do before do - stub_feature_flags(archive_project_custom_permission: false) + stub_feature_flags(archive_project: false) end let(:disallowed_abilities) { [:archive_project] } -- GitLab