From af5016004d8b7e2a0dafee9cb9c1c260ffa7c9e5 Mon Sep 17 00:00:00 2001 From: Hinam Mehra <hmehra@gitlab.com> Date: Thu, 1 Feb 2024 14:28:11 +0000 Subject: [PATCH] Allow creation of group-level custom-roles on self-managed instances - Introduce beta feature-flag, restrict_member_roles, which is disabled by default. - When it's off, admins can create group-level and instance-level custom roles when on a self-managed instance. - When it's on, admins can only create instance-level custom roles on a self-managed instance. - When on SaaS mode, only group-level roles can be created. Changelog: added EE: true --- .../graphql/mutations/member_roles/create.rb | 6 +- .../beta/restrict_member_roles.yml | 9 ++ ee/spec/features/admin/member_roles_spec.rb | 48 ++++++--- ee/spec/features/groups/member_roles_spec.rb | 67 +++++++----- .../finders/member_roles/roles_finder_spec.rb | 2 +- .../member_role/create_member_role_spec.rb | 101 ++++++++++-------- 6 files changed, 146 insertions(+), 87 deletions(-) create mode 100644 ee/config/feature_flags/beta/restrict_member_roles.yml diff --git a/ee/app/graphql/mutations/member_roles/create.rb b/ee/app/graphql/mutations/member_roles/create.rb index ab825731acb11..49924de1819d1 100644 --- a/ee/app/graphql/mutations/member_roles/create.rb +++ b/ee/app/graphql/mutations/member_roles/create.rb @@ -49,7 +49,7 @@ def authorize_admin_roles!(group) end def authorize_group_member_roles!(group) - raise_resource_not_available_error! unless saas? + raise_resource_not_available_error! if restrict_member_roles? && !saas? raise_resource_not_available_error! unless Ability.allowed?(current_user, :admin_member_role, group) raise_resource_not_available_error! unless group.custom_roles_enabled? end @@ -75,6 +75,10 @@ def canonicalize(args) new_args[permission.downcase] = true end end + + def restrict_member_roles? + Feature.enabled?(:restrict_member_roles, type: :beta) + end end end end diff --git a/ee/config/feature_flags/beta/restrict_member_roles.yml b/ee/config/feature_flags/beta/restrict_member_roles.yml new file mode 100644 index 0000000000000..5f94cc05989f4 --- /dev/null +++ b/ee/config/feature_flags/beta/restrict_member_roles.yml @@ -0,0 +1,9 @@ +--- +name: restrict_member_roles +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/439167 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/142637 +rollout_issue_url: https://gitlab.com/gitlab-com/gl-infra/production/-/issues/17458 +milestone: '16.9' +group: group::authorization +type: beta +default_enabled: false \ No newline at end of file diff --git a/ee/spec/features/admin/member_roles_spec.rb b/ee/spec/features/admin/member_roles_spec.rb index 023cb91a42bfa..22527be7765b3 100644 --- a/ee/spec/features/admin/member_roles_spec.rb +++ b/ee/spec/features/admin/member_roles_spec.rb @@ -11,10 +11,6 @@ let(:permission_name) { permission.to_s.humanize } let(:access_level) { 'Developer' } - before_all do - sign_in(admin) - end - before do stub_licensed_features(custom_roles: true) end @@ -37,21 +33,45 @@ def created_role(name, id, access_level, permissions) before do allow(Gitlab::CustomRoles::Definition).to receive(:all).and_return(permissions) - visit admin_application_settings_roles_and_permissions_path + gitlab_sign_in(admin) + end + + shared_examples 'creates a new custom role' do + it 'and displays it' do + create_role(access_level, name, [permission_name]) + + created_member_role = MemberRole.find_by( + name: name, + base_access_level: Gitlab::Access.options[access_level], + permission => true) + + expect(created_member_role).not_to be_nil + + role = created_role(name, created_member_role.id, access_level, [permission_name]) + expect(page).to have_content(role) + end end - it 'creates a new custom role' do - create_role(access_level, name, [permission_name]) + context 'when on self-managed' do + before do + stub_saas_features(group_custom_roles: false) + + visit admin_application_settings_roles_and_permissions_path + end + + it_behaves_like 'creates a new custom role' + end - created_member_role = MemberRole.find_by( - name: name, - base_access_level: Gitlab::Access.options[access_level], - permission => true) + context 'when on SaaS', :saas do + before do + visit admin_application_settings_roles_and_permissions_path + end - expect(created_member_role).not_to be_nil + it 'shows an error message' do + create_role(access_level, name, [permission_name]) - role = created_role(name, created_member_role.id, access_level, [permission_name]) - expect(page).to have_content(role) + expect(page).to have_content('Failed to create role') + end end end end diff --git a/ee/spec/features/groups/member_roles_spec.rb b/ee/spec/features/groups/member_roles_spec.rb index d8b72feb66286..a499ddbb16d98 100644 --- a/ee/spec/features/groups/member_roles_spec.rb +++ b/ee/spec/features/groups/member_roles_spec.rb @@ -39,46 +39,59 @@ def created_role(name, id, access_level, permissions) allow(Gitlab::CustomRoles::Definition).to receive(:all).and_return(permissions) sign_in(user) - visit group_settings_roles_and_permissions_path(group) end - it 'creates a new custom role' do - create_role(access_level, name, [permission_name]) + shared_examples 'creates a new custom role' do + it 'and displays it' do + create_role(access_level, name, [permission_name]) - created_member_role = MemberRole.find_by( - name: name, - base_access_level: Gitlab::Access.options[access_level], - permission => true) + created_member_role = MemberRole.find_by( + name: name, + base_access_level: Gitlab::Access.options[access_level], + permission => true) - expect(created_member_role).not_to be_nil + expect(created_member_role).not_to be_nil - role = created_role(name, created_member_role.id, access_level, [permission_name]) - expect(page).to have_content(role) + role = created_role(name, created_member_role.id, access_level, [permission_name]) + expect(page).to have_content(role) + end end - context 'when the permission has a requirement' do - let(:permissions) do - { admin_vulnerability: { name: 'admin_vulnerability', requirements: ['read_vulnerability'] }, - read_vulnerability: { name: 'read_vulnerability' } } + context 'when on SaaS' do + before do + visit group_settings_roles_and_permissions_path(group) end - let(:permission) { :admin_vulnerability } - let(:requirement) { permissions[permission][:requirements].first } - let(:requirement_name) { requirement.to_s.humanize } + it_behaves_like 'creates a new custom role' + end - it 'creates the custom role' do - create_role(access_level, name, [permission_name]) + context 'when on self-managed' do + before do + stub_saas_features(group_custom_roles: false) + end - created_member_role = MemberRole.find_by( - name: name, - base_access_level: Gitlab::Access.options[access_level], - permission => true, - requirement => true) + context 'when restrict_member_roles feature-flag is disabled' do + before do + stub_feature_flags(restrict_member_roles: false) - expect(created_member_role).not_to be_nil + visit group_settings_roles_and_permissions_path(group) + end - role = created_role(name, created_member_role.id, access_level, [permission_name, requirement_name]) - expect(page).to have_content(role) + it_behaves_like 'creates a new custom role' + end + + context 'when restrict_member_roles feature-flag is enabled' do + before do + stub_feature_flags(restrict_member_roles: true) + + visit group_settings_roles_and_permissions_path(group) + end + + it 'shows an error message' do + create_role(access_level, name, [permission_name]) + + expect(page).to have_content('Failed to create role') + end end end end diff --git a/ee/spec/finders/member_roles/roles_finder_spec.rb b/ee/spec/finders/member_roles/roles_finder_spec.rb index 6ca297eaa0877..b42c39fd49f89 100644 --- a/ee/spec/finders/member_roles/roles_finder_spec.rb +++ b/ee/spec/finders/member_roles/roles_finder_spec.rb @@ -174,7 +174,7 @@ let(:current_user) { admin } context 'when on self-managed' do - it 'returns instance member roles for instance admin' do + it 'returns instance member roles' do expect(find_member_roles).to eq([member_role_instance]) end end diff --git a/ee/spec/requests/api/graphql/mutations/member_role/create_member_role_spec.rb b/ee/spec/requests/api/graphql/mutations/member_role/create_member_role_spec.rb index 0e3373ea6a31b..443f2fdc95813 100644 --- a/ee/spec/requests/api/graphql/mutations/member_role/create_member_role_spec.rb +++ b/ee/spec/requests/api/graphql/mutations/member_role/create_member_role_spec.rb @@ -38,6 +38,28 @@ subject(:create_member_role) { graphql_mutation_response(:member_role_create) } + shared_examples 'a mutation that creates a member role' do + it 'returns success', :aggregate_failures do + post_graphql_mutation(mutation, current_user: current_user) + + expect(graphql_errors).to be_nil + + expect(create_member_role['memberRole']['enabledPermissions']['nodes'].flat_map(&:values)) + .to match_array(permissions) + end + + it 'creates the member role', :aggregate_failures do + expect { post_graphql_mutation(mutation, current_user: current_user) } + .to change { MemberRole.count }.by(1) + + member_role = MemberRole.last + + expect(member_role.read_vulnerability).to eq(true) + + expect(member_role.namespace).to eq(group) + end + end + context 'without the custom roles feature' do before do stub_licensed_features(custom_roles: false) @@ -74,49 +96,32 @@ end context 'when on self-managed' do - it_behaves_like 'a mutation that returns a top-level access error' - end + context 'when restrict_member_roles feature-flag is disabled' do + before do + stub_feature_flags(restrict_member_roles: false) + end - context 'when on Saas', :saas do - context 'with valid arguments' do - it 'returns success' do - post_graphql_mutation(mutation, current_user: current_user) + it_behaves_like 'a mutation that creates a member role' + end - expect(graphql_errors).to be_nil - expect(create_member_role['memberRole']['enabledPermissions']['nodes'].flat_map(&:values)) - .to match_array(MemberRole.all_customizable_permissions.keys.map(&:to_s).map(&:upcase)) + context 'when restrict_member_roles feature-flag is enabled' do + before do + stub_feature_flags(restrict_member_roles: true) end - it 'creates the member role' do - expect { post_graphql_mutation(mutation, current_user: current_user) } - .to change { MemberRole.count }.by(1) - - member_role = MemberRole.last + it_behaves_like 'a mutation that returns a top-level access error' + end + end - expect(member_role.read_vulnerability).to eq(true) - expect(member_role.namespace).to eq(group) - end + context 'when on SaaS', :saas do + context 'with valid arguments' do + it_behaves_like 'a mutation that creates a member role' end context 'with an array of permissions' do let(:permissions) { ['READ_VULNERABILITY'] } - it 'returns success' do - post_graphql_mutation(mutation, current_user: current_user) - - expect(graphql_errors).to be_nil - mutation_response = create_member_role['memberRole'] - expect(mutation_response['enabledPermissions']['nodes'].flat_map(&:values)).to eq(['READ_VULNERABILITY']) - end - - it 'creates a member role with the specified permissions' do - expect do - post_graphql_mutation(mutation, current_user: current_user) - end.to change { MemberRole.count }.by(1) - - member_role = MemberRole.last - expect(member_role.read_vulnerability).to eq(true) - end + it_behaves_like 'a mutation that creates a member role' end context 'with an unknown permission' do @@ -139,8 +144,11 @@ end context 'when creating an instance level member role' do - before do - input.delete(:group_path) + let(:input) do + { + base_access_level: 'GUEST', + permissions: permissions + } end context 'with unauthorized user' do @@ -152,30 +160,35 @@ current_user.update!(admin: true) end - context 'when on SaaS', :saas do - it_behaves_like 'a mutation that returns top-level errors', errors: ['group_path argument is required.'] - end - - context 'when running on self-managed' do - it 'returns success' do + context 'when on self-managed' do + it 'returns success', :aggregate_failures do post_graphql_mutation(mutation, current_user: current_user) expect(graphql_errors).to be_nil + expect(create_member_role['memberRole']['enabledPermissions']['nodes'].flat_map(&:values)) - .to include('READ_VULNERABILITY') - expect(create_member_role['memberRole']['namespace']).to be_nil + .to match_array(permissions) end - it 'creates the member role' do + it 'creates the member role', :aggregate_failures do expect { post_graphql_mutation(mutation, current_user: current_user) } .to change { MemberRole.count }.by(1) member_role = MemberRole.last expect(member_role.read_vulnerability).to eq(true) + expect(member_role.namespace).to be_nil end end + + context 'when on SaaS', :saas do + before do + stub_feature_flags(restrict_member_roles: false) + end + + it_behaves_like 'a mutation that returns top-level errors', errors: ['group_path argument is required.'] + end end end end -- GitLab