diff --git a/app/graphql/resolvers/project_packages_protection_rules_resolver.rb b/app/graphql/resolvers/project_packages_protection_rules_resolver.rb new file mode 100644 index 0000000000000000000000000000000000000000..5d3d0fbf79de487da7958232be5e51b04c6e9f41 --- /dev/null +++ b/app/graphql/resolvers/project_packages_protection_rules_resolver.rb @@ -0,0 +1,15 @@ +# frozen_string_literal: true + +module Resolvers + class ProjectPackagesProtectionRulesResolver < BaseResolver + type Types::Packages::Protection::RuleType.connection_type, null: true + + alias_method :project, :object + + def resolve(**_args) + return [] if Feature.disabled?(:packages_protected_packages, project) + + project.package_protection_rules + end + end +end diff --git a/app/graphql/types/packages/protection/rule_access_level_enum.rb b/app/graphql/types/packages/protection/rule_access_level_enum.rb index fbc19847bcc6a6860a686c14fb4dad2d8fb2ce18..098a3e48100dfe7bf9dee82d4df42311983c49b9 100644 --- a/app/graphql/types/packages/protection/rule_access_level_enum.rb +++ b/app/graphql/types/packages/protection/rule_access_level_enum.rb @@ -7,9 +7,10 @@ class RuleAccessLevelEnum < BaseEnum graphql_name 'PackagesProtectionRuleAccessLevel' description 'Access level of a package protection rule resource' - value 'DEVELOPER', value: Gitlab::Access::DEVELOPER, description: 'Developer access.' - value 'MAINTAINER', value: Gitlab::Access::MAINTAINER, description: 'Maintainer access.' - value 'OWNER', value: Gitlab::Access::OWNER, description: 'Owner access.' + ::Packages::Protection::Rule.push_protected_up_to_access_levels.each_key do |access_level_key| + value access_level_key.upcase, value: access_level_key.to_s, + description: "#{access_level_key.capitalize} access." + end end end end diff --git a/app/graphql/types/project_type.rb b/app/graphql/types/project_type.rb index bb0497600e512ba5b71c0a174d66677fd44b5c74..95caefc3825abe1b1d52acef05ebc8dfffcbd8ee 100644 --- a/app/graphql/types/project_type.rb +++ b/app/graphql/types/project_type.rb @@ -311,6 +311,12 @@ class ProjectType < BaseObject null: true, description: 'Packages cleanup policy for the project.' + field :packages_protection_rules, + Types::Packages::Protection::RuleType.connection_type, + null: true, + description: 'Packages protection rules for the project.', + resolver: Resolvers::ProjectPackagesProtectionRulesResolver + field :jobs, type: Types::Ci::JobType.connection_type, null: true, diff --git a/app/policies/packages/protection/rule_policy.rb b/app/policies/packages/protection/rule_policy.rb new file mode 100644 index 0000000000000000000000000000000000000000..fdf269e04cfa436431fff331df4e0018b27d5e28 --- /dev/null +++ b/app/policies/packages/protection/rule_policy.rb @@ -0,0 +1,9 @@ +# frozen_string_literal: true + +module Packages + module Protection + class RulePolicy < BasePolicy + delegate { @subject.project } + end + end +end diff --git a/doc/api/graphql/reference/index.md b/doc/api/graphql/reference/index.md index 3d0e9c113d71492871cbad7c72ae5a4d11a0ebaa..515445c2c0bb1fde39189e92f20f5410c3c6260a 100644 --- a/doc/api/graphql/reference/index.md +++ b/doc/api/graphql/reference/index.md @@ -11258,6 +11258,29 @@ The edge type for [`PackageTag`](#packagetag). | `cursor` | [`String!`](#string) | A cursor for use in pagination. | | `node` | [`PackageTag`](#packagetag) | The item at the end of the edge. | +#### `PackagesProtectionRuleConnection` + +The connection type for [`PackagesProtectionRule`](#packagesprotectionrule). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `edges` | [`[PackagesProtectionRuleEdge]`](#packagesprotectionruleedge) | A list of edges. | +| `nodes` | [`[PackagesProtectionRule]`](#packagesprotectionrule) | A list of nodes. | +| `pageInfo` | [`PageInfo!`](#pageinfo) | Information to aid in pagination. | + +#### `PackagesProtectionRuleEdge` + +The edge type for [`PackagesProtectionRule`](#packagesprotectionrule). + +##### Fields + +| Name | Type | Description | +| ---- | ---- | ----------- | +| `cursor` | [`String!`](#string) | A cursor for use in pagination. | +| `node` | [`PackagesProtectionRule`](#packagesprotectionrule) | The item at the end of the edge. | + #### `PagesDeploymentRegistryConnection` The connection type for [`PagesDeploymentRegistry`](#pagesdeploymentregistry). @@ -22346,6 +22369,7 @@ Represents vulnerability finding of a security report on the pipeline. | `onlyAllowMergeIfPipelineSucceeds` | [`Boolean`](#boolean) | Indicates if merge requests of the project can only be merged with successful jobs. | | `openIssuesCount` | [`Int`](#int) | Number of open issues for the project. | | `packagesCleanupPolicy` | [`PackagesCleanupPolicy`](#packagescleanuppolicy) | Packages cleanup policy for the project. | +| `packagesProtectionRules` | [`PackagesProtectionRuleConnection`](#packagesprotectionruleconnection) | Packages protection rules for the project. (see [Connections](#connections)) | | `path` | [`String!`](#string) | Path of the project. | | `pathLocks` | [`PathLockConnection`](#pathlockconnection) | The project's path locks. (see [Connections](#connections)) | | `pipelineAnalytics` | [`PipelineAnalytics`](#pipelineanalytics) | Pipeline analytics. | diff --git a/spec/requests/api/graphql/project/packages_protection_rules_spec.rb b/spec/requests/api/graphql/project/packages_protection_rules_spec.rb new file mode 100644 index 0000000000000000000000000000000000000000..0159f8a13e6546705891c0a33ff15c3d9ee6a694 --- /dev/null +++ b/spec/requests/api/graphql/project/packages_protection_rules_spec.rb @@ -0,0 +1,84 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe 'getting the packages protection rules linked to a project', :aggregate_failures, feature_category: :package_registry do + include GraphqlHelpers + + let_it_be_with_reload(:project) { create(:project) } + let_it_be(:user) { project.owner } + + let(:query) do + graphql_query_for( + :project, + { full_path: project.full_path }, + query_nodes(:packagesProtectionRules, of: 'PackagesProtectionRule') + ) + end + + subject { post_graphql(query, current_user: user) } + + context 'with authorized user owner' do + before do + subject + end + + context 'with package protection rule' do + let_it_be(:package_protection_rule) { create(:package_protection_rule, project: project) } + + it_behaves_like 'a working graphql query' + + it 'returns only on PackagesProtectionRule' do + expect(graphql_data_at(:project, :packagesProtectionRules, :nodes).count).to eq 1 + end + + it 'returns all packages protection rule fields' do + expect(graphql_data_at(:project, :packagesProtectionRules, :nodes)).to include( + hash_including( + 'packageNamePattern' => package_protection_rule.package_name_pattern, + 'packageType' => 'NPM', + 'pushProtectedUpToAccessLevel' => 'DEVELOPER' + ) + ) + end + end + + context 'without package protection rule' do + it_behaves_like 'a working graphql query' + + it 'returns no PackagesProtectionRule' do + expect(graphql_data_at(:project, :packagesProtectionRules, :nodes)).to eq [] + end + end + end + + context 'with unauthorized user' do + let_it_be(:user) { create(:user).tap { |u| project.add_developer(u) } } + + before do + subject + end + + it_behaves_like 'a working graphql query' + + it 'returns no package protection rules' do + expect(graphql_data_at(:project, :packagesProtectionRules, :nodes)).to eq [] + end + end + + context "when feature flag ':packages_protected_packages' disabled" do + let_it_be(:package_protection_rule) { create(:package_protection_rule, project: project) } + + before do + stub_feature_flags(packages_protected_packages: false) + + subject + end + + it_behaves_like 'a working graphql query' + + it 'returns no package protection rules' do + expect(graphql_data_at(:project, :packagesProtectionRules, :nodes)).to eq [] + end + end +end