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"