From 4c0f5bcda0542e21f0e28914204ba7757c2247e7 Mon Sep 17 00:00:00 2001 From: Alex Buijs Date: Wed, 1 May 2024 16:00:19 +0200 Subject: [PATCH 1/4] Add manage_deploy_tokens custom permission This adds the manage_deploy_tokens custom permission that allows to manage deploy tokens for groups and/or projects. --- .../json_schemas/member_role_permissions.json | 3 +++ doc/api/graphql/reference/index.md | 1 + doc/user/custom_roles/abilities.md | 6 ++++++ .../custom_abilities/manage_deploy_tokens.yml | 11 +++++++++++ .../manage_deploy_tokens/request_spec.rb | 16 ++++++++++++++++ 5 files changed, 37 insertions(+) create mode 100644 ee/config/custom_abilities/manage_deploy_tokens.yml create mode 100644 ee/spec/requests/custom_roles/manage_deploy_tokens/request_spec.rb diff --git a/app/validators/json_schemas/member_role_permissions.json b/app/validators/json_schemas/member_role_permissions.json index 8b75568ce8132e..a2d45d354fb10f 100644 --- a/app/validators/json_schemas/member_role_permissions.json +++ b/app/validators/json_schemas/member_role_permissions.json @@ -31,6 +31,9 @@ "archive_project": { "type": "boolean" }, + "manage_deploy_tokens": { + "type": "boolean" + }, "manage_group_access_tokens": { "type": "boolean" }, diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index cfc882bd1f78b4..bb48799946f006 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -33703,6 +33703,7 @@ Member role permission. | `ADMIN_VULNERABILITY` | Edit the vulnerability object, including the status and linking an issue. Includes the `read_vulnerability` permission actions. | | `ADMIN_WEB_HOOK` | Manage webhooks. | | `ARCHIVE_PROJECT` | Allows archiving of projects. | +| `MANAGE_DEPLOY_TOKENS` | Manage deploy tokens at the group or project level. | | `MANAGE_GROUP_ACCESS_TOKENS` | Create, read, update, and delete group access tokens. When creating a token, users with this custom permission must select a role for that token that has the same or fewer permissions as the default role used as the base for the custom role. | | `MANAGE_PROJECT_ACCESS_TOKENS` | Create, read, update, and delete project access tokens. When creating a token, users with this custom permission must select a role for that token that has the same or fewer permissions as the default role used as the base for the custom role. | | `MANAGE_SECURITY_POLICY_LINK` | Allows linking security policy projects. | diff --git a/doc/user/custom_roles/abilities.md b/doc/user/custom_roles/abilities.md index c19192be819d78..a3cfb1a653084e 100644 --- a/doc/user/custom_roles/abilities.md +++ b/doc/user/custom_roles/abilities.md @@ -29,6 +29,12 @@ These requirements are documented in the `Required permission` column in the fol |:-----|:------------|:------------------|:---------|:--------------|:---------| | [`admin_compliance_framework`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/144183) | | Create, read, update, and delete compliance frameworks. Users with this permission can also assign a compliance framework label to a project, and set the default framework of a group. | GitLab [17.0](https://gitlab.com/gitlab-org/gitlab/-/issues/411502) | | | +## Continuous delivery + +| Name | Required permission | Description | Introduced in | Feature flag | Enabled in | +|:-----|:------------|:------------------|:---------|:--------------|:---------| +| [`manage_deploy_tokens`](https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151677) | | Manage deploy tokens at the group or project level. | GitLab [17.0](https://gitlab.com/gitlab-org/gitlab/-/issues/448843) | | | + ## Groups and projects | Name | Required permission | Description | Introduced in | Feature flag | Enabled in | diff --git a/ee/config/custom_abilities/manage_deploy_tokens.yml b/ee/config/custom_abilities/manage_deploy_tokens.yml new file mode 100644 index 00000000000000..1a16216d6cd49a --- /dev/null +++ b/ee/config/custom_abilities/manage_deploy_tokens.yml @@ -0,0 +1,11 @@ +--- +name: manage_deploy_tokens +description: Manage deploy tokens at the group or project level. +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/448843 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/151677 +feature_category: continuous_delivery +milestone: "17.0" +group_ability: true +project_ability: true +requirements: [] +available_from_access_level: diff --git a/ee/spec/requests/custom_roles/manage_deploy_tokens/request_spec.rb b/ee/spec/requests/custom_roles/manage_deploy_tokens/request_spec.rb new file mode 100644 index 00000000000000..12839144e6645d --- /dev/null +++ b/ee/spec/requests/custom_roles/manage_deploy_tokens/request_spec.rb @@ -0,0 +1,16 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User with manage_deploy_tokens custom role', feature_category: :continuous_delivery do + let_it_be(:user) { create(:user) } + + before do + stub_licensed_features(custom_roles: true) + + sign_in(user) + end + + describe "Specify controller here" do + end +end -- GitLab From c6595dba223f9eb0cd04d1495460c52d45b49d41 Mon Sep 17 00:00:00 2001 From: Alex Buijs Date: Wed, 1 May 2024 17:22:54 +0200 Subject: [PATCH 2/4] Enable project level manage_deploy_tokens This enables the manage_deploy_tokens custom permission on project level. --- .../settings/repository_controller.rb | 3 +- app/helpers/deploy_tokens_helper.rb | 2 + app/policies/project_policy.rb | 1 + .../settings/repository/show.html.haml | 4 + .../ee/projects/deploy_tokens_controller.rb | 6 ++ .../settings/repository_controller.rb | 9 ++ ee/app/helpers/ee/deploy_tokens_helper.rb | 23 +++++ ee/app/policies/ee/project_policy.rb | 8 ++ .../sidebars/projects/menus/settings_menu.rb | 11 +-- .../helpers/ee/deploy_tokens_helper_spec.rb | 65 ++++++++++++++ .../projects/menus/settings_menu_spec.rb | 12 +++ ee/spec/policies/project_policy_spec.rb | 7 ++ .../manage_deploy_tokens/request_spec.rb | 89 ++++++++++++++++++- 13 files changed, 232 insertions(+), 8 deletions(-) create mode 100644 ee/app/helpers/ee/deploy_tokens_helper.rb create mode 100644 ee/spec/helpers/ee/deploy_tokens_helper_spec.rb diff --git a/app/controllers/projects/settings/repository_controller.rb b/app/controllers/projects/settings/repository_controller.rb index 8dc30ed5f68c46..2d7a3d698f03ac 100644 --- a/app/controllers/projects/settings/repository_controller.rb +++ b/app/controllers/projects/settings/repository_controller.rb @@ -4,8 +4,7 @@ module Projects module Settings class RepositoryController < Projects::ApplicationController layout 'project_settings' - before_action :authorize_admin_project!, except: [:show, :update] - before_action :authorize_admin_push_rules!, only: [:show, :update] + before_action :authorize_admin_project! before_action :define_variables, only: [:create_deploy_token] before_action do diff --git a/app/helpers/deploy_tokens_helper.rb b/app/helpers/deploy_tokens_helper.rb index 597823cdac7165..164549c44f5c5a 100644 --- a/app/helpers/deploy_tokens_helper.rb +++ b/app/helpers/deploy_tokens_helper.rb @@ -24,3 +24,5 @@ def deploy_token_revoke_button_data(token:, group_or_project:) } end end + +DeployTokensHelper.prepend_mod diff --git a/app/policies/project_policy.rb b/app/policies/project_policy.rb index 7ce72a8f731373..badbd5761e39b5 100644 --- a/app/policies/project_policy.rb +++ b/app/policies/project_policy.rb @@ -605,6 +605,7 @@ class ProjectPolicy < BasePolicy enable :read_import_error enable :admin_cicd_variables enable :admin_push_rules + enable :manage_deploy_tokens end rule { can?(:admin_build) }.enable :manage_trigger diff --git a/app/views/projects/settings/repository/show.html.haml b/app/views/projects/settings/repository/show.html.haml index f0ca2a18e5b3a2..dede437d2b35a0 100644 --- a/app/views/projects/settings/repository/show.html.haml +++ b/app/views/projects/settings/repository/show.html.haml @@ -18,7 +18,11 @@ -# Those are used throughout the actual views. These `shared` views are then -# reused in EE. = render "projects/settings/repository/protected_branches", protected_branch_entity: @project + +- if current_user.can?(:manage_deploy_tokens, @project) = render "shared/deploy_tokens/index", group_or_project: @project, description: deploy_token_description + +- if can?(current_user, :admin_project, @project) = render 'shared/deploy_keys/index' = render "projects/maintenance/show" diff --git a/ee/app/controllers/ee/projects/deploy_tokens_controller.rb b/ee/app/controllers/ee/projects/deploy_tokens_controller.rb index 71679814c4954e..9c99d9c43f2d1d 100644 --- a/ee/app/controllers/ee/projects/deploy_tokens_controller.rb +++ b/ee/app/controllers/ee/projects/deploy_tokens_controller.rb @@ -3,8 +3,14 @@ module EE module Projects module DeployTokensController + extend ActiveSupport::Concern extend ::Gitlab::Utils::Override + prepended do + skip_before_action :authorize_admin_project! + before_action :authorize_manage_deploy_tokens! + end + override :revoke def revoke super diff --git a/ee/app/controllers/ee/projects/settings/repository_controller.rb b/ee/app/controllers/ee/projects/settings/repository_controller.rb index 0bb43f383ecac7..35baae67dfb77a 100644 --- a/ee/app/controllers/ee/projects/settings/repository_controller.rb +++ b/ee/app/controllers/ee/projects/settings/repository_controller.rb @@ -8,6 +8,8 @@ module RepositoryController extend ::Gitlab::Utils::Override prepended do + skip_before_action :authorize_admin_project!, only: [:show, :create_deploy_token] + before_action :authorize_view_repository_settings!, only: [:show, :create_deploy_token] before_action :push_rule, only: :show end @@ -89,6 +91,13 @@ def allow_protected_branches_for_group?(group) ::Feature.enabled?(:group_protected_branches, group) || ::Feature.enabled?(:allow_protected_branches_for_group, group) end + + def authorize_view_repository_settings! + return if can?(current_user, :admin_push_rules, project) || + can?(current_user, :manage_deploy_tokens, project) + + authorize_admin_project! + end end end end diff --git a/ee/app/helpers/ee/deploy_tokens_helper.rb b/ee/app/helpers/ee/deploy_tokens_helper.rb new file mode 100644 index 00000000000000..f0a6f26f351ade --- /dev/null +++ b/ee/app/helpers/ee/deploy_tokens_helper.rb @@ -0,0 +1,23 @@ +# frozen_string_literal: true + +module EE + module DeployTokensHelper + extend ::Gitlab::Utils::Override + + override :container_registry_enabled? + def container_registry_enabled?(group_or_project) + return false unless ::Gitlab.config.registry.enabled + + can?(current_user, :read_container_image, group_or_project) || + can?(current_user, :manage_deploy_tokens, group_or_project) + end + + override :packages_registry_enabled? + def packages_registry_enabled?(group_or_project) + return false unless ::Gitlab.config.packages.enabled + + can?(current_user, :read_package, group_or_project&.packages_policy_subject) || + can?(current_user, :manage_deploy_tokens, group_or_project) + end + end +end diff --git a/ee/app/policies/ee/project_policy.rb b/ee/app/policies/ee/project_policy.rb index d8fbab0c1f291e..995fb29198c5d8 100644 --- a/ee/app/policies/ee/project_policy.rb +++ b/ee/app/policies/ee/project_policy.rb @@ -529,6 +529,7 @@ module ProjectPolicy enable :modify_merge_request_committer_setting enable :modify_product_analytics_settings enable :admin_push_rules + enable :manage_deploy_tokens end rule { license_scanning_enabled & can?(:maintainer_access) }.enable :admin_software_license_policy @@ -778,6 +779,13 @@ module ProjectPolicy enable :admin_compliance_framework end + rule { custom_role_enables_manage_deploy_tokens }.policy do + enable :manage_deploy_tokens + enable :read_deploy_token + enable :create_deploy_token + enable :destroy_deploy_token + end + rule { can?(:create_issue) & okrs_enabled }.policy do enable :create_objective enable :create_key_result diff --git a/ee/lib/ee/sidebars/projects/menus/settings_menu.rb b/ee/lib/ee/sidebars/projects/menus/settings_menu.rb index a9d1015ec0cf80..215bb866a6f08b 100644 --- a/ee/lib/ee/sidebars/projects/menus/settings_menu.rb +++ b/ee/lib/ee/sidebars/projects/menus/settings_menu.rb @@ -44,8 +44,8 @@ def custom_roles_menu_items items << general_menu_item if custom_roles_general_menu_item? items << access_tokens_menu_item if custom_roles_access_token_menu_item? - items << ci_cd_menu_item if custom_roles_ci_cd_menu_item? items << repository_menu_item if custom_roles_repository_menu_item? + items << ci_cd_menu_item if custom_roles_ci_cd_menu_item? items end @@ -58,12 +58,13 @@ def custom_roles_access_token_menu_item? can?(context.current_user, :manage_resource_access_tokens, context.project) end - def custom_roles_ci_cd_menu_item? - can?(context.current_user, :admin_cicd_variables, context.project) + def custom_roles_repository_menu_item? + can?(context.current_user, :admin_push_rules, context.project) || + can?(context.current_user, :manage_deploy_tokens, context.project) end - def custom_roles_repository_menu_item? - can?(context.current_user, :admin_push_rules, context.project) + def custom_roles_ci_cd_menu_item? + can?(context.current_user, :admin_cicd_variables, context.project) end end end diff --git a/ee/spec/helpers/ee/deploy_tokens_helper_spec.rb b/ee/spec/helpers/ee/deploy_tokens_helper_spec.rb new file mode 100644 index 00000000000000..d47e5855d0b6f9 --- /dev/null +++ b/ee/spec/helpers/ee/deploy_tokens_helper_spec.rb @@ -0,0 +1,65 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe EE::DeployTokensHelper, feature_category: :continuous_delivery do + using RSpec::Parameterized::TableSyntax + + describe '#container_registry_enabled?' do + let_it_be(:project) { build(:project) } + let_it_be(:user) { build(:user) } + + where(:registry_enabled, :can_read_container_image, :can_manage_deploy_tokens, :result) do + true | true | true | true + true | true | false | true + true | false | true | true + true | false | false | false + false | true | true | false + end + + with_them do + before do + allow(helper).to receive(:current_user).and_return(user) + allow(Gitlab.config.registry).to receive(:enabled).and_return(registry_enabled) + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :read_container_image, project) + .and_return(can_read_container_image) + allow(Ability).to receive(:allowed?).with(user, :manage_deploy_tokens, project) + .and_return(can_manage_deploy_tokens) + end + + it 'returns expected value' do + expect(helper.container_registry_enabled?(project)).to eq(result) + end + end + end + + describe '#packages_registry_enabled?' do + let_it_be(:project) { build(:project) } + let_it_be(:user) { build(:user) } + + where(:packages_enabled, :can_read_package, :can_manage_deploy_tokens, :result) do + true | true | true | true + true | true | false | true + true | false | true | true + true | false | false | false + false | true | true | false + end + + with_them do + before do + allow(helper).to receive(:current_user).and_return(user) + allow(Gitlab.config.packages).to receive(:enabled).and_return(packages_enabled) + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :read_package, instance_of(::Packages::Policies::Project)) + .and_return(can_read_package) + allow(Ability).to receive(:allowed?).with(user, :manage_deploy_tokens, project) + .and_return(can_manage_deploy_tokens) + end + + it 'returns expected value' do + expect(helper.packages_registry_enabled?(project)).to eq(result) + end + end + 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 b7dc170037d564..37f27647c8fe9a 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 @@ -130,6 +130,18 @@ expect(subject.title).to eql('Repository') end end + + describe 'when the user is not an admin but has the `manage_deploy_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_deploy_tokens, project).and_return(true) + end + + it 'includes Repository menu item' do + expect(subject.title).to eql('Repository') + end + end end end end diff --git a/ee/spec/policies/project_policy_spec.rb b/ee/spec/policies/project_policy_spec.rb index d94c50fd08ac98..29f85f5f700d8d 100644 --- a/ee/spec/policies/project_policy_spec.rb +++ b/ee/spec/policies/project_policy_spec.rb @@ -2989,6 +2989,13 @@ def create_member_role(member, abilities = member_role_abilities) it_behaves_like 'custom roles abilities' end + + context 'for a member role with `manage_deploy_tokens` true' do + let(:member_role_abilities) { { manage_deploy_tokens: true } } + let(:allowed_abilities) { [:manage_deploy_tokens, :read_deploy_token, :create_deploy_token, :destroy_deploy_token] } + + it_behaves_like 'custom roles abilities' + end end describe 'permissions for suggested reviewers bot', :saas do diff --git a/ee/spec/requests/custom_roles/manage_deploy_tokens/request_spec.rb b/ee/spec/requests/custom_roles/manage_deploy_tokens/request_spec.rb index 12839144e6645d..c10f5bae75e153 100644 --- a/ee/spec/requests/custom_roles/manage_deploy_tokens/request_spec.rb +++ b/ee/spec/requests/custom_roles/manage_deploy_tokens/request_spec.rb @@ -3,7 +3,12 @@ require 'spec_helper' RSpec.describe 'User with manage_deploy_tokens custom role', feature_category: :continuous_delivery do + include ApiHelpers + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, :repository, namespace: group) } + let_it_be_with_reload(:role) { create(:member_role, :guest, namespace: group, manage_deploy_tokens: true) } before do stub_licensed_features(custom_roles: true) @@ -11,6 +16,88 @@ sign_in(user) end - describe "Specify controller here" do + describe 'manage project deploy tokens' do + let_it_be(:membership) { create(:project_member, :guest, user: user, source: project, member_role: role) } + let_it_be(:deploy_token) { create(:deploy_token, projects: [project]) } + + describe Projects::Settings::RepositoryController do + describe '#show' do + it 'user has access via a custom role' do + get project_settings_repository_path(project) + + expect(response).to have_gitlab_http_status(:ok) + expect(response.body).to have_text(s_('DeployTokens|Deploy tokens')) + end + end + + describe '#create_deploy_token' do + it 'user has access via a custom role' do + params = { deploy_token: { name: 'name', expires_at: 1.day.from_now.to_datetime.to_s, read_repository: '1' } } + + expect do + post create_deploy_token_project_settings_repository_path(project, params: params, format: :json) + end.to change { project.deploy_tokens.count }.by(1) + + expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('public_api/v4/deploy_token') + end + end + end + + describe Projects::DeployTokensController do + describe '#revoke' do + it 'user has access via a custom role' do + expect do + put revoke_project_deploy_token_path(project, deploy_token) + end.to change { deploy_token.reload.revoked }.to(true) + + expect(response).to have_gitlab_http_status(:redirect) + expect(response).to redirect_to(project_settings_repository_path(project, anchor: 'js-deploy-tokens')) + end + end + end + + describe API::DeployTokens do + let_it_be(:url) { "/projects/#{project.id}/deploy_tokens" } + + describe 'GET all tokens' do + it 'user has access via a custom role' do + get api(url, user) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('public_api/v4/deploy_tokens') + end + end + + describe 'GET a single token' do + it 'user has access via a custom role' do + get api("#{url}/#{deploy_token.id}", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('public_api/v4/deploy_token') + end + end + + describe 'POST' do + it 'user has access via a custom role' do + expect do + post api(url, user), params: { name: 'Foo', expires_at: 1.day.from_now, scopes: ['read_repository'] } + end.to change { DeployToken.count }.by(1) + + expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('public_api/v4/deploy_token') + end + end + + describe 'DELETE' do + it 'user has access via a custom role' do + expect do + delete api("#{url}/#{deploy_token.id}", user) + end.to change { DeployToken.count }.by(-1) + + expect(response).to have_gitlab_http_status(:no_content) + end + end + end end end -- GitLab From 96933b82ee0534d44924da0e6c56031ba7136503 Mon Sep 17 00:00:00 2001 From: Alex Buijs Date: Fri, 3 May 2024 13:15:29 +0200 Subject: [PATCH 3/4] Enable group level manage_deploy_tokens This enables the manage_deploy_tokens custom permission on group level. --- .../groups/settings/repository/show.html.haml | 8 +- .../groups/settings/repository_controller.rb | 3 +- ee/app/policies/ee/group_policy.rb | 9 +- .../ee/sidebars/groups/menus/settings_menu.rb | 2 +- .../groups/menus/settings_menu_spec.rb | 22 +++++ ee/spec/policies/group_policy_spec.rb | 10 +++ .../manage_deploy_tokens/request_spec.rb | 85 +++++++++++++++++++ 7 files changed, 132 insertions(+), 7 deletions(-) diff --git a/app/views/groups/settings/repository/show.html.haml b/app/views/groups/settings/repository/show.html.haml index fc81d22391af6b..6b95189fbb6507 100644 --- a/app/views/groups/settings/repository/show.html.haml +++ b/app/views/groups/settings/repository/show.html.haml @@ -2,13 +2,13 @@ - page_title _('Repository') - @force_desktop_expanded_sidebar = true -- if can?(current_user, :admin_group, @group) +- if can?(current_user, :create_deploy_token, @group) - deploy_token_description = s_('DeployTokens|Group deploy tokens allow access to the packages, repositories, and registry images within the group.') - = render "shared/deploy_tokens/index", group_or_project: @group, description: deploy_token_description - = render "default_branch", group: @group -= render_if_exists "protected_branches/protected_branches", protected_branch_entity: @group +- if can?(current_user, :admin_group, @group) + = render "default_branch", group: @group + = render_if_exists "protected_branches/protected_branches", protected_branch_entity: @group - if can?(current_user, :change_push_rules, @group) = render "push_rules" diff --git a/ee/app/controllers/ee/groups/settings/repository_controller.rb b/ee/app/controllers/ee/groups/settings/repository_controller.rb index bbf75f0797daf3..40c3bb5fb8d793 100644 --- a/ee/app/controllers/ee/groups/settings/repository_controller.rb +++ b/ee/app/controllers/ee/groups/settings/repository_controller.rb @@ -16,7 +16,8 @@ module RepositoryController override :authorize_access! def authorize_access! - render_404 unless can?(current_user, :admin_group, group) || can?(current_user, :change_push_rules, group) + render_404 unless can?(current_user, :admin_group, group) || can?(current_user, :change_push_rules, group) || + can?(current_user, :manage_deploy_tokens, group) end def define_push_rule_variable diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb index 016219fd21554a..097a3ad2791fa9 100644 --- a/ee/app/policies/ee/group_policy.rb +++ b/ee/app/policies/ee/group_policy.rb @@ -550,10 +550,17 @@ module GroupPolicy enable :admin_push_rules end - rule { can?(:admin_group) | can?(:admin_compliance_framework) }.policy do + rule { can?(:admin_group) | can?(:admin_compliance_framework) | can?(:manage_deploy_tokens) }.policy do enable :view_edit_page end + rule { custom_role_enables_manage_deploy_tokens }.policy do + enable :manage_deploy_tokens + enable :read_deploy_token + enable :create_deploy_token + enable :destroy_deploy_token + end + rule { can?(:read_vulnerability) }.policy do enable :read_group_security_dashboard enable :create_vulnerability_export diff --git a/ee/lib/ee/sidebars/groups/menus/settings_menu.rb b/ee/lib/ee/sidebars/groups/menus/settings_menu.rb index e00a9734828b1a..04ae18bd753232 100644 --- a/ee/lib/ee/sidebars/groups/menus/settings_menu.rb +++ b/ee/lib/ee/sidebars/groups/menus/settings_menu.rb @@ -26,7 +26,7 @@ def configure_menu_items else add_menu_item_for_abilities(general_menu_item, [:remove_group, :admin_compliance_framework]) add_menu_item_for_abilities(access_tokens_menu_item, :read_resource_access_tokens) - add_menu_item_for_abilities(repository_menu_item, :admin_push_rules) + add_menu_item_for_abilities(repository_menu_item, [:admin_push_rules, :manage_deploy_tokens]) add_menu_item_for_abilities(ci_cd_menu_item, :admin_cicd_variables) add_menu_item_for_abilities(billing_menu_item, :read_billing) end 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 72802db9112167..38ebd24dd60cc5 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 @@ -403,5 +403,27 @@ end end end + + context 'when the user is not an owner but has `manage_deploy_tokens` custom permission', feature_category: :continuous_delivery do + let_it_be(:user) { create(:user) } + + subject { menu.renderable_items.find { |e| e.item_id == item_id } } + + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :admin_group, group).and_return(false) + allow(Ability).to receive(:allowed?).with(user, :manage_deploy_tokens, group).and_return(true) + end + + describe 'General menu item' do + let(:item_id) { :repository } + + 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/policies/group_policy_spec.rb b/ee/spec/policies/group_policy_spec.rb index 4d53868a75b8e0..3d8c4f28b2ac64 100644 --- a/ee/spec/policies/group_policy_spec.rb +++ b/ee/spec/policies/group_policy_spec.rb @@ -3563,6 +3563,16 @@ def create_member_role(member, abilities = member_role_abilities) it_behaves_like 'custom roles abilities' end + + context 'for a custom role with the `manage_deploy_tokens` permission' do + let(:member_role_abilities) { { manage_deploy_tokens: true } } + + let(:allowed_abilities) do + [:manage_deploy_tokens, :read_deploy_token, :create_deploy_token, :destroy_deploy_token, :view_edit_page] + end + + it_behaves_like 'custom roles abilities' + end end context 'for :read_limit_alert' do diff --git a/ee/spec/requests/custom_roles/manage_deploy_tokens/request_spec.rb b/ee/spec/requests/custom_roles/manage_deploy_tokens/request_spec.rb index c10f5bae75e153..ef5acf2a362373 100644 --- a/ee/spec/requests/custom_roles/manage_deploy_tokens/request_spec.rb +++ b/ee/spec/requests/custom_roles/manage_deploy_tokens/request_spec.rb @@ -100,4 +100,89 @@ end end end + + describe 'manage group deploy tokens' do + let_it_be(:membership) { create(:group_member, :guest, user: user, source: group, member_role: role) } + let_it_be(:deploy_token) { create(:deploy_token, :group, groups: [group]) } + + describe Groups::Settings::RepositoryController do + describe '#show' do + it 'user has access via a custom role' do + get group_settings_repository_path(group) + + expect(response).to have_gitlab_http_status(:ok) + expect(response.body).to have_text(s_('DeployTokens|Deploy tokens')) + end + end + + describe '#create_deploy_token' do + it 'user has access via a custom role' do + params = { deploy_token: { name: 'name', expires_at: 1.day.from_now.to_datetime.to_s, read_repository: '1' } } + + expect do + post create_deploy_token_group_settings_repository_path(group, params: params, format: :json) + end.to change { group.deploy_tokens.count }.by(1) + + expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('public_api/v4/deploy_token') + end + end + end + + describe Groups::DeployTokensController do + describe '#revoke' do + it 'user has access via a custom role' do + expect do + put revoke_group_deploy_token_path(group, deploy_token) + end.to change { deploy_token.reload.revoked }.to(true) + + expect(response).to have_gitlab_http_status(:redirect) + expect(response).to redirect_to(group_settings_repository_path(group, anchor: 'js-deploy-tokens')) + end + end + end + + describe API::DeployTokens do + let_it_be(:url) { "/groups/#{group.id}/deploy_tokens" } + + describe 'GET all tokens' do + it 'user has access via a custom role' do + get api(url, user) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('public_api/v4/deploy_tokens') + end + end + + describe 'GET a single token' do + it 'user has access via a custom role' do + get api("#{url}/#{deploy_token.id}", user) + + expect(response).to have_gitlab_http_status(:ok) + expect(response).to match_response_schema('public_api/v4/deploy_token') + end + end + + describe 'POST' do + it 'user has access via a custom role' do + expect do + post api(url, user), params: { name: 'Foo', expires_at: 1.day.from_now, scopes: ['read_repository'] } + end.to change { DeployToken.count }.by(1) + + expect(response).to have_gitlab_http_status(:created) + expect(response).to match_response_schema('public_api/v4/deploy_token') + end + end + + describe 'DELETE' do + it 'user has access via a custom role' do + expect do + delete api("#{url}/#{deploy_token.id}", user) + end.to change { DeployToken.count }.by(-1) + + expect(response).to have_gitlab_http_status(:no_content) + end + end + end + end end -- GitLab From 2d4a02cea2ec3ab87865ce63d87d1a26a768ec82 Mon Sep 17 00:00:00 2001 From: Alex Buijs Date: Wed, 8 May 2024 15:57:22 +0200 Subject: [PATCH 4/4] Implemented backend review feedback Backend reviewer feedback implemented. --- .../projects/deploy_tokens_controller.rb | 2 +- app/helpers/deploy_tokens_helper.rb | 14 ++-- .../ee/projects/deploy_tokens_controller.rb | 6 -- ee/app/helpers/ee/deploy_tokens_helper.rb | 23 ------- .../helpers/ee/deploy_tokens_helper_spec.rb | 65 ------------------- spec/helpers/deploy_tokens_helper_spec.rb | 62 +++++++++++++++++- 6 files changed, 70 insertions(+), 102 deletions(-) delete mode 100644 ee/app/helpers/ee/deploy_tokens_helper.rb delete mode 100644 ee/spec/helpers/ee/deploy_tokens_helper_spec.rb diff --git a/app/controllers/projects/deploy_tokens_controller.rb b/app/controllers/projects/deploy_tokens_controller.rb index ed77fa2fee617b..7ee9b271a1cb6a 100644 --- a/app/controllers/projects/deploy_tokens_controller.rb +++ b/app/controllers/projects/deploy_tokens_controller.rb @@ -1,7 +1,7 @@ # frozen_string_literal: true class Projects::DeployTokensController < Projects::ApplicationController - before_action :authorize_admin_project! + before_action :authorize_destroy_deploy_token! feature_category :continuous_delivery urgency :low diff --git a/app/helpers/deploy_tokens_helper.rb b/app/helpers/deploy_tokens_helper.rb index 164549c44f5c5a..cd6eb7b2dab854 100644 --- a/app/helpers/deploy_tokens_helper.rb +++ b/app/helpers/deploy_tokens_helper.rb @@ -8,13 +8,17 @@ def expand_deploy_tokens_section?(new_deploy_token, created_deploy_token) end def container_registry_enabled?(group_or_project) - Gitlab.config.registry.enabled && - can?(current_user, :read_container_image, group_or_project) + return false unless ::Gitlab.config.registry.enabled + + can?(current_user, :read_container_image, group_or_project) || + can?(current_user, :manage_deploy_tokens, group_or_project) end def packages_registry_enabled?(group_or_project) - Gitlab.config.packages.enabled && - can?(current_user, :read_package, group_or_project&.packages_policy_subject) + return false unless ::Gitlab.config.packages.enabled + + can?(current_user, :read_package, group_or_project&.packages_policy_subject) || + can?(current_user, :manage_deploy_tokens, group_or_project) end def deploy_token_revoke_button_data(token:, group_or_project:) @@ -24,5 +28,3 @@ def deploy_token_revoke_button_data(token:, group_or_project:) } end end - -DeployTokensHelper.prepend_mod diff --git a/ee/app/controllers/ee/projects/deploy_tokens_controller.rb b/ee/app/controllers/ee/projects/deploy_tokens_controller.rb index 9c99d9c43f2d1d..71679814c4954e 100644 --- a/ee/app/controllers/ee/projects/deploy_tokens_controller.rb +++ b/ee/app/controllers/ee/projects/deploy_tokens_controller.rb @@ -3,14 +3,8 @@ module EE module Projects module DeployTokensController - extend ActiveSupport::Concern extend ::Gitlab::Utils::Override - prepended do - skip_before_action :authorize_admin_project! - before_action :authorize_manage_deploy_tokens! - end - override :revoke def revoke super diff --git a/ee/app/helpers/ee/deploy_tokens_helper.rb b/ee/app/helpers/ee/deploy_tokens_helper.rb deleted file mode 100644 index f0a6f26f351ade..00000000000000 --- a/ee/app/helpers/ee/deploy_tokens_helper.rb +++ /dev/null @@ -1,23 +0,0 @@ -# frozen_string_literal: true - -module EE - module DeployTokensHelper - extend ::Gitlab::Utils::Override - - override :container_registry_enabled? - def container_registry_enabled?(group_or_project) - return false unless ::Gitlab.config.registry.enabled - - can?(current_user, :read_container_image, group_or_project) || - can?(current_user, :manage_deploy_tokens, group_or_project) - end - - override :packages_registry_enabled? - def packages_registry_enabled?(group_or_project) - return false unless ::Gitlab.config.packages.enabled - - can?(current_user, :read_package, group_or_project&.packages_policy_subject) || - can?(current_user, :manage_deploy_tokens, group_or_project) - end - end -end diff --git a/ee/spec/helpers/ee/deploy_tokens_helper_spec.rb b/ee/spec/helpers/ee/deploy_tokens_helper_spec.rb deleted file mode 100644 index d47e5855d0b6f9..00000000000000 --- a/ee/spec/helpers/ee/deploy_tokens_helper_spec.rb +++ /dev/null @@ -1,65 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe EE::DeployTokensHelper, feature_category: :continuous_delivery do - using RSpec::Parameterized::TableSyntax - - describe '#container_registry_enabled?' do - let_it_be(:project) { build(:project) } - let_it_be(:user) { build(:user) } - - where(:registry_enabled, :can_read_container_image, :can_manage_deploy_tokens, :result) do - true | true | true | true - true | true | false | true - true | false | true | true - true | false | false | false - false | true | true | false - end - - with_them do - before do - allow(helper).to receive(:current_user).and_return(user) - allow(Gitlab.config.registry).to receive(:enabled).and_return(registry_enabled) - allow(Ability).to receive(:allowed?).and_call_original - allow(Ability).to receive(:allowed?).with(user, :read_container_image, project) - .and_return(can_read_container_image) - allow(Ability).to receive(:allowed?).with(user, :manage_deploy_tokens, project) - .and_return(can_manage_deploy_tokens) - end - - it 'returns expected value' do - expect(helper.container_registry_enabled?(project)).to eq(result) - end - end - end - - describe '#packages_registry_enabled?' do - let_it_be(:project) { build(:project) } - let_it_be(:user) { build(:user) } - - where(:packages_enabled, :can_read_package, :can_manage_deploy_tokens, :result) do - true | true | true | true - true | true | false | true - true | false | true | true - true | false | false | false - false | true | true | false - end - - with_them do - before do - allow(helper).to receive(:current_user).and_return(user) - allow(Gitlab.config.packages).to receive(:enabled).and_return(packages_enabled) - allow(Ability).to receive(:allowed?).and_call_original - allow(Ability).to receive(:allowed?).with(user, :read_package, instance_of(::Packages::Policies::Project)) - .and_return(can_read_package) - allow(Ability).to receive(:allowed?).with(user, :manage_deploy_tokens, project) - .and_return(can_manage_deploy_tokens) - end - - it 'returns expected value' do - expect(helper.packages_registry_enabled?(project)).to eq(result) - end - end - end -end diff --git a/spec/helpers/deploy_tokens_helper_spec.rb b/spec/helpers/deploy_tokens_helper_spec.rb index e5dd5ff79a2e13..3ec00c87141eda 100644 --- a/spec/helpers/deploy_tokens_helper_spec.rb +++ b/spec/helpers/deploy_tokens_helper_spec.rb @@ -2,7 +2,9 @@ require 'spec_helper' -RSpec.describe DeployTokensHelper do +RSpec.describe DeployTokensHelper, feature_category: :continuous_delivery do + using RSpec::Parameterized::TableSyntax + describe '#deploy_token_revoke_button_data' do let_it_be(:token) { build(:deploy_token) } let_it_be(:project) { build(:project) } @@ -17,4 +19,62 @@ }) end end + + describe '#container_registry_enabled?' do + let_it_be(:project) { build(:project) } + let_it_be(:user) { build(:user) } + + where(:registry_enabled, :can_read_container_image, :can_manage_deploy_tokens, :result) do + true | true | true | true + true | true | false | true + true | false | true | true + true | false | false | false + false | true | true | false + end + + with_them do + before do + allow(helper).to receive(:current_user).and_return(user) + allow(Gitlab.config.registry).to receive(:enabled).and_return(registry_enabled) + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :read_container_image, project) + .and_return(can_read_container_image) + allow(Ability).to receive(:allowed?).with(user, :manage_deploy_tokens, project) + .and_return(can_manage_deploy_tokens) + end + + it 'returns expected value' do + expect(helper.container_registry_enabled?(project)).to eq(result) + end + end + end + + describe '#packages_registry_enabled?' do + let_it_be(:project) { build(:project) } + let_it_be(:user) { build(:user) } + + where(:packages_enabled, :can_read_package, :can_manage_deploy_tokens, :result) do + true | true | true | true + true | true | false | true + true | false | true | true + true | false | false | false + false | true | true | false + end + + with_them do + before do + allow(helper).to receive(:current_user).and_return(user) + allow(Gitlab.config.packages).to receive(:enabled).and_return(packages_enabled) + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :read_package, instance_of(::Packages::Policies::Project)) + .and_return(can_read_package) + allow(Ability).to receive(:allowed?).with(user, :manage_deploy_tokens, project) + .and_return(can_manage_deploy_tokens) + end + + it 'returns expected value' do + expect(helper.packages_registry_enabled?(project)).to eq(result) + end + end + end end -- GitLab