diff --git a/CHANGELOG.md b/CHANGELOG.md
index 77e7f8813e08341c74e7f113c9ff461691fd5f39..970c800f71565f3f37d19b63db1fa3766a926b30 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,6 +2,25 @@
 documentation](doc/development/changelog.md) for instructions on adding your own
 entry.
 
+## 16.4.3 (2023-11-30)
+
+### Fixed (1 change)
+
+- [Fix assign security check permission checks](gitlab-org/security/gitlab@68b0fe3e41199a47e5851f3f00412ba18cc61a27) **GitLab Enterprise Edition**
+
+### Security (10 changes)
+
+- [Enforce ref protection on pipeline schedule updates](gitlab-org/security/gitlab@222b8d02d95e6c33ef26bfbb69718fa73daf31bc) ([merge request](gitlab-org/security/gitlab!3657))
+- [Update mermaid version for DOS security fixes](gitlab-org/security/gitlab@91f6263eb4697e9aebe059aee46ccfe1974d481c) ([merge request](gitlab-org/security/gitlab!3672))
+- [Prevent guest users from being able to add emojis in confidential issues](gitlab-org/security/gitlab@cc233c603bc595ef60f1b7ea2fcd69ab6113a374) ([merge request](gitlab-org/security/gitlab!3689))
+- [Do not run ssl cert validation if key has errors](gitlab-org/security/gitlab@ce234f97638d9182c22636301eccae87e7af854a) ([merge request](gitlab-org/security/gitlab!3662))
+- [Ensure access is checked when loading releases associated with tags](gitlab-org/security/gitlab@fead41322a5cf79513b5e3375fb2372ca936ef10) ([merge request](gitlab-org/security/gitlab!3696))
+- [XSS and ReDoS in Markdown via Banzai pipeline of Jira](gitlab-org/security/gitlab@7d9d64aa7123287c495b6be291a9b00dc60f179e) ([merge request](gitlab-org/security/gitlab!3692))
+- [Prevent branch names starting with SHA-1 and SHA-256 values](gitlab-org/security/gitlab@f51d428a6961bf77661cffffd50face4d02c6f43) ([merge request](gitlab-org/security/gitlab!3688))
+- [Filter out projects with disabled package registry in Composer finder](gitlab-org/security/gitlab@844ddc2028fd7389beee440034a1e83a42693ba2) ([merge request](gitlab-org/security/gitlab!3683))
+- [Check max role for user for group access to protected ref](gitlab-org/security/gitlab@1f6036ab1e227d013c0d42210a9c08ac7ff231c6) ([merge request](gitlab-org/security/gitlab!3643))
+- [Treat security policy bots as external](gitlab-org/security/gitlab@b0cf61131f21381978509ab2698b9da57522e726) ([merge request](gitlab-org/security/gitlab!3677))
+
 ## 16.4.2 (2023-10-30)
 
 ### Fixed (4 changes)
diff --git a/GITALY_SERVER_VERSION b/GITALY_SERVER_VERSION
index 189aba114d84ddbf52bd52abbbeee4534348c634..11701a599de58403ec6aacca12a661d655f295ea 100644
--- a/GITALY_SERVER_VERSION
+++ b/GITALY_SERVER_VERSION
@@ -1 +1 @@
-16.4.2
\ No newline at end of file
+16.4.3
\ No newline at end of file
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION
index 189aba114d84ddbf52bd52abbbeee4534348c634..11701a599de58403ec6aacca12a661d655f295ea 100644
--- a/GITLAB_PAGES_VERSION
+++ b/GITLAB_PAGES_VERSION
@@ -1 +1 @@
-16.4.2
\ No newline at end of file
+16.4.3
\ No newline at end of file
diff --git a/VERSION b/VERSION
index 9e59143863b4689651a7048d09dba3b6df5eff88..352dd3e1b6c612df0554d6e60524146e7cc2a4db 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-16.4.2-ee
\ No newline at end of file
+16.4.3-ee
\ No newline at end of file
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb
index 3c1735c728c9675b6c976a178359d0afe1128606..d3e38774aaa55a46a60f6d571071146fa2a13dc1 100644
--- a/app/controllers/projects/tags_controller.rb
+++ b/app/controllers/projects/tags_controller.rb
@@ -29,7 +29,7 @@ def index
       tag_names = @tags.map(&:name)
       @tags_pipelines = @project.ci_pipelines.latest_successful_for_refs(tag_names)
 
-      @releases = project.releases.where(tag: tag_names)
+      @releases = ReleasesFinder.new(project, current_user, tag: tag_names).execute
       @tag_pipeline_statuses = Ci::CommitStatusesFinder.new(@project, @repository, current_user, @tags).execute
 
     rescue Gitlab::Git::CommandError => e
diff --git a/app/finders/packages/composer/packages_finder.rb b/app/finders/packages/composer/packages_finder.rb
index b5a1b19216ffbc6953d107b599bf65c617b95fc5..1581c48dd747baedf1d10c9710f1954ec055a29e 100644
--- a/app/finders/packages/composer/packages_finder.rb
+++ b/app/finders/packages/composer/packages_finder.rb
@@ -2,14 +2,12 @@
 module Packages
   module Composer
     class PackagesFinder < Packages::GroupPackagesFinder
-      def initialize(current_user, group, params = {})
-        @current_user = current_user
-        @group = group
-        @params = params
+      def initialize(current_user, group, params = { package_type: :composer, with_package_registry_enabled: true })
+        super(current_user, group, params)
       end
 
       def execute
-        packages_for_group_projects(installable_only: true).composer.preload_composer
+        packages_for_group_projects(installable_only: true).preload_composer
       end
     end
   end
diff --git a/app/finders/packages/group_packages_finder.rb b/app/finders/packages/group_packages_finder.rb
index 3a068252d5c949b81108f1c94a3a3bd0c8386c3c..3b211882fa0d6ad5c893e99cecba45416bd48231 100644
--- a/app/finders/packages/group_packages_finder.rb
+++ b/app/finders/packages/group_packages_finder.rb
@@ -40,14 +40,17 @@ def group_projects_visible_to_current_user
       # access to packages is ruled by:
       # - project is public or the current user has access to it with at least the reporter level
       # - the repository feature is available to the current_user
-      if current_user.is_a?(DeployToken)
-        current_user.accessible_projects
-      else
-        ::Project
-          .in_namespace(groups)
-          .public_or_visible_to_user(current_user, Gitlab::Access::REPORTER)
-          .with_feature_available_for_user(:repository, current_user)
-      end
+      projects = if current_user.is_a?(DeployToken)
+                   current_user.accessible_projects
+                 else
+                   ::Project
+                     .in_namespace(groups)
+                     .public_or_visible_to_user(current_user, Gitlab::Access::REPORTER)
+                     .with_feature_available_for_user(:repository, current_user)
+                 end
+
+      projects = projects.with_package_registry_enabled if params[:with_package_registry_enabled]
+      projects
     end
 
     def groups
diff --git a/app/graphql/types/permission_types/base_permission_type.rb b/app/graphql/types/permission_types/base_permission_type.rb
index d45c61f489b62ea321efbc75225eca9f8f78404d..ae9de845593ba4b47877fca5b294055f3aeb082c 100644
--- a/app/graphql/types/permission_types/base_permission_type.rb
+++ b/app/graphql/types/permission_types/base_permission_type.rb
@@ -30,7 +30,7 @@ def self.permission_field(name, **kword_args, &block)
       def self.define_field_resolver_method(ability)
         unless respond_to?(ability)
           define_method ability.to_sym do |*args|
-            Ability.allowed?(context[:current_user], ability, object, args.to_h)
+            Ability.allowed?(context[:current_user], ability, object, **args.to_h)
           end
         end
       end
diff --git a/app/models/ability.rb b/app/models/ability.rb
index d8510524c1f5e8b1afecb271f9bffb5025677916..d9a875bf93e5621be8bc24ee16299342181a9221 100644
--- a/app/models/ability.rb
+++ b/app/models/ability.rb
@@ -70,13 +70,13 @@ def feature_flags_readable_by_user(feature_flags, user = nil, filters: {})
       end
     end
 
-    def allowed?(user, ability, subject = :global, opts = {})
+    def allowed?(user, ability, subject = :global, **opts)
       if subject.is_a?(Hash)
         opts = subject
         subject = :global
       end
 
-      policy = policy_for(user, subject)
+      policy = policy_for(user, subject, **opts.slice(:cache))
 
       before_check(policy, ability.to_sym, user, subject, opts)
 
@@ -100,8 +100,14 @@ def before_check(policy, ability, user, subject, opts)
       # See Support::AbilityCheck and Support::PermissionsCheck.
     end
 
-    def policy_for(user, subject = :global)
-      DeclarativePolicy.policy_for(user, subject, cache: ::Gitlab::SafeRequestStore.storage)
+    # We cache in the request store by default. This can lead to unexpected
+    # results if abilities are re-checked after objects are modified and the
+    # check depends on the modified attributes. In such cases, you should pass
+    # `cache: false` for the second check to ensure all rules get re-evaluated.
+    def policy_for(user, subject = :global, cache: true)
+      policy_cache = cache ? ::Gitlab::SafeRequestStore.storage : {}
+
+      DeclarativePolicy.policy_for(user, subject, cache: policy_cache)
     end
 
     # This method is something of a band-aid over the problem. The problem is
diff --git a/app/models/concerns/protected_branch_access.rb b/app/models/concerns/protected_branch_access.rb
index 8156090fd9cef84984b80e1f830be0b901219d70..6a7fdce62fb2225a94d0171f7ee9db8212382e0f 100644
--- a/app/models/concerns/protected_branch_access.rb
+++ b/app/models/concerns/protected_branch_access.rb
@@ -10,3 +10,5 @@ module ProtectedBranchAccess
     delegate :project, to: :protected_branch
   end
 end
+
+ProtectedBranchAccess.prepend_mod_with('ProtectedBranchAccess')
diff --git a/app/models/integrations/jira.rb b/app/models/integrations/jira.rb
index d8d1f860e9a2db7a3abe3ed1e6ab14464490638e..4255d0bbcabebd6d7e8deff53c406e5b3410c81d 100644
--- a/app/models/integrations/jira.rb
+++ b/app/models/integrations/jira.rb
@@ -384,9 +384,9 @@ def personal_access_token_authorization?
     private
 
     def jira_issue_match_regex
-      return /\b#{jira_issue_prefix}(?<issue>#{Gitlab::Regex.jira_issue_key_regex})/ if jira_issue_regex.blank?
+      jira_regex = jira_issue_regex.presence || Gitlab::Regex.jira_issue_key_regex.source
 
-      Gitlab::UntrustedRegexp.new("\\b#{jira_issue_prefix}(?P<issue>#{jira_issue_regex})")
+      Gitlab::UntrustedRegexp.new("\\b#{jira_issue_prefix}(?P<issue>#{jira_regex})")
     end
 
     def parse_project_from_issue_key(issue_key)
diff --git a/app/models/pages_domain.rb b/app/models/pages_domain.rb
index b86bc761cc1962bd39ec5045e011a032e699b94f..08326cf39e6ea3a7ddc1b26a6eeb867bb024b1f1 100644
--- a/app/models/pages_domain.rb
+++ b/app/models/pages_domain.rb
@@ -33,10 +33,11 @@ class PagesDomain < ApplicationRecord
   validates :verification_code, presence: true, allow_blank: false
 
   validate :validate_pages_domain
+  validate :max_certificate_key_length, if: ->(domain) { domain.key.present? }
   validate :validate_matching_key, if: ->(domain) { domain.certificate.present? || domain.key.present? }
-  validate :validate_intermediates, if: ->(domain) { domain.certificate.present? && domain.certificate_changed? }
+  # validate_intermediates must run after key validations to skip expensive SSL validation when there is a key error
+  validate :validate_intermediates, if: ->(domain) { domain.certificate.present? && domain.certificate_changed? && errors[:key].blank? }
   validate :validate_custom_domain_count_per_project, on: :create
-  validate :max_certificate_key_length, if: ->(domain) { domain.key.present? }
 
   attribute :auto_ssl_enabled, default: -> { ::Gitlab::LetsEncrypt.enabled? }
   attribute :wildcard, default: false
diff --git a/app/models/project.rb b/app/models/project.rb
index 5989584ce43a07a4783be6cd39b38729593a8b35..96a23c4b03d5996f7d9572a09e05dfe8fb6c24bd 100644
--- a/app/models/project.rb
+++ b/app/models/project.rb
@@ -749,6 +749,7 @@ def self.integration_association_name(name)
   scope :service_desk_enabled, -> { where(service_desk_enabled: true) }
   scope :with_builds_enabled, -> { with_feature_enabled(:builds) }
   scope :with_issues_enabled, -> { with_feature_enabled(:issues) }
+  scope :with_package_registry_enabled, -> { with_feature_enabled(:package_registry) }
   scope :with_issues_available_for_user, ->(current_user) { with_feature_available_for_user(:issues, current_user) }
   scope :with_merge_requests_available_for_user, ->(current_user) { with_feature_available_for_user(:merge_requests, current_user) }
   scope :with_issues_or_mrs_available_for_user, -> (user) do
diff --git a/app/models/user.rb b/app/models/user.rb
index c4e867ab5716cbbc5bc84e51946715f9a67677cc..b1349cd3f650c4c044721e8c01754fe9f3f6a451 100644
--- a/app/models/user.rb
+++ b/app/models/user.rb
@@ -1297,8 +1297,8 @@ def can_select_namespace?
     several_namespaces? || admin
   end
 
-  def can?(action, subject = :global)
-    Ability.allowed?(self, action, subject)
+  def can?(action, subject = :global, **opts)
+    Ability.allowed?(self, action, subject, **opts)
   end
 
   def confirm_deletion_with_password?
diff --git a/app/policies/ci/pipeline_schedule_policy.rb b/app/policies/ci/pipeline_schedule_policy.rb
index cbc60c4a30a0a8c8eba3b356840a83c11ca1643a..9e558cd91c19c427cf3b326e48420b5fac9156c7 100644
--- a/app/policies/ci/pipeline_schedule_policy.rb
+++ b/app/policies/ci/pipeline_schedule_policy.rb
@@ -25,7 +25,7 @@ class PipelineSchedulePolicy < PipelinePolicy
 
     rule { can?(:create_pipeline) }.enable :play_pipeline_schedule
 
-    rule { can?(:admin_pipeline) | (can?(:update_build) & owner_of_schedule) }.policy do
+    rule { can?(:admin_pipeline) | (owner_of_schedule & can?(:update_build)) }.policy do
       enable :admin_pipeline_schedule
       enable :read_pipeline_schedule_variables
     end
@@ -45,6 +45,7 @@ class PipelineSchedulePolicy < PipelinePolicy
     rule { protected_ref }.policy do
       prevent :play_pipeline_schedule
       prevent :create_pipeline_schedule
+      prevent :update_pipeline_schedule
     end
 
     private
diff --git a/app/policies/issue_policy.rb b/app/policies/issue_policy.rb
index 538959c92bd7b92ef7d62cf6c7ecac9fc92bda82..f642c6eab2d812168e98d0e903a833ec00586fff 100644
--- a/app/policies/issue_policy.rb
+++ b/app/policies/issue_policy.rb
@@ -51,7 +51,10 @@ class IssuePolicy < IssuablePolicy
     prevent :read_issue
   end
 
-  rule { ~can?(:read_issue) }.prevent :create_note
+  rule { ~can?(:read_issue) }.policy do
+    prevent :create_note
+    prevent :award_emoji
+  end
 
   rule { locked }.policy do
     prevent :reopen_issue
diff --git a/app/services/ci/pipeline_schedules/base_save_service.rb b/app/services/ci/pipeline_schedules/base_save_service.rb
index 45d70e5a65d9c422843cde4d9ae1c399b9faa82f..e6f633498e9b256518519a69cfe5410ebdb0acf4 100644
--- a/app/services/ci/pipeline_schedules/base_save_service.rb
+++ b/app/services/ci/pipeline_schedules/base_save_service.rb
@@ -23,7 +23,11 @@ def execute
       attr_reader :project, :user, :params, :schedule
 
       def allowed_to_save?
-        user.can?(self.class::AUTHORIZE, schedule)
+        # Disable cache because the same ability may already have been checked
+        # for the same records with different attributes. For example, we do not
+        # want an unauthorized user to change an unprotected ref to a protected
+        # ref.
+        user.can?(self.class::AUTHORIZE, schedule, cache: false)
       end
 
       def forbidden_to_save
diff --git a/app/services/ci/pipeline_schedules/update_service.rb b/app/services/ci/pipeline_schedules/update_service.rb
index 2fd1173ecce3fa208a60bff3190f9c9bb8c46f18..76b2121c4e11d809f10672e2d11d01e05fdc68d1 100644
--- a/app/services/ci/pipeline_schedules/update_service.rb
+++ b/app/services/ci/pipeline_schedules/update_service.rb
@@ -12,6 +12,12 @@ def initialize(schedule, user, params)
         @params = params
       end
 
+      def execute
+        return forbidden_to_save unless allowed_to_save?
+
+        super
+      end
+
       private
 
       def authorize_message
diff --git a/ee/app/models/concerns/ee/protected_branch_access.rb b/ee/app/models/concerns/ee/protected_branch_access.rb
new file mode 100644
index 0000000000000000000000000000000000000000..4b520f65e44f4753db93ffe6695799f2b267b21a
--- /dev/null
+++ b/ee/app/models/concerns/ee/protected_branch_access.rb
@@ -0,0 +1,29 @@
+# frozen_string_literal: true
+
+module EE
+  module ProtectedBranchAccess
+    extend ActiveSupport::Concern
+    extend ::Gitlab::Utils::Override
+
+    private
+
+    def group_access_allowed?(current_user)
+      # For protected branches, only groups that are invited to the project
+      # can granted push and merge access. This feature does not work groups
+      # that are ancestors of the project.
+      # See https://gitlab.com/gitlab-org/gitlab/-/issues/427486.
+      # Hence, we only consider the role of the user in the group limited by
+      # the max role of the project_group_link.
+      #
+      # We do not use the access level provided by direct membership to the project
+      # or inherited through ancestor groups of the project.
+      # See https://gitlab.com/gitlab-org/gitlab/-/issues/423835
+
+      project_group_link = project.project_group_links.find_by(group: group)
+      return false unless project_group_link.present?
+      return false if project_group_link.group_access < ::Gitlab::Access::DEVELOPER
+
+      group.member?(current_user, ::Gitlab::Access::DEVELOPER)
+    end
+  end
+end
diff --git a/ee/app/models/concerns/ee/protected_ref_access.rb b/ee/app/models/concerns/ee/protected_ref_access.rb
index e155ae65b5ce3f0b22715a5aebc5b27b371f30bf..021a127995d95919a9a471dc110845b0de6171b0 100644
--- a/ee/app/models/concerns/ee/protected_ref_access.rb
+++ b/ee/app/models/concerns/ee/protected_ref_access.rb
@@ -62,7 +62,7 @@ def humanize
     def check_access(current_user)
       super do
         break current_user.id == user_id if user?
-        break group.users.exists?(current_user.id) if group?
+        break group_access_allowed?(current_user) if group?
 
         yield if block_given?
       end
@@ -70,6 +70,10 @@ def check_access(current_user)
 
     private
 
+    def group_access_allowed?(current_user)
+      group.users.exists?(current_user.id)
+    end
+
     def user?
       type == :user
     end
diff --git a/ee/app/models/ee/user.rb b/ee/app/models/ee/user.rb
index 3e9abdbfabdfa7ac8a1687c572ff7940442bb294..65ebdefb30d4221c843f1a8cdf8b21596976df73 100644
--- a/ee/app/models/ee/user.rb
+++ b/ee/app/models/ee/user.rb
@@ -231,6 +231,12 @@ def use_separate_indices?
       end
     end
 
+    def external?
+      return true if security_policy_bot?
+
+      read_attribute(:external)
+    end
+
     def cannot_be_admin_and_auditor
       if admin? && auditor?
         errors.add(:admin, 'user cannot also be an Auditor.')
diff --git a/ee/app/models/protected_branch/unprotect_access_level.rb b/ee/app/models/protected_branch/unprotect_access_level.rb
index 27a8b1534703eb42af4c282a93b0bbac5cf6e9d3..b5f59dfbd6441ae8ab4e2a9d973e5a9f9f17b1fd 100644
--- a/ee/app/models/protected_branch/unprotect_access_level.rb
+++ b/ee/app/models/protected_branch/unprotect_access_level.rb
@@ -28,5 +28,12 @@ class ProtectedBranch::UnprotectAccessLevel < ApplicationRecord
   def self.allowed_access_levels
     super.excluding(Gitlab::Access::NO_ACCESS)
   end
-  include ProtectedBranchAccess
+  include ProtectedRefAccess
+
+  belongs_to :protected_branch
+  delegate :project, to: :protected_branch
+
+  def self.declarative_policy_class
+    'ProtectedBranchPolicy'
+  end
 end
diff --git a/ee/app/services/security/orchestration/create_bot_service.rb b/ee/app/services/security/orchestration/create_bot_service.rb
index 56949428cda39196c9504f2caef0ddd44535b5d7..949866a5584b8c67af9a2c1bdc85e39f0ca05c6b 100644
--- a/ee/app/services/security/orchestration/create_bot_service.rb
+++ b/ee/app/services/security/orchestration/create_bot_service.rb
@@ -37,7 +37,8 @@ def bot_user_params
           email: username_and_email_generator.email,
           username: username_and_email_generator.username,
           user_type: :security_policy_bot,
-          skip_confirmation: true # Bot users should always have their emails confirmed.
+          skip_confirmation: true, # Bot users should always have their emails confirmed.
+          external: true
         }
       end
 
diff --git a/ee/spec/models/ee/user_spec.rb b/ee/spec/models/ee/user_spec.rb
index da0f7c7ac4b3632515c08dbf66300454b2dd97eb..b5ce65c1f878e353abacb1afce0b58944cd8adc2 100644
--- a/ee/spec/models/ee/user_spec.rb
+++ b/ee/spec/models/ee/user_spec.rb
@@ -3599,4 +3599,26 @@
       end
     end
   end
+
+  describe '#external?' do
+    subject { user.external? }
+
+    context 'when external is true' do
+      let(:user) { build(:user, external: true) }
+
+      it { is_expected.to eq(true) }
+    end
+
+    context 'when external is false' do
+      let(:user) { build(:user, external: false) }
+
+      it { is_expected.to eq(false) }
+    end
+
+    context 'when user is security_policy_bot' do
+      let(:user) { build(:user, :security_policy_bot, external: false) }
+
+      it { is_expected.to eq(true) }
+    end
+  end
 end
diff --git a/ee/spec/models/protected_branch/merge_access_level_spec.rb b/ee/spec/models/protected_branch/merge_access_level_spec.rb
index b33f70c50bcae70b85a4b1c2265c3a119024b56e..bdd809c3459d4baf799a10dcba996ae31b1702e0 100644
--- a/ee/spec/models/protected_branch/merge_access_level_spec.rb
+++ b/ee/spec/models/protected_branch/merge_access_level_spec.rb
@@ -3,5 +3,6 @@
 require 'spec_helper'
 
 RSpec.describe ProtectedBranch::MergeAccessLevel, feature_category: :source_code_management do
-  include_examples 'ee protected ref access', :protected_branch
+  include_examples 'protected ref access configured for users', :protected_branch
+  include_examples 'ee protected branch access'
 end
diff --git a/ee/spec/models/protected_branch/push_access_level_spec.rb b/ee/spec/models/protected_branch/push_access_level_spec.rb
index ec5cbc9bc702442c1b3698fe6e7feeec1f0b0469..e37bde5c28872813c716f5aad2cc23ff484e48d9 100644
--- a/ee/spec/models/protected_branch/push_access_level_spec.rb
+++ b/ee/spec/models/protected_branch/push_access_level_spec.rb
@@ -3,5 +3,6 @@
 require 'spec_helper'
 
 RSpec.describe ProtectedBranch::PushAccessLevel, feature_category: :source_code_management do
-  include_examples 'ee protected ref access', :protected_branch
+  include_examples 'protected ref access configured for users', :protected_branch
+  include_examples 'ee protected branch access'
 end
diff --git a/ee/spec/models/protected_branch/unprotect_access_level_spec.rb b/ee/spec/models/protected_branch/unprotect_access_level_spec.rb
index fec0224ef39dac7b53bd9a5a541bb6f477b7eb0b..4d367eea7a0e136d8ca6fbafd83c0fd9af92431a 100644
--- a/ee/spec/models/protected_branch/unprotect_access_level_spec.rb
+++ b/ee/spec/models/protected_branch/unprotect_access_level_spec.rb
@@ -5,5 +5,6 @@
 RSpec.describe ProtectedBranch::UnprotectAccessLevel, feature_category: :source_code_management do
   include_examples 'protected branch access'
   include_examples 'protected ref access allowed_access_levels', excludes: [Gitlab::Access::NO_ACCESS]
-  include_examples 'ee protected ref access', :protected_branch
+  include_examples 'protected ref access configured for users', :protected_branch
+  include_examples 'protected ref access configured for groups', :protected_branch
 end
diff --git a/ee/spec/models/protected_tag/create_access_level_spec.rb b/ee/spec/models/protected_tag/create_access_level_spec.rb
index b71242084bad1479f26028f4c447eaadc25ca806..4c71e5182eb5946ce652034aed7c0aec1fc0cf34 100644
--- a/ee/spec/models/protected_tag/create_access_level_spec.rb
+++ b/ee/spec/models/protected_tag/create_access_level_spec.rb
@@ -3,5 +3,6 @@
 require 'spec_helper'
 
 RSpec.describe ProtectedTag::CreateAccessLevel, feature_category: :source_code_management do
-  include_examples 'ee protected ref access', :protected_tag
+  include_examples 'protected ref access configured for users', :protected_tag
+  include_examples 'protected ref access configured for groups', :protected_tag
 end
diff --git a/ee/spec/services/security/orchestration/create_bot_service_spec.rb b/ee/spec/services/security/orchestration/create_bot_service_spec.rb
index 3f6cd9e2dec664788ed356a5381292f0b571218c..b33265f0f40363d1ddbf91ae8d49656ff1e26633 100644
--- a/ee/spec/services/security/orchestration/create_bot_service_spec.rb
+++ b/ee/spec/services/security/orchestration/create_bot_service_spec.rb
@@ -35,6 +35,7 @@
       expect(bot_user.username).to start_with("gitlab_security_policy_project_#{project.id}_bot")
       expect(bot_user.email).to start_with("gitlab_security_policy_project_#{project.id}_bot")
       expect(bot_user.user_type).to eq('security_policy_bot')
+      expect(bot_user.external).to eq(true)
     end
 
     it 'adds the bot user as a guest to the project', :aggregate_failures do
diff --git a/ee/spec/support/shared_examples/models/concerns/protected_branch_access_shared_examples.rb b/ee/spec/support/shared_examples/models/concerns/protected_branch_access_shared_examples.rb
new file mode 100644
index 0000000000000000000000000000000000000000..096a25144e033cdb8db3738efd87a62342abb493
--- /dev/null
+++ b/ee/spec/support/shared_examples/models/concerns/protected_branch_access_shared_examples.rb
@@ -0,0 +1,148 @@
+# frozen_string_literal: true
+
+RSpec.shared_examples 'ee protected branch access' do
+  describe '#check_access' do
+    let_it_be(:user_project) { create(:project, :empty_repo) }
+    let(:protected_ref) { create(:protected_branch, project: project) }
+    let_it_be(:current_user) { create(:user) }
+    let(:project) { user_project }
+
+    let(:access_level) { nil }
+    let(:user) { nil }
+    let(:group) { create(:group) }
+
+    before_all do
+      user_project.add_maintainer(current_user)
+    end
+
+    subject do
+      described_class.new(
+        protected_branch: protected_ref,
+        user: user,
+        group: group,
+        access_level: access_level
+      )
+    end
+
+    context 'when the group is not invited' do
+      it { expect(subject.check_access(current_user)).to eq(false) }
+
+      context 'when group has no access to project' do
+        context 'and the user is a developer in the group ' do
+          before do
+            group.add_developer(current_user)
+          end
+
+          it { expect(subject.check_access(current_user)).to eq(false) }
+        end
+      end
+    end
+
+    context 'when group is invited' do
+      let!(:project_group_link) do
+        create :project_group_link, invited_group_access_level, project: project, group: group
+      end
+
+      context 'and the group has max role less than developer' do
+        let(:invited_group_access_level) { :reporter }
+
+        context 'and the user is a developer in the group ' do
+          before do
+            group.add_developer(current_user)
+          end
+
+          it { expect(subject.check_access(current_user)).to eq(false) }
+        end
+      end
+
+      context 'and the group has max role of at least developer' do
+        let(:invited_group_access_level) { :developer }
+
+        context 'when current_user is a developer the group' do
+          before do
+            group.add_developer(current_user)
+          end
+
+          it { expect(subject.check_access(current_user)).to eq(true) }
+        end
+
+        context 'when current_user is a guest in the group' do
+          before do
+            group.add_guest(current_user)
+          end
+
+          it { expect(subject.check_access(current_user)).to eq(false) }
+        end
+
+        context 'when current_user is not in the group' do
+          it { expect(subject.check_access(current_user)).to eq(false) }
+        end
+
+        context 'when current_user is a member of another group that has access to group' do
+          using RSpec::Parameterized::TableSyntax
+          let(:group_group_link) do
+            create(:group_group_link, other_group_access_level, shared_group: project_group_link.group)
+          end
+
+          let(:other_group) { group_group_link.shared_with_group }
+
+          context 'when current user has develop access to the other group' do
+            where(:invited_group_access_level, :other_group_access_level, :expected_access) do
+              :developer | :developer | true
+              :developer | :guest     | false
+              :guest     | :guest     | false
+              :guest     | :developer | false
+            end
+
+            before do
+              other_group.add_developer(current_user)
+            end
+
+            with_them do
+              it { expect(subject.check_access(current_user)).to eq(expected_access) }
+            end
+          end
+
+          context 'when current user has guest access to the other group' do
+            where(:invited_group_access_level, :other_group_access_level, :expected_access) do
+              :developer | :developer | false
+              :developer | :guest     | false
+              :guest     | :guest     | false
+              :guest     | :developer | false
+            end
+
+            before do
+              other_group.add_guest(current_user)
+            end
+
+            with_them do
+              it { expect(subject.check_access(current_user)).to eq(expected_access) }
+            end
+          end
+        end
+      end
+
+      context 'when group is a subgroup' do
+        let(:subgroup) { create(:group, :nested) }
+        let(:parent_group) { subgroup.parent }
+        let(:parent_group_developer) { create(:user) }
+        let(:parent_group_guest) { create(:user) }
+        let(:invited_group_access_level) { :developer }
+        let(:group) { subgroup }
+
+        before do
+          parent_group.add_developer(parent_group_developer)
+          parent_group.add_guest(parent_group_guest)
+        end
+
+        context 'when user is a developer of the parent group' do
+          it { expect(subject.check_access(parent_group_developer)).to eq(true) }
+        end
+
+        context 'when user is a guest of the parent group' do
+          it { expect(subject.check_access(parent_group_guest)).to eq(false) }
+        end
+      end
+    end
+  end
+end
diff --git a/ee/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb b/ee/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb
index 505717422d76ee655c997fc402a50575241c8bcb..e784d722d6edb02a3d62350f47cf5fd21bfaa837 100644
--- a/ee/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb
+++ b/ee/spec/support/shared_examples/models/concerns/protected_ref_access_examples.rb
@@ -1,8 +1,8 @@
 # frozen_string_literal: true
 
-RSpec.shared_examples 'ee protected ref access' do |association|
+RSpec.shared_examples 'protected ref access configured for users' do |association|
   let_it_be(:project) { create(:project) }
-  let_it_be(:protected_ref) { create(association, project: project) } # rubocop:disable Rails/SaveBang
+  let_it_be(:protected_ref) { create(association, project: project) }
 
   describe '#check_access' do
     let_it_be(:current_user) { create(:user) }
@@ -37,6 +37,32 @@
         it { expect(subject.check_access(current_user)).to eq(false) }
       end
     end
+  end
+end
+
+RSpec.shared_examples 'protected ref access configured for groups' do |association|
+  let_it_be(:project) { create(:project) }
+  let_it_be(:protected_ref) { create(association, project: project) }
+
+  describe '#check_access' do
+    let_it_be(:current_user) { create(:user) }
+
+    let(:access_level) { nil }
+    let(:user) { nil }
+    let(:group) { nil }
+
+    before_all do
+      project.add_maintainer(current_user)
+    end
+
+    subject do
+      described_class.new(
+        association => protected_ref,
+        user: user,
+        group: group,
+        access_level: access_level
+      )
+    end
 
     context 'when group is assigned' do
       let(:group) { create(:group) }
diff --git a/lib/gitlab/checks/branch_check.rb b/lib/gitlab/checks/branch_check.rb
index b675eca826a1a51aac42c5ceaf8fc410c8f9aaff..3bedc483e7525158a9f98822dbaaeb5fac3f93ec 100644
--- a/lib/gitlab/checks/branch_check.rb
+++ b/lib/gitlab/checks/branch_check.rb
@@ -13,7 +13,7 @@ class BranchCheck < BaseSingleChecker
         create_protected_branch: 'You are not allowed to create protected branches on this project.',
         invalid_commit_create_protected_branch: 'You can only use an existing protected branch ref as the basis of a new protected branch.',
         non_web_create_protected_branch: 'You can only create protected branches using the web interface and API.',
-        prohibited_hex_branch_name: 'You cannot create a branch with a 40-character hexadecimal branch name.',
+        prohibited_hex_branch_name: 'You cannot create a branch with a SHA-1 or SHA-256 branch name.',
         invalid_branch_name: 'You cannot create a branch with an invalid name.'
       }.freeze
 
@@ -43,7 +43,7 @@ def validate!
       def prohibited_branch_checks
         return if deletion?
 
-        if %r{\A#{Gitlab::Git::Commit::RAW_FULL_SHA_PATTERN}(-/|/|\z)}o.match?(branch_name)
+        if %r{\A#{Gitlab::Git::Commit::RAW_FULL_SHA_PATTERN}}o.match?(branch_name)
           raise GitAccess::ForbiddenError, ERROR_MESSAGES[:prohibited_hex_branch_name]
         end
 
diff --git a/lib/gitlab/regex.rb b/lib/gitlab/regex.rb
index 8ef455efe0711858e5ef2d26758f27a13c7bde79..fd72376513f4beb93c850da6f65ac6fefb738250 100644
--- a/lib/gitlab/regex.rb
+++ b/lib/gitlab/regex.rb
@@ -255,10 +255,8 @@ def markdown_code_or_html_blocks_or_html_comments_untrusted
 
     # Based on Jira's project key format
     # https://confluence.atlassian.com/adminjiraserver073/changing-the-project-key-format-861253229.html
-    # Avoids linking CVE IDs (https://cve.mitre.org/cve/identifiers/syntaxchange.html#new) as Jira issues.
-    # CVE IDs use the format of CVE-YYYY-NNNNNNN
     def jira_issue_key_regex(expression_escape: '\b')
-      /#{expression_escape}(?!CVE-\d+-\d+)[A-Z][A-Z_0-9]+-\d+/
+      /#{expression_escape}([A-Z][A-Z_0-9]+-\d+)/
     end
 
     def jira_issue_key_project_key_extraction_regex
diff --git a/package.json b/package.json
index 0893f6a6ddee9effb4f933478112c39bd21d63bd..0133e2d8871015dff555fe774bac1394ca133a2f 100644
--- a/package.json
+++ b/package.json
@@ -167,7 +167,7 @@
     "marked-bidi": "^1.0.3",
     "mathjax": "3",
     "mdurl": "^1.0.1",
-    "mermaid": "10.5.0",
+    "mermaid": "10.6.1",
     "micromatch": "^4.0.5",
     "minimatch": "^3.0.4",
     "monaco-editor": "^0.30.1",
diff --git a/spec/controllers/projects/pipeline_schedules_controller_spec.rb b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
index cd828c956a0a1506c8ce2d810d5976c1d8293bca..7cd4f43d4da47c3a8a99b287ed6393a1a85095a9 100644
--- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb
+++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb
@@ -65,6 +65,10 @@
           create(:protected_branch, *branch_access_levels, name: ref_name, project: project)
         end
 
+        after do
+          ProtectedBranches::CacheService.new(project).refresh
+        end
+
         it { expect { go }.to try(maintainer_accessible, :maintainer).of(project) }
         it { expect { go }.to try(developer_accessible, :developer).of(project) }
       end
diff --git a/spec/controllers/projects/tags_controller_spec.rb b/spec/controllers/projects/tags_controller_spec.rb
index 3d1f8c120221f53a3ac2adb9b7cd22bea40b4c04..cab0778bd13b5c0febb6dac80bb5fd3030421e1c 100644
--- a/spec/controllers/projects/tags_controller_spec.rb
+++ b/spec/controllers/projects/tags_controller_spec.rb
@@ -52,6 +52,18 @@
       expect(assigns(:releases)).not_to include(invalid_release)
     end
 
+    context 'when releases are private' do
+      before do
+        project.project_feature.update!(releases_access_level: ProjectFeature::PRIVATE)
+      end
+
+      it 'does not contain release data' do
+        subject
+
+        expect(assigns(:releases)).to be_empty
+      end
+    end
+
     context '@tag_pipeline_status' do
       context 'when no pipelines exist' do
         it 'is empty' do
diff --git a/spec/features/projects/pages/user_adds_domain_spec.rb b/spec/features/projects/pages/user_adds_domain_spec.rb
index 14b01cb63d2a48bb9f9fc5ee8a962ee9150d2dcc..04a9f450b52d5d7941141100ace906ff3752dd0d 100644
--- a/spec/features/projects/pages/user_adds_domain_spec.rb
+++ b/spec/features/projects/pages/user_adds_domain_spec.rb
@@ -155,7 +155,6 @@
           click_button 'Save Changes'
 
           expect(page).to have_content('Certificate must be a valid PEM certificate')
-          expect(page).to have_content('Certificate misses intermediates')
           expect(page).to have_content("Key doesn't match the certificate")
         end
       end
diff --git a/spec/finders/packages/composer/packages_finder_spec.rb b/spec/finders/packages/composer/packages_finder_spec.rb
index d4328827de309795ba26c219ced3ce4db59b960f..1701243063bdc82299f6b7cb0b367176d8f6377d 100644
--- a/spec/finders/packages/composer/packages_finder_spec.rb
+++ b/spec/finders/packages/composer/packages_finder_spec.rb
@@ -1,18 +1,19 @@
 # frozen_string_literal: true
 require 'spec_helper'
 
-RSpec.describe ::Packages::Composer::PackagesFinder do
+RSpec.describe ::Packages::Composer::PackagesFinder, feature_category: :package_registry do
   let_it_be(:user) { create(:user) }
   let_it_be(:group) { create(:group) }
   let_it_be(:project) { create(:project, group: group) }
 
-  let(:params) { {} }
+  let(:params) { { package_type: :composer } }
 
   describe '#execute' do
     let_it_be(:composer_package) { create(:composer_package, project: project) }
     let_it_be(:composer_package2) { create(:composer_package, project: project) }
     let_it_be(:error_package) { create(:composer_package, :error, project: project) }
     let_it_be(:composer_package3) { create(:composer_package) }
+    let_it_be(:nuget_package) { create(:nuget_package, project: project) }
 
     subject { described_class.new(user, group, params).execute }
 
@@ -21,5 +22,15 @@
     end
 
     it { is_expected.to match_array([composer_package, composer_package2]) }
+
+    context 'when disabling the package registry for the project' do
+      let(:params) { super().merge(with_package_registry_enabled: true) }
+
+      before do
+        project.update!(package_registry_access_level: 'disabled', packages_enabled: false)
+      end
+
+      it { is_expected.to be_empty }
+    end
   end
 end
diff --git a/spec/finders/packages/group_packages_finder_spec.rb b/spec/finders/packages/group_packages_finder_spec.rb
index e4a944eb837153210930018a8713669f2db3e8f4..f78be857357beb541c807de23174906ea2916649 100644
--- a/spec/finders/packages/group_packages_finder_spec.rb
+++ b/spec/finders/packages/group_packages_finder_spec.rb
@@ -1,7 +1,7 @@
 # frozen_string_literal: true
 require 'spec_helper'
 
-RSpec.describe Packages::GroupPackagesFinder do
+RSpec.describe Packages::GroupPackagesFinder, feature_category: :package_registry do
   using RSpec::Parameterized::TableSyntax
 
   let_it_be(:user) { create(:user) }
@@ -25,6 +25,16 @@
       it { is_expected.to match_array([send("package_#{package_type}")]) }
     end
 
+    shared_examples 'disabling package registry for project' do
+      let(:params) { super().merge(with_package_registry_enabled: true) }
+
+      before do
+        project.update!(package_registry_access_level: 'disabled', packages_enabled: false)
+      end
+
+      it { is_expected.to match_array(packages_returned) }
+    end
+
     def self.package_types
       @package_types ||= Packages::Package.package_types.keys
     end
@@ -117,6 +127,10 @@ def self.package_types
             let(:user) { deploy_token_for_group }
 
             it { is_expected.to match_array([package1, package2, package4]) }
+
+            it_behaves_like 'disabling package registry for project' do
+              let(:packages_returned) { [package4] }
+            end
           end
 
           context 'project deploy token' do
@@ -126,6 +140,11 @@ def self.package_types
             let(:user) { deploy_token_for_project }
 
             it { is_expected.to match_array([package4]) }
+
+            it_behaves_like 'disabling package registry for project' do
+              let(:project) { subproject }
+              let(:packages_returned) { [] }
+            end
           end
         end
 
@@ -200,6 +219,9 @@ def self.package_types
 
       it_behaves_like 'concerning versionless param'
       it_behaves_like 'concerning package statuses'
+      it_behaves_like 'disabling package registry for project' do
+        let(:packages_returned) { [] }
+      end
     end
 
     context 'group has package of all types' do
diff --git a/spec/lib/gitlab/checks/branch_check_spec.rb b/spec/lib/gitlab/checks/branch_check_spec.rb
index c3d6b9510e56139170ffdbd8d4e08f9cbd66b8d4..8772e8dd9044aa182d8bb67ba8d7df144d314440 100644
--- a/spec/lib/gitlab/checks/branch_check_spec.rb
+++ b/spec/lib/gitlab/checks/branch_check_spec.rb
@@ -19,39 +19,39 @@
       end
     end
 
-    context "prohibited branches check" do
-      it "prohibits 40-character hexadecimal branch names" do
+    describe "prohibited branches check" do
+      it "forbids SHA-1 values" do
         allow(subject).to receive(:branch_name).and_return("267208abfe40e546f5e847444276f7d43a39503e")
 
-        expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
+        expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a SHA-1 or SHA-256 branch name.")
       end
 
-      it "prohibits 40-character hexadecimal branch names as the start of a path" do
-        allow(subject).to receive(:branch_name).and_return("267208abfe40e546f5e847444276f7d43a39503e/test")
+      it "forbids SHA-256 values" do
+        allow(subject).to receive(:branch_name).and_return("09b9fd3ea68e9b95a51b693a29568c898e27d1476bbd83c825664f18467fc175")
 
-        expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
+        expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a SHA-1 or SHA-256 branch name.")
       end
 
-      it "prohibits 40-character hexadecimal branch names followed by a dash as the start of a path" do
-        allow(subject).to receive(:branch_name).and_return("267208abfe40e546f5e847444276f7d43a39503e-/test")
+      it "forbids '{SHA-1}{+anything}' values" do
+        allow(subject).to receive(:branch_name).and_return("267208abfe40e546f5e847444276f7d43a39503e-")
 
-        expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
+        expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a SHA-1 or SHA-256 branch name.")
       end
 
-      it "prohibits 64-character hexadecimal branch names" do
-        allow(subject).to receive(:branch_name).and_return("09b9fd3ea68e9b95a51b693a29568c898e27d1476bbd83c825664f18467fc175")
+      it "forbids '{SHA-256}{+anything} values" do
+        allow(subject).to receive(:branch_name).and_return("09b9fd3ea68e9b95a51b693a29568c898e27d1476bbd83c825664f18467fc175-")
 
-        expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
+        expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a SHA-1 or SHA-256 branch name.")
       end
 
-      it "prohibits 64-character hexadecimal branch names as the start of a path" do
-        allow(subject).to receive(:branch_name).and_return("09b9fd3ea68e9b95a51b693a29568c898e27d1476bbd83c825664f18467fc175/test")
+      it "allows SHA-1 values to be appended to the branch name" do
+        allow(subject).to receive(:branch_name).and_return("fix-267208abfe40e546f5e847444276f7d43a39503e")
 
-        expect { subject.validate! }.to raise_error(Gitlab::GitAccess::ForbiddenError, "You cannot create a branch with a 40-character hexadecimal branch name.")
+        expect { subject.validate! }.not_to raise_error
       end
 
-      it "doesn't prohibit a nested hexadecimal in a branch name" do
-        allow(subject).to receive(:branch_name).and_return("267208abfe40e546f5e847444276f7d43a39503e-fix")
+      it "allows SHA-256 values to be appended to the branch name" do
+        allow(subject).to receive(:branch_name).and_return("fix-09b9fd3ea68e9b95a51b693a29568c898e27d1476bbd83c825664f18467fc175")
 
         expect { subject.validate! }.not_to raise_error
       end
diff --git a/spec/models/ability_spec.rb b/spec/models/ability_spec.rb
index a808cb1c823e5223609528c35b502a706ef30942..1f0e074d90b51c31bf8cd448612429d931539be0 100644
--- a/spec/models/ability_spec.rb
+++ b/spec/models/ability_spec.rb
@@ -3,9 +3,42 @@
 require 'spec_helper'
 
 RSpec.describe Ability do
-  context 'using a nil subject' do
-    it 'has no permissions' do
-      expect(described_class.policy_for(nil, nil)).to be_banned
+  describe '#policy_for' do
+    subject(:policy) { described_class.policy_for(user, subject, **options) }
+
+    let(:user) { User.new }
+    let(:subject) { :global }
+    let(:options) { {} }
+
+    context 'using a nil subject' do
+      let(:user) { nil }
+      let(:subject) { nil }
+
+      it 'has no permissions' do
+        expect(policy).to be_banned
+      end
+    end
+
+    context 'with request store', :request_store do
+      before do
+        ::Gitlab::SafeRequestStore.write(:example, :value) # make request store different from {}
+      end
+
+      it 'caches in the request store' do
+        expect(DeclarativePolicy).to receive(:policy_for).with(user, subject, cache: ::Gitlab::SafeRequestStore.storage)
+
+        policy
+      end
+
+      context 'when cache: false' do
+        let(:options) { { cache: false } }
+
+        it 'uses a fresh cache each time' do
+          expect(DeclarativePolicy).to receive(:policy_for).with(user, subject, cache: {})
+
+          policy
+        end
+      end
     end
   end
 
diff --git a/spec/models/integrations/jira_spec.rb b/spec/models/integrations/jira_spec.rb
index 9bb77f6d6d4c1d7ebd55af18a4846de73222cf54..f5eaed4d7e4151b36d117ccd806c2260de986c26 100644
--- a/spec/models/integrations/jira_spec.rb
+++ b/spec/models/integrations/jira_spec.rb
@@ -251,7 +251,7 @@
       'EXT_EXT-1234'       | 'EXT_EXT-1234'
       'EXT3_EXT-1234'      | 'EXT3_EXT-1234'
       '3EXT_EXT-1234'      | ''
-      'CVE-2022-123'       | ''
+      'CVE-2022-123'       | 'CVE-2022'
       'CVE-123'            | 'CVE-123'
       'abc-JIRA-1234'      | 'JIRA-1234'
     end
diff --git a/spec/models/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb
index cd740bca502c528589c1a0f82127376d880d3bb1..5f7b5e9c7fb367628440a723b92723fcbd9e8507 100644
--- a/spec/models/pages_domain_spec.rb
+++ b/spec/models/pages_domain_spec.rb
@@ -165,7 +165,7 @@
         it "adds error to certificate" do
           domain.valid?
 
-          expect(domain.errors.attribute_names).to contain_exactly(:key, :certificate)
+          expect(domain.errors.attribute_names).to contain_exactly(:key)
         end
       end
 
@@ -206,10 +206,25 @@
       it 'validates the certificate key length' do
         valid_domain = build(:pages_domain, :key_length_8192)
         expect(valid_domain).to be_valid
+      end
+
+      context 'when the key has more than 8192 bytes' do
+        let(:domain) do
+          build(:pages_domain, :extra_long_key)
+        end
 
-        invalid_domain = build(:pages_domain, :extra_long_key)
-        expect(invalid_domain).to be_invalid
-        expect(invalid_domain.errors[:key]).to include('Certificate Key is too long. (Max 8192 bytes)')
+        it 'adds a human readable error' do
+          expect(domain).to be_invalid
+          expect(domain.errors[:key]).to include('Certificate Key is too long. (Max 8192 bytes)')
+        end
+
+        it 'does not run SSL key verification' do
+          allow(domain).to receive(:validate_intermediates)
+
+          domain.valid?
+
+          expect(domain).not_to have_received(:validate_intermediates)
+        end
       end
     end
   end
diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb
index 46bf80b1e8fe78682b7a18201a780ca19f546e8e..341f5f2b07834517b0e432059a3039f09cbdb76e 100644
--- a/spec/models/project_spec.rb
+++ b/spec/models/project_spec.rb
@@ -6908,6 +6908,17 @@ def has_external_wiki
     end
   end
 
+  describe '.with_package_registry_enabled' do
+    subject { described_class.with_package_registry_enabled }
+
+    it 'returns projects with the package registry enabled' do
+      project_1 = create(:project)
+      create(:project, package_registry_access_level: ProjectFeature::DISABLED, packages_enabled: false)
+
+      expect(subject).to contain_exactly(project_1)
+    end
+  end
+
   describe '.deployments' do
     subject { project.deployments }
 
diff --git a/spec/policies/ci/pipeline_schedule_policy_spec.rb b/spec/policies/ci/pipeline_schedule_policy_spec.rb
index 8fc5c6ca2967cfae5dad71df5a6b5aea1ca48391..1d353b9a35e584ff0aafa3d6e6c308bf736374ae 100644
--- a/spec/policies/ci/pipeline_schedule_policy_spec.rb
+++ b/spec/policies/ci/pipeline_schedule_policy_spec.rb
@@ -6,6 +6,7 @@
   using RSpec::Parameterized::TableSyntax
 
   let_it_be(:user) { create(:user) }
+  let_it_be(:other_user) { create(:user) }
   let_it_be_with_reload(:project) { create(:project, :repository, create_tag: tag_ref_name) }
   let_it_be_with_reload(:pipeline_schedule) { create(:ci_pipeline_schedule, :nightly, project: project) }
   let_it_be(:tag_ref_name) { "v1.0.0" }
@@ -17,89 +18,180 @@
   describe 'rules' do
     describe 'rules for protected ref' do
       context 'for branch' do
+        subject(:policy) { described_class.new(user, pipeline_schedule) }
+
         %w[refs/heads/master master].each do |branch_ref|
           context "with #{branch_ref}" do
             let_it_be(:branch_ref_name) { "master" }
-            let_it_be(:branch_pipeline_schedule) do
+            let_it_be(:pipeline_schedule) do
               create(:ci_pipeline_schedule, :nightly, project: project, ref: branch_ref)
             end
 
-            where(:push_access_level, :merge_access_level, :project_role, :accessible) do
-              :no_one_can_push | :no_one_can_merge | :owner      | :be_disallowed
-              :no_one_can_push | :no_one_can_merge | :maintainer | :be_disallowed
-              :no_one_can_push | :no_one_can_merge | :developer  | :be_disallowed
-              :no_one_can_push | :no_one_can_merge | :reporter   | :be_disallowed
-              :no_one_can_push | :no_one_can_merge | :guest      | :be_disallowed
-
-              :maintainers_can_push | :no_one_can_merge | :owner      | :be_allowed
-              :maintainers_can_push | :no_one_can_merge | :maintainer | :be_allowed
-              :maintainers_can_push | :no_one_can_merge | :developer  | :be_disallowed
-              :maintainers_can_push | :no_one_can_merge | :reporter   | :be_disallowed
-              :maintainers_can_push | :no_one_can_merge | :guest      | :be_disallowed
-
-              :developers_can_push | :no_one_can_merge |  :owner      | :be_allowed
-              :developers_can_push | :no_one_can_merge |  :maintainer | :be_allowed
-              :developers_can_push | :no_one_can_merge |  :developer  | :be_allowed
-              :developers_can_push | :no_one_can_merge |  :reporter   | :be_disallowed
-              :developers_can_push | :no_one_can_merge |  :guest      | :be_disallowed
-
-              :no_one_can_push | :maintainers_can_merge | :owner      | :be_allowed
-              :no_one_can_push | :maintainers_can_merge | :maintainer | :be_allowed
-              :no_one_can_push | :maintainers_can_merge | :developer  | :be_disallowed
-              :no_one_can_push | :maintainers_can_merge | :reporter   | :be_disallowed
-              :no_one_can_push | :maintainers_can_merge | :guest      | :be_disallowed
-
-              :maintainers_can_push | :maintainers_can_merge | :owner      | :be_allowed
-              :maintainers_can_push | :maintainers_can_merge | :maintainer | :be_allowed
-              :maintainers_can_push | :maintainers_can_merge | :developer  | :be_disallowed
-              :maintainers_can_push | :maintainers_can_merge | :reporter   | :be_disallowed
-              :maintainers_can_push | :maintainers_can_merge | :guest      | :be_disallowed
-
-              :developers_can_push | :maintainers_can_merge |  :owner      | :be_allowed
-              :developers_can_push | :maintainers_can_merge |  :maintainer | :be_allowed
-              :developers_can_push | :maintainers_can_merge |  :developer  | :be_allowed
-              :developers_can_push | :maintainers_can_merge |  :reporter   | :be_disallowed
-              :developers_can_push | :maintainers_can_merge |  :guest      | :be_disallowed
-
-              :no_one_can_push | :developers_can_merge | :owner      | :be_allowed
-              :no_one_can_push | :developers_can_merge | :maintainer | :be_allowed
-              :no_one_can_push | :developers_can_merge | :developer  | :be_allowed
-              :no_one_can_push | :developers_can_merge | :reporter   | :be_disallowed
-              :no_one_can_push | :developers_can_merge | :guest      | :be_disallowed
-
-              :maintainers_can_push | :developers_can_merge | :owner      | :be_allowed
-              :maintainers_can_push | :developers_can_merge | :maintainer | :be_allowed
-              :maintainers_can_push | :developers_can_merge | :developer  | :be_allowed
-              :maintainers_can_push | :developers_can_merge | :reporter   | :be_disallowed
-              :maintainers_can_push | :developers_can_merge | :guest      | :be_disallowed
-
-              :developers_can_push | :developers_can_merge |  :owner      | :be_allowed
-              :developers_can_push | :developers_can_merge |  :maintainer | :be_allowed
-              :developers_can_push | :developers_can_merge |  :developer  | :be_allowed
-              :developers_can_push | :developers_can_merge |  :reporter   | :be_disallowed
-              :developers_can_push | :developers_can_merge |  :guest      | :be_disallowed
+            shared_examples_for 'allowed by those who can update the branch' do
+              where(:push_access_level, :merge_access_level, :project_role, :accessible) do
+                :no_one_can_push | :no_one_can_merge | :owner      | :be_disallowed
+                :no_one_can_push | :no_one_can_merge | :maintainer | :be_disallowed
+                :no_one_can_push | :no_one_can_merge | :developer  | :be_disallowed
+                :no_one_can_push | :no_one_can_merge | :reporter   | :be_disallowed
+                :no_one_can_push | :no_one_can_merge | :guest      | :be_disallowed
+
+                :maintainers_can_push | :no_one_can_merge | :owner      | :be_allowed
+                :maintainers_can_push | :no_one_can_merge | :maintainer | :be_allowed
+                :maintainers_can_push | :no_one_can_merge | :developer  | :be_disallowed
+                :maintainers_can_push | :no_one_can_merge | :reporter   | :be_disallowed
+                :maintainers_can_push | :no_one_can_merge | :guest      | :be_disallowed
+
+                :developers_can_push | :no_one_can_merge |  :owner      | :be_allowed
+                :developers_can_push | :no_one_can_merge |  :maintainer | :be_allowed
+                :developers_can_push | :no_one_can_merge |  :developer  | :be_allowed
+                :developers_can_push | :no_one_can_merge |  :reporter   | :be_disallowed
+                :developers_can_push | :no_one_can_merge |  :guest      | :be_disallowed
+
+                :no_one_can_push | :maintainers_can_merge | :owner      | :be_allowed
+                :no_one_can_push | :maintainers_can_merge | :maintainer | :be_allowed
+                :no_one_can_push | :maintainers_can_merge | :developer  | :be_disallowed
+                :no_one_can_push | :maintainers_can_merge | :reporter   | :be_disallowed
+                :no_one_can_push | :maintainers_can_merge | :guest      | :be_disallowed
+
+                :maintainers_can_push | :maintainers_can_merge | :owner      | :be_allowed
+                :maintainers_can_push | :maintainers_can_merge | :maintainer | :be_allowed
+                :maintainers_can_push | :maintainers_can_merge | :developer  | :be_disallowed
+                :maintainers_can_push | :maintainers_can_merge | :reporter   | :be_disallowed
+                :maintainers_can_push | :maintainers_can_merge | :guest      | :be_disallowed
+
+                :developers_can_push | :maintainers_can_merge |  :owner      | :be_allowed
+                :developers_can_push | :maintainers_can_merge |  :maintainer | :be_allowed
+                :developers_can_push | :maintainers_can_merge |  :developer  | :be_allowed
+                :developers_can_push | :maintainers_can_merge |  :reporter   | :be_disallowed
+                :developers_can_push | :maintainers_can_merge |  :guest      | :be_disallowed
+
+                :no_one_can_push | :developers_can_merge | :owner      | :be_allowed
+                :no_one_can_push | :developers_can_merge | :maintainer | :be_allowed
+                :no_one_can_push | :developers_can_merge | :developer  | :be_allowed
+                :no_one_can_push | :developers_can_merge | :reporter   | :be_disallowed
+                :no_one_can_push | :developers_can_merge | :guest      | :be_disallowed
+
+                :maintainers_can_push | :developers_can_merge | :owner      | :be_allowed
+                :maintainers_can_push | :developers_can_merge | :maintainer | :be_allowed
+                :maintainers_can_push | :developers_can_merge | :developer  | :be_allowed
+                :maintainers_can_push | :developers_can_merge | :reporter   | :be_disallowed
+                :maintainers_can_push | :developers_can_merge | :guest      | :be_disallowed
+
+                :developers_can_push | :developers_can_merge |  :owner      | :be_allowed
+                :developers_can_push | :developers_can_merge |  :maintainer | :be_allowed
+                :developers_can_push | :developers_can_merge |  :developer  | :be_allowed
+                :developers_can_push | :developers_can_merge |  :reporter   | :be_disallowed
+                :developers_can_push | :developers_can_merge |  :guest      | :be_disallowed
+              end
+
+              with_them do
+                before do
+                  create(:protected_branch, push_access_level, merge_access_level, name: branch_ref_name,
+                    project: project)
+                  project.add_role(user, project_role)
+                end
+
+                it { expect(policy).to try(accessible, :create_pipeline_schedule) }
+              end
             end
 
-            with_them do
-              before do
-                create(:protected_branch, push_access_level, merge_access_level, name: branch_ref_name,
-                  project: project)
-                project.add_role(user, project_role)
+            shared_examples_for 'only allowed by schedule owners who can update the branch' do
+              where(:push_access_level, :merge_access_level, :schedule_owner, :project_role, :accessible) do
+                :no_one_can_push | :no_one_can_merge  | :other_user | :owner      | :be_disallowed
+                :no_one_can_push | :no_one_can_merge  | :user       | :owner      | :be_disallowed
+                :no_one_can_push | :no_one_can_merge  | :user       | :maintainer | :be_disallowed
+                :no_one_can_push | :no_one_can_merge  | :user       | :developer  | :be_disallowed
+                :no_one_can_push | :no_one_can_merge  | :user       | :reporter   | :be_disallowed
+                :no_one_can_push | :no_one_can_merge  | :user       | :guest      | :be_disallowed
+
+                :maintainers_can_push | :no_one_can_merge | :other_user | :owner      | :be_disallowed
+                :maintainers_can_push | :no_one_can_merge | :user       | :owner      | :be_allowed
+                :maintainers_can_push | :no_one_can_merge | :user       | :maintainer | :be_allowed
+                :maintainers_can_push | :no_one_can_merge | :user       | :developer  | :be_disallowed
+                :maintainers_can_push | :no_one_can_merge | :user       | :reporter   | :be_disallowed
+                :maintainers_can_push | :no_one_can_merge | :user       | :guest      | :be_disallowed
+
+                :developers_can_push | :no_one_can_merge | :other_user | :owner      | :be_disallowed
+                :developers_can_push | :no_one_can_merge | :user       | :owner      | :be_allowed
+                :developers_can_push | :no_one_can_merge | :user       | :maintainer | :be_allowed
+                :developers_can_push | :no_one_can_merge | :user       | :developer  | :be_allowed
+                :developers_can_push | :no_one_can_merge | :user       | :reporter   | :be_disallowed
+                :developers_can_push | :no_one_can_merge | :user       | :guest      | :be_disallowed
+
+                :no_one_can_push | :maintainers_can_merge | :other_user | :owner      | :be_disallowed
+                :no_one_can_push | :maintainers_can_merge | :user       | :owner      | :be_allowed
+                :no_one_can_push | :maintainers_can_merge | :user       | :maintainer | :be_allowed
+                :no_one_can_push | :maintainers_can_merge | :user       | :developer  | :be_disallowed
+                :no_one_can_push | :maintainers_can_merge | :user       | :reporter   | :be_disallowed
+                :no_one_can_push | :maintainers_can_merge | :user       | :guest      | :be_disallowed
+
+                :maintainers_can_push | :maintainers_can_merge | :other_user | :owner      | :be_disallowed
+                :maintainers_can_push | :maintainers_can_merge | :user       | :owner      | :be_allowed
+                :maintainers_can_push | :maintainers_can_merge | :user       | :maintainer | :be_allowed
+                :maintainers_can_push | :maintainers_can_merge | :user       | :developer  | :be_disallowed
+                :maintainers_can_push | :maintainers_can_merge | :user       | :reporter   | :be_disallowed
+                :maintainers_can_push | :maintainers_can_merge | :user       | :guest      | :be_disallowed
+
+                :developers_can_push | :maintainers_can_merge | :other_user | :owner      | :be_disallowed
+                :developers_can_push | :maintainers_can_merge | :user       | :owner      | :be_allowed
+                :developers_can_push | :maintainers_can_merge | :user       | :maintainer | :be_allowed
+                :developers_can_push | :maintainers_can_merge | :user       | :developer  | :be_allowed
+                :developers_can_push | :maintainers_can_merge | :user       | :reporter   | :be_disallowed
+                :developers_can_push | :maintainers_can_merge | :user       | :guest      | :be_disallowed
+
+                :no_one_can_push | :developers_can_merge | :other_user | :owner      | :be_disallowed
+                :no_one_can_push | :developers_can_merge | :user       | :owner      | :be_allowed
+                :no_one_can_push | :developers_can_merge | :user       | :maintainer | :be_allowed
+                :no_one_can_push | :developers_can_merge | :user       | :developer  | :be_allowed
+                :no_one_can_push | :developers_can_merge | :user       | :reporter   | :be_disallowed
+                :no_one_can_push | :developers_can_merge | :user       | :guest      | :be_disallowed
+
+                :maintainers_can_push | :developers_can_merge | :other_user | :owner      | :be_disallowed
+                :maintainers_can_push | :developers_can_merge | :user       | :owner      | :be_allowed
+                :maintainers_can_push | :developers_can_merge | :user       | :maintainer | :be_allowed
+                :maintainers_can_push | :developers_can_merge | :user       | :developer  | :be_allowed
+                :maintainers_can_push | :developers_can_merge | :user       | :reporter   | :be_disallowed
+                :maintainers_can_push | :developers_can_merge | :user       | :guest      | :be_disallowed
+
+                :developers_can_push | :developers_can_merge | :other_user | :owner      | :be_disallowed
+                :developers_can_push | :developers_can_merge | :user       | :owner      | :be_allowed
+                :developers_can_push | :developers_can_merge | :user       | :maintainer | :be_allowed
+                :developers_can_push | :developers_can_merge | :user       | :developer  | :be_allowed
+                :developers_can_push | :developers_can_merge | :user       | :reporter   | :be_disallowed
+                :developers_can_push | :developers_can_merge | :user       | :guest      | :be_disallowed
               end
 
-              context 'for create_pipeline_schedule' do
-                subject(:policy) { described_class.new(user, new_branch_pipeline_schedule) }
+              with_them do
+                before do
+                  create(:protected_branch, push_access_level, merge_access_level, name: branch_ref_name,
+                    project: project)
+                  project.add_role(user, project_role)
+                  project.add_role(other_user, project_role)
 
-                let(:new_branch_pipeline_schedule) { project.pipeline_schedules.new(ref: branch_ref) }
+                  pipeline_schedule.owner = schedule_owner == :user ? user : other_user
+                end
 
-                it { expect(policy).to try(accessible, :create_pipeline_schedule) }
+                it { expect(policy).to try(accessible, ability_name) }
               end
+            end
 
-              context 'for play_pipeline_schedule' do
-                subject(:policy) { described_class.new(user, branch_pipeline_schedule) }
+            describe 'create_pipeline_schedule' do
+              let(:ability_name) { :create_pipeline_schedule }
+              let(:pipeline_schedule) { project.pipeline_schedules.new(ref: branch_ref) }
 
-                it { expect(policy).to try(accessible, :play_pipeline_schedule) }
-              end
+              it_behaves_like 'allowed by those who can update the branch'
+            end
+
+            describe 'play_pipeline_schedule' do
+              let(:ability_name) { :play_pipeline_schedule }
+
+              it_behaves_like 'allowed by those who can update the branch'
+            end
+
+            describe 'update_pipeline_schedule' do
+              let(:ability_name) { :update_pipeline_schedule }
+
+              it_behaves_like 'only allowed by schedule owners who can update the branch'
             end
           end
         end
@@ -108,49 +200,97 @@
       context 'for tag' do
         %w[refs/tags/v1.0.0 v1.0.0].each do |tag_ref|
           context "with #{tag_ref}" do
-            let_it_be(:tag_pipeline_schedule) do
+            let_it_be(:pipeline_schedule) do
               create(:ci_pipeline_schedule, :nightly, project: project, ref: tag_ref)
             end
 
-            where(:access_level, :project_role, :accessible) do
-              :no_one_can_create | :owner      | :be_disallowed
-              :no_one_can_create | :maintainer | :be_disallowed
-              :no_one_can_create | :developer  | :be_disallowed
-              :no_one_can_create | :reporter   | :be_disallowed
-              :no_one_can_create | :guest      | :be_disallowed
-
-              :maintainers_can_create | :owner      | :be_allowed
-              :maintainers_can_create | :maintainer | :be_allowed
-              :maintainers_can_create | :developer  | :be_disallowed
-              :maintainers_can_create | :reporter   | :be_disallowed
-              :maintainers_can_create | :guest      | :be_disallowed
-
-              :developers_can_create | :owner      | :be_allowed
-              :developers_can_create | :maintainer | :be_allowed
-              :developers_can_create | :developer  | :be_allowed
-              :developers_can_create | :reporter   | :be_disallowed
-              :developers_can_create | :guest      | :be_disallowed
+            subject(:policy) { described_class.new(user, pipeline_schedule) }
+
+            shared_examples_for 'allowed by those who can update the tag' do
+              where(:access_level, :project_role, :accessible) do
+                :no_one_can_create | :owner      | :be_disallowed
+                :no_one_can_create | :maintainer | :be_disallowed
+                :no_one_can_create | :developer  | :be_disallowed
+                :no_one_can_create | :reporter   | :be_disallowed
+                :no_one_can_create | :guest      | :be_disallowed
+
+                :maintainers_can_create | :owner      | :be_allowed
+                :maintainers_can_create | :maintainer | :be_allowed
+                :maintainers_can_create | :developer  | :be_disallowed
+                :maintainers_can_create | :reporter   | :be_disallowed
+                :maintainers_can_create | :guest      | :be_disallowed
+
+                :developers_can_create | :owner      | :be_allowed
+                :developers_can_create | :maintainer | :be_allowed
+                :developers_can_create | :developer  | :be_allowed
+                :developers_can_create | :reporter   | :be_disallowed
+                :developers_can_create | :guest      | :be_disallowed
+              end
+
+              with_them do
+                before do
+                  create(:protected_tag, access_level, name: tag_ref_name, project: project)
+                  project.add_role(user, project_role)
+                end
+
+                it { expect(policy).to try(accessible, ability_name) }
+              end
             end
 
-            with_them do
-              before do
-                create(:protected_tag, access_level, name: tag_ref_name, project: project)
-                project.add_role(user, project_role)
+            shared_examples_for 'only allowed by schedule owners who can update the tag' do
+              where(:access_level, :schedule_owner, :project_role, :accessible) do
+                :no_one_can_create | :other_user | :owner      | :be_disallowed
+                :no_one_can_create | :user       | :owner      | :be_disallowed
+                :no_one_can_create | :user       | :maintainer | :be_disallowed
+                :no_one_can_create | :user       | :developer  | :be_disallowed
+                :no_one_can_create | :user       | :reporter   | :be_disallowed
+                :no_one_can_create | :user       | :guest      | :be_disallowed
+
+                :maintainers_can_create | :other_user | :owner      | :be_disallowed
+                :maintainers_can_create | :user       | :owner      | :be_allowed
+                :maintainers_can_create | :user       | :maintainer | :be_allowed
+                :maintainers_can_create | :user       | :developer  | :be_disallowed
+                :maintainers_can_create | :user       | :reporter   | :be_disallowed
+                :maintainers_can_create | :user       | :guest      | :be_disallowed
+
+                :developers_can_create | :other_user | :owner      | :be_disallowed
+                :developers_can_create | :user       | :owner      | :be_allowed
+                :developers_can_create | :user       | :maintainer | :be_allowed
+                :developers_can_create | :user       | :developer  | :be_allowed
+                :developers_can_create | :user       | :reporter   | :be_disallowed
+                :developers_can_create | :user       | :guest      | :be_disallowed
               end
 
-              context 'for create_pipeline_schedule' do
-                subject(:policy) { described_class.new(user, new_tag_pipeline_schedule) }
+              with_them do
+                before do
+                  create(:protected_tag, access_level, name: tag_ref_name, project: project)
+                  project.add_role(user, project_role)
+                  project.add_role(other_user, project_role)
 
-                let(:new_tag_pipeline_schedule) { project.pipeline_schedules.new(ref: tag_ref) }
+                  pipeline_schedule.owner = schedule_owner == :user ? user : other_user
+                end
 
-                it { expect(policy).to try(accessible, :create_pipeline_schedule) }
+                it { expect(policy).to try(accessible, ability_name) }
               end
+            end
 
-              context 'for play_pipeline_schedule' do
-                subject(:policy) { described_class.new(user, tag_pipeline_schedule) }
+            describe 'create_pipeline_schedule' do
+              let(:ability_name) { :create_pipeline_schedule }
+              let(:pipeline_schedule) { project.pipeline_schedules.new(ref: tag_ref) }
 
-                it { expect(policy).to try(accessible, :play_pipeline_schedule) }
-              end
+              it_behaves_like 'allowed by those who can update the tag'
+            end
+
+            describe 'play_pipeline_schedule' do
+              let(:ability_name) { :play_pipeline_schedule }
+
+              it_behaves_like 'allowed by those who can update the tag'
+            end
+
+            describe 'update_pipeline_schedule' do
+              let(:ability_name) { :update_pipeline_schedule }
+
+              it_behaves_like 'only allowed by schedule owners who can update the tag'
             end
           end
         end
diff --git a/spec/policies/issue_policy_spec.rb b/spec/policies/issue_policy_spec.rb
index 743d96ee3dd1ede562059fdc751b9bea2665ea46..61d5b11d69d9ade05ad477158d6e732697ed1f78 100644
--- a/spec/policies/issue_policy_spec.rb
+++ b/spec/policies/issue_policy_spec.rb
@@ -142,50 +142,50 @@ def permissions(user, issue)
       let(:confidential_issue_no_assignee) { create(:issue, :confidential, project: project) }
 
       it 'does not allow non-members to read confidential issues' do
-        expect(permissions(non_member, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :admin_issue_relation)
-        expect(permissions(non_member, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation)
+        expect(permissions(non_member, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :admin_issue_relation, :award_emoji)
+        expect(permissions(non_member, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation, :award_emoji)
       end
 
       it 'does not allow guests to read confidential issues' do
-        expect(permissions(guest, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :admin_issue_relation)
-        expect(permissions(guest, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation)
+        expect(permissions(guest, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :admin_issue_relation, :award_emoji)
+        expect(permissions(guest, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation, :award_emoji)
       end
 
       it 'allows reporters to read, update, and admin confidential issues' do
-        expect(permissions(reporter, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation)
-        expect(permissions(reporter, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation)
+        expect(permissions(reporter, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation, :award_emoji)
+        expect(permissions(reporter, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation, :award_emoji)
       end
 
       it 'allows reporters from group links to read, update, and admin confidential issues' do
-        expect(permissions(reporter_from_group_link, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation)
-        expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation)
+        expect(permissions(reporter_from_group_link, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation, :award_emoji)
+        expect(permissions(reporter_from_group_link, confidential_issue_no_assignee)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation, :award_emoji)
       end
 
       it 'allows issue authors to read and update their confidential issues' do
-        expect(permissions(author, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue_relation)
+        expect(permissions(author, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue_relation, :award_emoji)
         expect(permissions(author, confidential_issue)).to be_disallowed(:admin_issue, :set_issue_metadata, :set_confidentiality)
 
-        expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :admin_issue_relation)
-        expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:admin_issue, :set_issue_metadata, :set_confidentiality)
+        expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :admin_issue_relation, :award_emoji)
+        expect(permissions(author, confidential_issue_no_assignee)).to be_disallowed(:admin_issue, :set_issue_metadata, :set_confidentiality, :award_emoji)
       end
 
       it 'does not allow issue author to read or update confidential issue moved to an private project' do
         confidential_issue.project = create(:project, :private)
 
-        expect(permissions(author, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation)
+        expect(permissions(author, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation, :award_emoji)
       end
 
       it 'allows issue assignees to read and update their confidential issues' do
-        expect(permissions(assignee, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue)
+        expect(permissions(assignee, confidential_issue)).to be_allowed(:read_issue, :read_issue_iid, :update_issue, :award_emoji)
         expect(permissions(assignee, confidential_issue)).to be_disallowed(:admin_issue, :set_issue_metadata, :set_confidentiality)
 
-        expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation)
+        expect(permissions(assignee, confidential_issue_no_assignee)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :admin_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation, :award_emoji)
       end
 
       it 'does not allow issue assignees to read or update confidential issue moved to an private project' do
         confidential_issue.project = create(:project, :private)
 
-        expect(permissions(assignee, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation)
+        expect(permissions(assignee, confidential_issue)).to be_disallowed(:read_issue, :read_issue_iid, :update_issue, :set_issue_metadata, :set_confidentiality, :admin_issue_relation, :award_emoji)
       end
     end
   end
diff --git a/spec/services/ci/pipeline_schedules/update_service_spec.rb b/spec/services/ci/pipeline_schedules/update_service_spec.rb
index 834bbcfcfeb34988d5fc51cdab94539b5b77a1f7..b84afacdcff513de7ba302fa579be9d505af67f6 100644
--- a/spec/services/ci/pipeline_schedules/update_service_spec.rb
+++ b/spec/services/ci/pipeline_schedules/update_service_spec.rb
@@ -7,16 +7,16 @@
   let_it_be_with_reload(:project) { create(:project, :public, :repository) }
   let_it_be_with_reload(:pipeline_schedule) { create(:ci_pipeline_schedule, project: project, owner: user) }
   let_it_be(:reporter) { create(:user) }
+  let_it_be(:project_owner) { create(:user) }
 
   let_it_be(:pipeline_schedule_variable) do
     create(:ci_pipeline_schedule_variable,
       key: 'foo', value: 'foovalue', pipeline_schedule: pipeline_schedule)
   end
 
-  subject(:service) { described_class.new(pipeline_schedule, user, params) }
-
   before_all do
     project.add_maintainer(user)
+    project.add_owner(project_owner)
     project.add_reporter(reporter)
 
     pipeline_schedule.reload
@@ -54,8 +54,10 @@
       subject(:service) { described_class.new(pipeline_schedule, user, params) }
 
       it 'updates database values with passed params' do
-        expect { service.execute }
-          .to change { pipeline_schedule.description }.from('pipeline schedule').to('updated_desc')
+        expect do
+          service.execute
+          pipeline_schedule.reload
+        end.to change { pipeline_schedule.description }.from('pipeline schedule').to('updated_desc')
           .and change { pipeline_schedule.ref }.from('master').to('patch-x')
           .and change { pipeline_schedule.active }.from(true).to(false)
           .and change { pipeline_schedule.cron }.from('0 1 * * *').to('*/1 * * * *')
@@ -63,6 +65,48 @@
           .and change { pipeline_schedule.variables.last.value }.from('foovalue').to('barvalue')
       end
 
+      context 'when the new branch is protected', :request_store do
+        let(:maintainer_access) { :no_one_can_merge }
+
+        before do
+          create(:protected_branch, :no_one_can_push, maintainer_access, name: 'patch-x', project: project)
+        end
+
+        after do
+          ProtectedBranches::CacheService.new(project).refresh
+        end
+
+        context 'when called by someone other than the schedule owner who can update the ref' do
+          let(:maintainer_access) { :maintainers_can_merge }
+
+          subject(:service) { described_class.new(pipeline_schedule, project_owner, params) }
+
+          it 'does not update the schedule' do
+            expect do
+              service.execute
+              pipeline_schedule.reload
+            end.not_to change { pipeline_schedule.description }
+          end
+        end
+
+        context 'when called by the schedule owner' do
+          it 'does not update the schedule' do
+            expect do
+              service.execute
+              pipeline_schedule.reload
+            end.not_to change { pipeline_schedule.description }
+          end
+
+          context 'when the owner can update the ref' do
+            let(:maintainer_access) { :maintainers_can_merge }
+
+            it 'updates the schedule' do
+              expect { service.execute }.to change { pipeline_schedule.description }
+            end
+          end
+        end
+      end
+
       context 'when creating a variable' do
         let(:params) do
           {
@@ -126,6 +170,8 @@
       end
     end
 
-    it_behaves_like 'pipeline schedules checking variables permission'
+    it_behaves_like 'pipeline schedules checking variables permission' do
+      subject(:service) { described_class.new(pipeline_schedule, user, params) }
+    end
   end
 end
diff --git a/yarn.lock b/yarn.lock
index 0e6fc9ea1ee795c295332ee3f484d8121a8b35ca..1eecf751d11bb8c3564d973c1305b97ad5b8f7e2 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -9450,10 +9450,10 @@ merge2@^1.3.0, merge2@^1.4.1:
   resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
   integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
 
-mermaid@10.5.0:
-  version "10.5.0"
-  resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-10.5.0.tgz#e90512a65b5c6e29bd86cd04ce45aa31da2be76d"
-  integrity sha512-9l0o1uUod78D3/FVYPGSsgV+Z0tSnzLBDiC9rVzvelPxuO80HbN1oDr9ofpPETQy9XpypPQa26fr09VzEPfvWA==
+mermaid@10.6.1:
+  version "10.6.1"
+  resolved "https://registry.yarnpkg.com/mermaid/-/mermaid-10.6.1.tgz#701f4160484137a417770ce757ce1887a98c00fc"
+  integrity sha512-Hky0/RpOw/1il9X8AvzOEChfJtVvmXm+y7JML5C//ePYMy0/9jCEmW1E1g86x9oDfW9+iVEdTV/i+M6KWRNs4A==
   dependencies:
     "@braintree/sanitize-url" "^6.0.1"
     "@types/d3-scale" "^4.0.3"