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