From b2eacfe98aba8835c01f77d51c19a5ca56a2c3d3 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Tue, 21 Oct 2025 19:33:10 +0300 Subject: [PATCH 1/6] Fix the permissions to use single permission for read security attributes and the ability to see attributes in project or group --- config/authz/permissions/definitions_todo.txt | 1 - .../graphql/types/security/category_type.rb | 2 +- ee/app/models/security/category.rb | 4 ++ ee/app/policies/security/attribute_policy.rb | 4 ++ ee/app/policies/security/category_policy.rb | 11 ---- .../graphql/ee/types/category_type_spec.rb | 2 +- .../policies/security/category_policy_spec.rb | 50 ------------------- 7 files changed, 10 insertions(+), 64 deletions(-) delete mode 100644 ee/app/policies/security/category_policy.rb delete mode 100644 ee/spec/policies/security/category_policy_spec.rb diff --git a/config/authz/permissions/definitions_todo.txt b/config/authz/permissions/definitions_todo.txt index 5081bbef1d35d2..4fb0f7047f2db3 100644 --- a/config/authz/permissions/definitions_todo.txt +++ b/config/authz/permissions/definitions_todo.txt @@ -671,7 +671,6 @@ read_scan read_secret_push_protection_info read_secure_files read_security_attribute -read_security_category read_security_configuration read_security_inventory read_security_orchestration_policies diff --git a/ee/app/graphql/types/security/category_type.rb b/ee/app/graphql/types/security/category_type.rb index 1eb7701ea48878..95dc1dbe17bc69 100644 --- a/ee/app/graphql/types/security/category_type.rb +++ b/ee/app/graphql/types/security/category_type.rb @@ -6,7 +6,7 @@ class CategoryType < BaseObject graphql_name 'SecurityCategory' description 'A security category' - authorize :read_security_category + authorize :read_security_attribute field :description, GraphQL::Types::String, null: true, diff --git a/ee/app/models/security/category.rb b/ee/app/models/security/category.rb index 5f26467aac2948..9db2758e7f692d 100644 --- a/ee/app/models/security/category.rb +++ b/ee/app/models/security/category.rb @@ -5,6 +5,10 @@ class Category < ::SecApplicationRecord self.table_name = 'security_categories' MAX_ATTRIBUTES = 50 + def self.declarative_policy_class + 'Security::AttributePolicy' + end + belongs_to :namespace, optional: false has_many :security_attributes, class_name: 'Security::Attribute', inverse_of: :security_category diff --git a/ee/app/policies/security/attribute_policy.rb b/ee/app/policies/security/attribute_policy.rb index ffa70ddab5bf55..c1bd61316cd151 100644 --- a/ee/app/policies/security/attribute_policy.rb +++ b/ee/app/policies/security/attribute_policy.rb @@ -7,5 +7,9 @@ class AttributePolicy < BasePolicy rule { can?(:admin_security_attributes) }.policy do enable :read_security_attribute end + + rule { can?(:read_project) | can?(:read_group) }.policy do + enable :read_security_attribute + end end end diff --git a/ee/app/policies/security/category_policy.rb b/ee/app/policies/security/category_policy.rb deleted file mode 100644 index 8a112b1826f4cc..00000000000000 --- a/ee/app/policies/security/category_policy.rb +++ /dev/null @@ -1,11 +0,0 @@ -# frozen_string_literal: true - -module Security - class CategoryPolicy < BasePolicy - delegate { @subject.namespace } - - rule { can?(:admin_security_attributes) }.policy do - enable :read_security_category - end - end -end diff --git a/ee/spec/graphql/ee/types/category_type_spec.rb b/ee/spec/graphql/ee/types/category_type_spec.rb index 9fbc7110ecfd92..635ccad77dd0cb 100644 --- a/ee/spec/graphql/ee/types/category_type_spec.rb +++ b/ee/spec/graphql/ee/types/category_type_spec.rb @@ -18,7 +18,7 @@ end it { expect(described_class.graphql_name).to eq('SecurityCategory') } - it { expect(described_class).to require_graphql_authorizations(:read_security_category) } + it { expect(described_class).to require_graphql_authorizations(:read_security_attribute) } describe 'fields' do it { expect(described_class).to have_graphql_field(:id, resolver_method: :resolve_id) } diff --git a/ee/spec/policies/security/category_policy_spec.rb b/ee/spec/policies/security/category_policy_spec.rb deleted file mode 100644 index 4d112c90ebd7d2..00000000000000 --- a/ee/spec/policies/security/category_policy_spec.rb +++ /dev/null @@ -1,50 +0,0 @@ -# frozen_string_literal: true - -require 'spec_helper' - -RSpec.describe Security::CategoryPolicy, feature_category: :security_asset_inventories do - let_it_be(:root_group) { create(:group) } - let_it_be(:group) { create(:group, parent: root_group) } - let_it_be(:user) { create(:user) } - let_it_be(:category) { create(:security_category, namespace: root_group) } - - subject { described_class.new(user, category) } - - context 'when user is maintainer of root group' do - before_all do - root_group.add_maintainer(user) - end - - it { is_expected.to be_allowed(:read_security_category) } - end - - context 'when user is maintainer of subgroup' do - before_all do - group.add_maintainer(user) - end - - it { is_expected.to be_disallowed(:read_security_category) } - end - - context 'when user has developer access to root_group' do - before_all do - root_group.add_developer(user) - end - - it { is_expected.to be_disallowed(:read_security_category) } - end - - context 'when user has no access to any group' do - subject { described_class.new(user, category) } - - it { is_expected.to be_disallowed(:read_security_category) } - end - - context 'when user is owner of subgroup' do - before_all do - group.add_owner(user) - end - - it { is_expected.to be_disallowed(:read_security_category) } - end -end -- GitLab From 0a9414aff99ba8b6c2eb782c18b44bcd55b24fdc Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Wed, 22 Oct 2025 17:21:20 +0300 Subject: [PATCH 2/6] Improve permissions validation for security categories and attributes Changelog: added EE: true --- ee/app/graphql/ee/types/group_type.rb | 1 - .../resolvers/security/attributes_resolver.rb | 2 ++ .../resolvers/security/category_resolver.rb | 2 +- .../graphql/types/security/attribute_type.rb | 4 +-- .../graphql/types/security/category_type.rb | 4 +-- ee/app/policies/ee/group_policy.rb | 6 +++++ ee/app/policies/security/attribute_policy.rb | 4 --- .../read_security_attribute.yml | 11 ++++++++ ...custom_ability_read_security_attribute.yml | 10 +++++++ .../graphql/ee/types/attribute_type_spec.rb | 1 - .../graphql/ee/types/category_type_spec.rb | 1 - ee/spec/policies/group_policy_spec.rb | 2 ++ .../security/attribute_policy_spec.rb | 16 ++++++++++++ .../read_security_attribute/request_spec.rb | 26 +++++++++++++++++++ 14 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 ee/config/custom_abilities/read_security_attribute.yml create mode 100644 ee/config/feature_flags/beta/custom_ability_read_security_attribute.yml create mode 100644 ee/spec/requests/custom_roles/read_security_attribute/request_spec.rb diff --git a/ee/app/graphql/ee/types/group_type.rb b/ee/app/graphql/ee/types/group_type.rb index e8799b30b2a618..d0c03057f1149c 100644 --- a/ee/app/graphql/ee/types/group_type.rb +++ b/ee/app/graphql/ee/types/group_type.rb @@ -389,7 +389,6 @@ module GroupType field :security_categories, [::Types::Security::CategoryType], null: true, description: 'Security categories for the group.', - authorize: :admin_security_attributes, resolver: ::Resolvers::Security::CategoryResolver field :compliance_requirement_control_coverage, diff --git a/ee/app/graphql/resolvers/security/attributes_resolver.rb b/ee/app/graphql/resolvers/security/attributes_resolver.rb index 92504447154678..2a53c36f9e8a1e 100644 --- a/ee/app/graphql/resolvers/security/attributes_resolver.rb +++ b/ee/app/graphql/resolvers/security/attributes_resolver.rb @@ -15,6 +15,8 @@ def resolve project = object.is_a?(::Project) ? object : object.try(:project) return ::Security::Attribute.none unless project + authorize!(project.namespace) + return [] unless ::Feature.enabled?(:security_categories_and_attributes, project.root_ancestor) project.security_attributes.include_category diff --git a/ee/app/graphql/resolvers/security/category_resolver.rb b/ee/app/graphql/resolvers/security/category_resolver.rb index ad057827ab72c0..2de1692b3b1c20 100644 --- a/ee/app/graphql/resolvers/security/category_resolver.rb +++ b/ee/app/graphql/resolvers/security/category_resolver.rb @@ -7,7 +7,7 @@ class CategoryResolver < BaseResolver type Types::Security::CategoryType.connection_type, null: true - authorize :admin_security_attributes + authorize :read_security_attribute description 'Resolves security categories for a group.' diff --git a/ee/app/graphql/types/security/attribute_type.rb b/ee/app/graphql/types/security/attribute_type.rb index da4f855ab9769c..04d84b8d2f9797 100644 --- a/ee/app/graphql/types/security/attribute_type.rb +++ b/ee/app/graphql/types/security/attribute_type.rb @@ -2,12 +2,10 @@ module Types module Security - class AttributeType < BaseObject + class AttributeType < BaseObject # rubocop:disable Graphql/AuthorizeTypes -- Authorization is done in resolver layer based on viewing context graphql_name 'SecurityAttribute' description 'A security attribute' - authorize :read_security_attribute - field :color, Types::ColorType, null: false, description: 'Color of the security attribute.' diff --git a/ee/app/graphql/types/security/category_type.rb b/ee/app/graphql/types/security/category_type.rb index 95dc1dbe17bc69..8cec72ad365784 100644 --- a/ee/app/graphql/types/security/category_type.rb +++ b/ee/app/graphql/types/security/category_type.rb @@ -2,12 +2,10 @@ module Types module Security - class CategoryType < BaseObject + class CategoryType < BaseObject # rubocop:disable Graphql/AuthorizeTypes -- Authorization is done in resolver layer based on viewing context graphql_name 'SecurityCategory' description 'A security category' - authorize :read_security_attribute - field :description, GraphQL::Types::String, null: true, description: 'Description of the security category.' diff --git a/ee/app/policies/ee/group_policy.rb b/ee/app/policies/ee/group_policy.rb index c118ccc70ae3a0..89467e1c79e4a8 100644 --- a/ee/app/policies/ee/group_policy.rb +++ b/ee/app/policies/ee/group_policy.rb @@ -663,6 +663,12 @@ module GroupPolicy rule { custom_role_enables_admin_security_attributes }.enable(:admin_security_attributes) + rule { can?(:admin_security_attributes) }.policy do + enable :read_security_attribute + end + + rule { custom_role_enables_read_security_attribute }.enable(:read_security_attribute) + rule { ~security_inventory_available }.prevent :read_security_inventory rule { custom_role_enables_read_vulnerability }.policy do diff --git a/ee/app/policies/security/attribute_policy.rb b/ee/app/policies/security/attribute_policy.rb index c1bd61316cd151..ffa70ddab5bf55 100644 --- a/ee/app/policies/security/attribute_policy.rb +++ b/ee/app/policies/security/attribute_policy.rb @@ -7,9 +7,5 @@ class AttributePolicy < BasePolicy rule { can?(:admin_security_attributes) }.policy do enable :read_security_attribute end - - rule { can?(:read_project) | can?(:read_group) }.policy do - enable :read_security_attribute - end end end diff --git a/ee/config/custom_abilities/read_security_attribute.yml b/ee/config/custom_abilities/read_security_attribute.yml new file mode 100644 index 00000000000000..a1be3edb7e2db2 --- /dev/null +++ b/ee/config/custom_abilities/read_security_attribute.yml @@ -0,0 +1,11 @@ +--- +title: View security attributes +name: read_security_attribute +description: View the security attributes belonging to a top-level group. +introduced_by_issue: https://gitlab.com/gitlab-org/gitlab/-/issues/567237 +introduced_by_mr: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/209670 +feature_category: security_asset_inventories +milestone: '18.6' +group_ability: true +enabled_for_group_access_levels: [50] +project_ability: false diff --git a/ee/config/feature_flags/beta/custom_ability_read_security_attribute.yml b/ee/config/feature_flags/beta/custom_ability_read_security_attribute.yml new file mode 100644 index 00000000000000..5237123922d926 --- /dev/null +++ b/ee/config/feature_flags/beta/custom_ability_read_security_attribute.yml @@ -0,0 +1,10 @@ +--- +name: custom_ability_read_security_attribute +description: +feature_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/567237 +introduced_by_url: https://gitlab.com/gitlab-org/gitlab/-/merge_requests/209670 +rollout_issue_url: +milestone: '18.6' +group: group::security platform management +type: beta +default_enabled: false diff --git a/ee/spec/graphql/ee/types/attribute_type_spec.rb b/ee/spec/graphql/ee/types/attribute_type_spec.rb index 513f42e5205a3a..d491891b97a3a1 100644 --- a/ee/spec/graphql/ee/types/attribute_type_spec.rb +++ b/ee/spec/graphql/ee/types/attribute_type_spec.rb @@ -17,7 +17,6 @@ end it { expect(described_class.graphql_name).to eq('SecurityAttribute') } - it { expect(described_class).to require_graphql_authorizations(:read_security_attribute) } describe 'fields' do it { expect(described_class).to have_graphql_field(:id, resolver_method: :resolve_id) } diff --git a/ee/spec/graphql/ee/types/category_type_spec.rb b/ee/spec/graphql/ee/types/category_type_spec.rb index 635ccad77dd0cb..e1148520b418b2 100644 --- a/ee/spec/graphql/ee/types/category_type_spec.rb +++ b/ee/spec/graphql/ee/types/category_type_spec.rb @@ -18,7 +18,6 @@ end it { expect(described_class.graphql_name).to eq('SecurityCategory') } - it { expect(described_class).to require_graphql_authorizations(:read_security_attribute) } describe 'fields' do it { expect(described_class).to have_graphql_field(:id, resolver_method: :resolve_id) } diff --git a/ee/spec/policies/group_policy_spec.rb b/ee/spec/policies/group_policy_spec.rb index 90f28c23f4e104..7e0f70c0ce2d74 100644 --- a/ee/spec/policies/group_policy_spec.rb +++ b/ee/spec/policies/group_policy_spec.rb @@ -5111,12 +5111,14 @@ def create_member_role(member, abilities = member_role_abilities) let(:current_user) { maintainer } it { is_expected.to be_allowed(:admin_security_attributes) } + it { is_expected.to be_allowed(:read_security_attribute) } end context 'when user is developer' do let(:current_user) { developer } it { is_expected.to be_disallowed(:admin_security_attributes) } + it { is_expected.to be_disallowed(:read_security_attribute) } end end end diff --git a/ee/spec/policies/security/attribute_policy_spec.rb b/ee/spec/policies/security/attribute_policy_spec.rb index 9833d40f7eabba..c8786217d86fa7 100644 --- a/ee/spec/policies/security/attribute_policy_spec.rb +++ b/ee/spec/policies/security/attribute_policy_spec.rb @@ -47,4 +47,20 @@ it { is_expected.to be_disallowed(:read_security_attribute) } end + + context 'when user has reporter access to root group' do + before_all do + root_group.add_reporter(user) + end + + it { is_expected.to be_disallowed(:read_security_attribute) } + end + + context 'when user has guest access to root group' do + before_all do + root_group.add_guest(user) + end + + it { is_expected.to be_disallowed(:read_security_attribute) } + end end diff --git a/ee/spec/requests/custom_roles/read_security_attribute/request_spec.rb b/ee/spec/requests/custom_roles/read_security_attribute/request_spec.rb new file mode 100644 index 00000000000000..73129459d7f0af --- /dev/null +++ b/ee/spec/requests/custom_roles/read_security_attribute/request_spec.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'User with read_security_attribute custom role', feature_category: :security_asset_inventories do + let_it_be(:user) { create(:user) } + let_it_be(:group) { create(:group) } + let_it_be(:role) { create(:member_role, :reporter, namespace: group, read_security_attribute: true) } + let_it_be(:membership) { create(:group_member, :reporter, member_role: role, user: user, group: group) } + + before do + stub_licensed_features(custom_roles: true, security_attributes: true) + + sign_in(user) + end + + describe "Controllers endpoints" do + describe Groups::Security::ConfigurationController do + it 'can access the show endpoint' do + get group_security_configuration_path(group) + + expect(response).to have_gitlab_http_status(:ok) + end + end + end +end -- GitLab From af3044179ae1b994f756e93b0b41ba14a3f53e0a Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Wed, 22 Oct 2025 17:24:07 +0300 Subject: [PATCH 3/6] Fix graphql docs --- doc/api/graphql/reference/_index.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/doc/api/graphql/reference/_index.md b/doc/api/graphql/reference/_index.md index 8d1737a575d453..c1f6ed71ecdca4 100644 --- a/doc/api/graphql/reference/_index.md +++ b/doc/api/graphql/reference/_index.md @@ -49988,6 +49988,7 @@ Member role permission. | `READ_CRM_CONTACT` | Read CRM contact. | | `READ_DEPENDENCY` | Allows read-only access to the dependencies and licenses. | | `READ_RUNNERS` | Allows read-only access to group or project runners, including the runner fleet dashboard. | +| `READ_SECURITY_ATTRIBUTE` | View the security attributes belonging to a top-level group. | | `READ_VULNERABILITY` | Read vulnerability reports and security dashboards. | | `REMOVE_GROUP` | Ability to delete or restore a group. This ability does not allow deleting top-level groups. Review the Retention period settings to prevent accidental deletion. | | `REMOVE_PROJECT` | Allows deletion of projects. | @@ -50024,6 +50025,7 @@ Member role standard permission. | `READ_CRM_CONTACT` | Read CRM contact. | | `READ_DEPENDENCY` | Allows read-only access to the dependencies and licenses. | | `READ_RUNNERS` | Allows read-only access to group or project runners, including the runner fleet dashboard. | +| `READ_SECURITY_ATTRIBUTE` {{< icon name="warning-solid" >}} | **Introduced** in GitLab 18.6. **Status**: Experiment. View the security attributes belonging to a top-level group. | | `READ_VULNERABILITY` | Read vulnerability reports and security dashboards. | | `REMOVE_GROUP` | Ability to delete or restore a group. This ability does not allow deleting top-level groups. Review the Retention period settings to prevent accidental deletion. | | `REMOVE_PROJECT` | Allows deletion of projects. | -- GitLab From 7dae70b242be0d955427216a8fabba911256d5c9 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Wed, 22 Oct 2025 20:38:16 +0300 Subject: [PATCH 4/6] Fix failing pipeline --- doc/api/openapi/openapi_v2.yaml | 2 + .../read_security_attribute/request_spec.rb | 49 ++++++++++++++++--- 2 files changed, 44 insertions(+), 7 deletions(-) diff --git a/doc/api/openapi/openapi_v2.yaml b/doc/api/openapi/openapi_v2.yaml index d56488608333d2..e940c77c55d263 100644 --- a/doc/api/openapi/openapi_v2.yaml +++ b/doc/api/openapi/openapi_v2.yaml @@ -52328,6 +52328,8 @@ definitions: type: boolean read_runners: type: boolean + read_security_attribute: + type: boolean read_admin_subscription: type: boolean read_admin_monitoring: diff --git a/ee/spec/requests/custom_roles/read_security_attribute/request_spec.rb b/ee/spec/requests/custom_roles/read_security_attribute/request_spec.rb index 73129459d7f0af..53ed53cae2ef49 100644 --- a/ee/spec/requests/custom_roles/read_security_attribute/request_spec.rb +++ b/ee/spec/requests/custom_roles/read_security_attribute/request_spec.rb @@ -3,23 +3,58 @@ require 'spec_helper' RSpec.describe 'User with read_security_attribute custom role', feature_category: :security_asset_inventories do + include GraphqlHelpers + let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } let_it_be(:role) { create(:member_role, :reporter, namespace: group, read_security_attribute: true) } let_it_be(:membership) { create(:group_member, :reporter, member_role: role, user: user, group: group) } + let_it_be(:category) { create(:security_category, namespace: group) } + + let(:query) do + <<~GQL + query { + group(fullPath: "#{group.full_path}") { + securityCategories { + nodes { + id + name + description + } + } + } + } + GQL + end before do stub_licensed_features(custom_roles: true, security_attributes: true) - - sign_in(user) + stub_feature_flags(security_categories_and_attributes: true) end - describe "Controllers endpoints" do - describe Groups::Security::ConfigurationController do - it 'can access the show endpoint' do - get group_security_configuration_path(group) + describe "GraphQL queries" do + describe 'security categories query' do + it 'can access security categories' do + post_graphql(query, current_user: user) + + expect(response).to have_gitlab_http_status(:success) + expect(graphql_errors).to be_blank + expect(graphql_data_at(:group, :security_categories, :nodes)).to be_present + end + end + + context 'when user does not have the custom role' do + let_it_be(:user_without_permission) { create(:user) } + let_it_be(:membership_without_role) do + create(:group_member, :reporter, user: user_without_permission, group: group) + end + + it 'cannot access security categories' do + post_graphql(query, current_user: user_without_permission) - expect(response).to have_gitlab_http_status(:ok) + expect(response).to have_gitlab_http_status(:success) + expect(graphql_errors).to be_present + expect(graphql_errors.first['message']).to match(/you do not have the required permission/) end end end -- GitLab From bfbfa72c9ae2d9c82e1f13080949bc711f9f5a7e Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Thu, 23 Oct 2025 11:17:50 +0300 Subject: [PATCH 5/6] Fix the read security attributes request spec and the attribute resolver authorize check --- .../resolvers/security/attributes_resolver.rb | 4 +- .../read_security_attribute/request_spec.rb | 106 +++++++++++++----- 2 files changed, 83 insertions(+), 27 deletions(-) diff --git a/ee/app/graphql/resolvers/security/attributes_resolver.rb b/ee/app/graphql/resolvers/security/attributes_resolver.rb index 2a53c36f9e8a1e..4ccdc068abac8f 100644 --- a/ee/app/graphql/resolvers/security/attributes_resolver.rb +++ b/ee/app/graphql/resolvers/security/attributes_resolver.rb @@ -15,7 +15,9 @@ def resolve project = object.is_a?(::Project) ? object : object.try(:project) return ::Security::Attribute.none unless project - authorize!(project.namespace) + # Use authorized_resource? instead of authorize! to avoid raising an exception + # This returns a boolean, allowing graceful handling when permission is denied + return ::Security::Attribute.none unless authorized_resource?(project.namespace) return [] unless ::Feature.enabled?(:security_categories_and_attributes, project.root_ancestor) diff --git a/ee/spec/requests/custom_roles/read_security_attribute/request_spec.rb b/ee/spec/requests/custom_roles/read_security_attribute/request_spec.rb index 53ed53cae2ef49..05dff4d48817c8 100644 --- a/ee/spec/requests/custom_roles/read_security_attribute/request_spec.rb +++ b/ee/spec/requests/custom_roles/read_security_attribute/request_spec.rb @@ -5,41 +5,89 @@ RSpec.describe 'User with read_security_attribute custom role', feature_category: :security_asset_inventories do include GraphqlHelpers - let_it_be(:user) { create(:user) } let_it_be(:group) { create(:group) } + let_it_be(:project) { create(:project, group: group) } + let_it_be(:user) { create(:user) } let_it_be(:role) { create(:member_role, :reporter, namespace: group, read_security_attribute: true) } let_it_be(:membership) { create(:group_member, :reporter, member_role: role, user: user, group: group) } - let_it_be(:category) { create(:security_category, namespace: group) } - - let(:query) do - <<~GQL - query { - group(fullPath: "#{group.full_path}") { - securityCategories { - nodes { - id - name - description - } - } - } - } - GQL - end before do stub_licensed_features(custom_roles: true, security_attributes: true) stub_feature_flags(security_categories_and_attributes: true) + + # Clear and stub GraphqlKnownOperations to avoid "undefined method `map' for String" error + Gitlab::Webpack::GraphqlKnownOperations.clear_memoization! + allow(Gitlab::Webpack::GraphqlKnownOperations).to receive(:load).and_return(['SubgroupsAndProjects']) end describe "GraphQL queries" do - describe 'security categories query' do - it 'can access security categories' do - post_graphql(query, current_user: user) + let_it_be(:category) { create(:security_category, namespace: group) } + let_it_be(:attribute) { create(:security_attribute, namespace: group, security_category: category) } + let_it_be(:project_attribute) do + create(:project_to_security_attribute, project: project, security_attribute: attribute) + end + + let(:query) do + <<~GQL + query SubgroupsAndProjects($fullPath: ID!, $projectsFirst: Int, + $projectsAfter: String, $search: String, $hasSearch: Boolean!) { + group(fullPath: $fullPath) { + id + projects( + first: $projectsFirst + after: $projectsAfter + search: $search + includeSubgroups: $hasSearch + includeArchived: false + ) @skip(if: $hasSearch) { + pageInfo { + hasNextPage + endCursor + } + nodes { + id + name + path + fullPath + securityAttributes { + nodes { + id + securityCategory { + id + name + } + name + description + color + } + } + } + } + } + } + GQL + end + + let(:variables) do + { + fullPath: group.full_path, + search: "", + hasSearch: false, + projectsFirst: 20, + projectsAfter: nil + } + end + + describe 'security attributes query for projects' do + it 'can access security attributes for projects' do + post_graphql(query, current_user: user, variables: variables) expect(response).to have_gitlab_http_status(:success) expect(graphql_errors).to be_blank - expect(graphql_data_at(:group, :security_categories, :nodes)).to be_present + + projects_data = graphql_data.dig('group', 'projects', 'nodes') + expect(projects_data).to be_present + expect(projects_data.first.dig('securityAttributes', 'nodes')).not_to be_empty end end @@ -49,12 +97,18 @@ create(:group_member, :reporter, user: user_without_permission, group: group) end - it 'cannot access security categories' do - post_graphql(query, current_user: user_without_permission) + it 'cannot access security attributes' do + post_graphql(query, current_user: user_without_permission, variables: variables) expect(response).to have_gitlab_http_status(:success) - expect(graphql_errors).to be_present - expect(graphql_errors.first['message']).to match(/you do not have the required permission/) + + # Authorization should prevent access - either through errors or empty results + if graphql_errors.blank? + projects_data = graphql_data.dig('group', 'projects', 'nodes') + expect(projects_data.first.dig('securityAttributes', 'nodes')).to be_empty + else + expect(graphql_errors).to be_present + end end end end -- GitLab From a30747b31321b2fade084ba4bb46890a5df714a5 Mon Sep 17 00:00:00 2001 From: Miki Amos Date: Thu, 23 Oct 2025 11:46:23 +0300 Subject: [PATCH 6/6] Fix the attribute resolver spec file --- ee/spec/graphql/resolvers/security/attributes_resolver_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ee/spec/graphql/resolvers/security/attributes_resolver_spec.rb b/ee/spec/graphql/resolvers/security/attributes_resolver_spec.rb index ea3775f3e69aa8..c3fa5c608d3bb2 100644 --- a/ee/spec/graphql/resolvers/security/attributes_resolver_spec.rb +++ b/ee/spec/graphql/resolvers/security/attributes_resolver_spec.rb @@ -41,7 +41,7 @@ context 'when user has permission' do before_all do - project.add_maintainer(current_user) + sub_group.add_maintainer(current_user) end context 'when project has linked attributes' do -- GitLab