diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 5ded21eeb60771cf1e4620fa14f36d072d38eef2..532c9ed9a217edb5f70562ffda981fbc5436c9e6 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -16719,8 +16719,10 @@ Represents the scan result policy. | ---- | ---- | ----------- | | <a id="scanresultpolicydescription"></a>`description` | [`String!`](#string) | Description of the policy. | | <a id="scanresultpolicyenabled"></a>`enabled` | [`Boolean!`](#boolean) | Indicates whether this policy is enabled. | +| <a id="scanresultpolicygroupapprovers"></a>`groupApprovers` | [`[Group!]`](#group) | Approvers of the group type. | | <a id="scanresultpolicyname"></a>`name` | [`String!`](#string) | Name of the policy. | | <a id="scanresultpolicyupdatedat"></a>`updatedAt` | [`Time!`](#time) | Timestamp of when the policy YAML was last updated. | +| <a id="scanresultpolicyuserapprovers"></a>`userApprovers` | [`[UserCore!]`](#usercore) | Approvers of the user type. | | <a id="scanresultpolicyyaml"></a>`yaml` | [`String!`](#string) | YAML definition of the policy. | ### `ScannedResource` diff --git a/ee/app/graphql/resolvers/security_orchestration/scan_result_policy_resolver.rb b/ee/app/graphql/resolvers/security_orchestration/scan_result_policy_resolver.rb index 88646f00e663b5cd54cc47724281cb22eb0cff57..5a0f04d7349f7d1df01931d9e8b3f9e4ddcdd5d5 100644 --- a/ee/app/graphql/resolvers/security_orchestration/scan_result_policy_resolver.rb +++ b/ee/app/graphql/resolvers/security_orchestration/scan_result_policy_resolver.rb @@ -13,15 +13,26 @@ def resolve(**args) authorize! policy_configuration.scan_result_policies.map do |policy| + approvers = approvers(policy) { name: policy[:name], description: policy[:description], enabled: policy[:enabled], yaml: YAML.dump(policy.deep_stringify_keys), - updated_at: policy_configuration.policy_last_updated_at + updated_at: policy_configuration.policy_last_updated_at, + user_approvers: approvers[:users], + group_approvers: approvers[:groups] } end end + + private + + def approvers(policy) + Security::SecurityOrchestrationPolicies::FetchPolicyApproversService + .new(policy: policy, project: project, current_user: context[:current_user]) + .execute + end end end end diff --git a/ee/app/graphql/types/security_orchestration/scan_result_policy_type.rb b/ee/app/graphql/types/security_orchestration/scan_result_policy_type.rb index fc052ddaf1acdfa1854fda2becb8b50fb62d8453..b96a1e9a6503660eb0d985bffea709a42be7a35e 100644 --- a/ee/app/graphql/types/security_orchestration/scan_result_policy_type.rb +++ b/ee/app/graphql/types/security_orchestration/scan_result_policy_type.rb @@ -10,6 +10,9 @@ class ScanResultPolicyType < BaseObject description 'Represents the scan result policy' implements OrchestrationPolicyType + + field :group_approvers, ['::Types::GroupType'], null: true, description: 'Approvers of the group type.' + field :user_approvers, [::Types::UserType], null: true, description: 'Approvers of the user type.' end # rubocop: enable Graphql/AuthorizeTypes end diff --git a/ee/spec/graphql/resolvers/security_orchestration/scan_result_policy_resolver_spec.rb b/ee/spec/graphql/resolvers/security_orchestration/scan_result_policy_resolver_spec.rb index 926067226d17f45ab4d6ba5010a707dca26ef645..6a7d18471eac2c22a1cbe9ede23f804e50b95d49 100644 --- a/ee/spec/graphql/resolvers/security_orchestration/scan_result_policy_resolver_spec.rb +++ b/ee/spec/graphql/resolvers/security_orchestration/scan_result_policy_resolver_spec.rb @@ -16,7 +16,9 @@ description: 'This policy considers only container scanning and critical severities', enabled: true, yaml: YAML.dump(policy.deep_stringify_keys), - updated_at: policy_last_updated_at + updated_at: policy_last_updated_at, + user_approvers: [], + group_approvers: [] } ] end diff --git a/ee/spec/requests/api/graphql/project/security_orchestration/scan_result_policy_spec.rb b/ee/spec/requests/api/graphql/project/security_orchestration/scan_result_policy_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..d64f71d7529fd53066b25271d9525b004b9cf29b --- /dev/null +++ b/ee/spec/requests/api/graphql/project/security_orchestration/scan_result_policy_spec.rb @@ -0,0 +1,78 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'Query.project(fullPath).scanResultPolicies' do + let_it_be(:project) { create(:project) } + let_it_be(:policy_management_project) { create(:project, :repository) } + let_it_be(:user) { policy_management_project.first_owner } + let_it_be(:group) { create(:group, :public) } + let_it_be(:action) do + { type: 'require_approval', approvals_required: 1, user_approvers_ids: [user.id], group_approvers_ids: [group.id] } + end + + let_it_be(:policy) { build(:scan_result_policy, actions: [action]) } + let_it_be(:policy_yaml) { build(:orchestration_policy_yaml, scan_result_policy: [policy]) } + let_it_be(:policy_configuration) do + create(:security_orchestration_policy_configuration, + security_policy_management_project: policy_management_project, + project: project) + end + + let_it_be(:query) do + %( + query { + project(fullPath: "#{project.full_path}") { + scanResultPolicies { + nodes{ + userApprovers{ + id + webUrl + } + groupApprovers{ + id + webUrl + } + } + } + } + } + ) + end + + let_it_be(:expected_data) do + [ + { + "userApprovers" => [ + { + "id" => "gid://gitlab/User/#{user.id}", + "webUrl" => "http://localhost/#{user.full_path}" + } + ], + "groupApprovers" => [ + { + "id" => "gid://gitlab/Group/#{group.id}", + "webUrl" => "http://localhost/groups/#{group.full_path}" + } + ] + } + ] + end + + before do + stub_licensed_features(security_orchestration_policies: true) + project.add_maintainer(user) + project.invited_groups = [group] + allow_next_instance_of(Repository) do |repository| + allow(repository).to receive(:blob_data_at).and_return(policy_yaml) + end + end + + subject { GitlabSchema.execute(query, context: { current_user: user }).as_json } + + it "returns both user and group approvers" do + result = subject.dig('data', 'project', 'scanResultPolicies', 'nodes') + + expect(result).to eq(expected_data) + end +end