diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 163c6174c9dfedcf757b319146af8591d9d79285..ab992d082b89b2cf9ee095dd42caae5661535b74 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -504,6 +504,16 @@ This field returns a [connection](#connections). It accepts the four standard [pagination arguments](#connection-pagination-arguments): `before: String`, `after: String`, `first: Int`, `last: Int`. +### `Query.memberRolePermissions` + +List of all customizable permissions. + +Returns [`CustomizablePermissionConnection`](#customizablepermissionconnection). + +This field returns a [connection](#connections). It accepts the +four standard [pagination arguments](#connection-pagination-arguments): +`before: String`, `after: String`, `first: Int`, `last: Int`. + ### `Query.mergeRequest` Find a merge request. @@ -9216,6 +9226,29 @@ The edge type for [`CustomizableDashboardVisualization`](#customizabledashboardv | <a id="customizabledashboardvisualizationedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | | <a id="customizabledashboardvisualizationedgenode"></a>`node` | [`CustomizableDashboardVisualization`](#customizabledashboardvisualization) | The item at the end of the edge. | +#### `CustomizablePermissionConnection` + +The connection type for [`CustomizablePermission`](#customizablepermission). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="customizablepermissionconnectionedges"></a>`edges` | [`[CustomizablePermissionEdge]`](#customizablepermissionedge) | A list of edges. | +| <a id="customizablepermissionconnectionnodes"></a>`nodes` | [`[CustomizablePermission]`](#customizablepermission) | A list of nodes. | +| <a id="customizablepermissionconnectionpageinfo"></a>`pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | + +#### `CustomizablePermissionEdge` + +The edge type for [`CustomizablePermission`](#customizablepermission). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="customizablepermissionedgecursor"></a>`cursor` | [`String!`](#string) | A cursor for use in pagination. | +| <a id="customizablepermissionedgenode"></a>`node` | [`CustomizablePermission`](#customizablepermission) | The item at the end of the edge. | + #### `DastProfileConnection` The connection type for [`DastProfile`](#dastprofile). @@ -15307,6 +15340,18 @@ Represents a product analytics dashboard visualization. | <a id="customizabledashboardvisualizationslug"></a>`slug` | [`String!`](#string) | Slug of the visualization. | | <a id="customizabledashboardvisualizationtype"></a>`type` | [`String!`](#string) | Type of the visualization. | +### `CustomizablePermission` + +#### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| <a id="customizablepermissionavailablefor"></a>`availableFor` | [`[String!]!`](#string) | Objects the permission is available for. | +| <a id="customizablepermissiondescription"></a>`description` | [`String`](#string) | Description of the permission. | +| <a id="customizablepermissionname"></a>`name` | [`String!`](#string) | Localized name of the permission. | +| <a id="customizablepermissionrequirement"></a>`requirement` | [`String`](#string) | Requirement of the permission. | +| <a id="customizablepermissionvalue"></a>`value` | [`String!`](#string) | Value of the permission. | + ### `DastPreScanVerification` Represents a DAST Pre Scan Verification. diff --git a/ee/app/graphql/ee/types/query_type.rb b/ee/app/graphql/ee/types/query_type.rb index f230bd22b15c3b1b2cf301ac7da8f2b361e417f1..802b827b0be15f90208c763d9774efc1c9c87eb0 100644 --- a/ee/app/graphql/ee/types/query_type.rb +++ b/ee/app/graphql/ee/types/query_type.rb @@ -129,6 +129,11 @@ module QueryType null: true, description: 'Instance level google cloud logging configurations.', resolver: ::Resolvers::AuditEvents::Instance::GoogleCloudLoggingConfigurationsResolver + field :member_role_permissions, + ::Types::MemberRoles::CustomizablePermissionType.connection_type, + resolver: ::Resolvers::MemberRoles::PermissionListResolver, + null: true, + description: 'List of all customizable permissions.' end def vulnerability(id:) diff --git a/ee/app/graphql/resolvers/member_roles/permission_list_resolver.rb b/ee/app/graphql/resolvers/member_roles/permission_list_resolver.rb new file mode 100644 index 0000000000000000000000000000000000000000..27b46bd1a2bb18cf0b9d9e06108a14cb4759c46f --- /dev/null +++ b/ee/app/graphql/resolvers/member_roles/permission_list_resolver.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Resolvers + module MemberRoles + class PermissionListResolver < BaseResolver + type Types::MemberRoles::CustomizablePermissionType, null: true + + def resolve + MemberRole::ALL_CUSTOMIZABLE_PERMISSIONS.map do |permission, definition| + definition.merge(value: permission) + end + end + end + end +end diff --git a/ee/app/graphql/types/member_roles/customizable_permission_type.rb b/ee/app/graphql/types/member_roles/customizable_permission_type.rb new file mode 100644 index 0000000000000000000000000000000000000000..0fe8185f7a669af98efcac8bc69074cc86501ecd --- /dev/null +++ b/ee/app/graphql/types/member_roles/customizable_permission_type.rb @@ -0,0 +1,34 @@ +# frozen_string_literal: true + +module Types + module MemberRoles + # rubocop: disable Graphql/AuthorizeTypes + class CustomizablePermissionType < BaseObject + graphql_name 'CustomizablePermission' + + field :available_for, [GraphQL::Types::String], null: false, + description: 'Objects the permission is available for.' + field :description, GraphQL::Types::String, null: true, description: 'Description of the permission.' + field :name, GraphQL::Types::String, null: false, + description: 'Localized name of the permission.' + field :requirement, GraphQL::Types::String, null: true, description: 'Requirement of the permission.' + field :value, GraphQL::Types::String, null: false, description: 'Value of the permission.' + + def available_for + result = [] + result << :project if MemberRole::ALL_CUSTOMIZABLE_PROJECT_PERMISSIONS.include?(object[:value]) + result << :group if MemberRole::ALL_CUSTOMIZABLE_GROUP_PERMISSIONS.include?(object[:value]) + result + end + + def description + _(object[:description]) + end + + def name + _(object[:value].to_s.humanize) + end + end + # rubocop: enable Graphql/AuthorizeTypes + end +end diff --git a/ee/app/models/members/member_role.rb b/ee/app/models/members/member_role.rb index b47a244af576ced17efb8baa5e07eb899c3f2d4f..6108f8db722f0ea0c45b2808539c8ab3a8f67c62 100644 --- a/ee/app/models/members/member_role.rb +++ b/ee/app/models/members/member_role.rb @@ -4,20 +4,20 @@ class MemberRole < ApplicationRecord # rubocop:disable Gitlab/NamespacedClass MAX_COUNT_PER_GROUP_HIERARCHY = 10 ALL_CUSTOMIZABLE_PERMISSIONS = { admin_merge_request: { - descripition: 'Permission to admin merge requests' + description: 'Allows admin access to the merge requests.' }, admin_vulnerability: { - description: 'Permission to admin vulnerability', + description: 'Allows admin access to the vulnerability reports.', requirement: :read_vulnerability }, read_code: { - description: 'Permission to read code' + description: 'Allows read-only access to the source code.' }, read_dependency: { - description: 'Permission to read dependency' + description: 'Allows read-only access to the dependencies.' }, read_vulnerability: { - description: 'Permission to read vulnerability' + description: 'Allows read-only access to the vulnerability reports.' } }.freeze ALL_CUSTOMIZABLE_PROJECT_PERMISSIONS = [ diff --git a/ee/spec/graphql/types/member_roles/customizable_permission_type_spec.rb b/ee/spec/graphql/types/member_roles/customizable_permission_type_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..725845d59bb86a40e979ae261fbc5e3d25c6bc12 --- /dev/null +++ b/ee/spec/graphql/types/member_roles/customizable_permission_type_spec.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe GitlabSchema.types['CustomizablePermission'], feature_category: :system_access do + include GraphqlHelpers + + it { expect(described_class.graphql_name).to eq('CustomizablePermission') } + + it 'has the expected fields' do + expected_fields = %w[availableFor description requirement name value] + + expect(described_class).to include_graphql_fields(*expected_fields) + end +end diff --git a/ee/spec/graphql/types/query_type_spec.rb b/ee/spec/graphql/types/query_type_spec.rb index 8a8c060dcb1d9a2529a094573ecf0433a29fc7d8..b8426fcd6d4a1daf351152a340667e58b0ccea6e 100644 --- a/ee/spec/graphql/types/query_type_spec.rb +++ b/ee/spec/graphql/types/query_type_spec.rb @@ -20,6 +20,7 @@ :instance_security_dashboard, :iteration, :license_history_entries, + :member_role_permissions, :organization, :subscription_future_entries, :vulnerabilities, diff --git a/ee/spec/requests/api/graphql/member_role/permissions_list_spec.rb b/ee/spec/requests/api/graphql/member_role/permissions_list_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..c88292a1e2466163001d13f0c2d00ada3974e81e --- /dev/null +++ b/ee/spec/requests/api/graphql/member_role/permissions_list_spec.rb @@ -0,0 +1,68 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Query.member_role_permissions', feature_category: :system_access do + include GraphqlHelpers + + let(:fields) do + <<~QUERY + nodes { + availableFor + description + name + requirement + value + } + QUERY + end + + let(:query) do + graphql_query_for('memberRolePermissions', fields) + end + + before do + stub_const('MemberRole::ALL_CUSTOMIZABLE_PERMISSIONS', + { + admin_ability_one: { + description: 'Allows admin access to do something.', + minimal_level: Gitlab::Access::GUEST + }, + admin_ability_two: { + description: 'Allows admin access to do something else.', + minimal_level: Gitlab::Access::DEVELOPER, + requirement: :read_ability_two + }, + read_ability_two: { + description: 'Allows read access to do something else.', + minimal_level: Gitlab::Access::GUEST + } + } + ) + stub_const('::MemberRole::ALL_CUSTOMIZABLE_PROJECT_PERMISSIONS', + [:admin_ability_one, :read_ability_two] + ) + stub_const('::MemberRole::ALL_CUSTOMIZABLE_GROUP_PERMISSIONS', + [:admin_ability_two, :read_ability_two] + ) + + post_graphql(query) + end + + subject { graphql_data.dig('memberRolePermissions', 'nodes') } + + it_behaves_like 'a working graphql query' + + it 'returns all customizable ablities' do + expected_result = [ + { 'availableFor' => ['project'], 'description' => 'Allows admin access to do something.', + 'name' => 'Admin ability one', 'requirement' => nil, 'value' => 'admin_ability_one' }, + { 'availableFor' => %w[project group], 'description' => 'Allows read access to do something else.', + 'name' => 'Read ability two', 'requirement' => nil, 'value' => 'read_ability_two' }, + { 'availableFor' => ['group'], 'description' => "Allows admin access to do something else.", + 'requirement' => 'read_ability_two', 'name' => 'Admin ability two', 'value' => 'admin_ability_two' } + ] + + expect(subject).to match_array(expected_result) + end +end