diff options
-rw-r--r-- | app/controllers/projects/tags_controller.rb | 2 | ||||
-rw-r--r-- | app/graphql/types/permission_types/base_permission_type.rb | 2 | ||||
-rw-r--r-- | app/models/ability.rb | 14 | ||||
-rw-r--r-- | app/models/pages_domain.rb | 5 | ||||
-rw-r--r-- | app/models/user.rb | 4 | ||||
-rw-r--r-- | app/policies/ci/pipeline_schedule_policy.rb | 3 | ||||
-rw-r--r-- | app/policies/issue_policy.rb | 5 | ||||
-rw-r--r-- | app/services/ci/pipeline_schedules/base_save_service.rb | 6 | ||||
-rw-r--r-- | app/services/ci/pipeline_schedules/update_service.rb | 6 | ||||
-rw-r--r-- | package.json | 2 | ||||
-rw-r--r-- | spec/controllers/projects/pipeline_schedules_controller_spec.rb | 4 | ||||
-rw-r--r-- | spec/controllers/projects/tags_controller_spec.rb | 12 | ||||
-rw-r--r-- | spec/features/projects/pages/user_adds_domain_spec.rb | 1 | ||||
-rw-r--r-- | spec/models/ability_spec.rb | 39 | ||||
-rw-r--r-- | spec/models/pages_domain_spec.rb | 23 | ||||
-rw-r--r-- | spec/policies/ci/pipeline_schedule_policy_spec.rb | 338 | ||||
-rw-r--r-- | spec/policies/issue_policy_spec.rb | 30 | ||||
-rw-r--r-- | spec/services/ci/pipeline_schedules/update_service_spec.rb | 56 | ||||
-rw-r--r-- | yarn.lock | 8 |
19 files changed, 415 insertions, 145 deletions
diff --git a/app/controllers/projects/tags_controller.rb b/app/controllers/projects/tags_controller.rb index 3c1735c728c..d3e38774aaa 100644 --- a/app/controllers/projects/tags_controller.rb +++ b/app/controllers/projects/tags_controller.rb @@ -29,7 +29,7 @@ class Projects::TagsController < Projects::ApplicationController 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/graphql/types/permission_types/base_permission_type.rb b/app/graphql/types/permission_types/base_permission_type.rb index d45c61f489b..ae9de845593 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 @@ module Types 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 d8510524c1f..d9a875bf93e 100644 --- a/app/models/ability.rb +++ b/app/models/ability.rb @@ -70,13 +70,13 @@ class Ability 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 @@ class Ability # 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/pages_domain.rb b/app/models/pages_domain.rb index b86bc761cc1..08326cf39e6 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/user.rb b/app/models/user.rb index c4e867ab571..b1349cd3f65 100644 --- a/app/models/user.rb +++ b/app/models/user.rb @@ -1297,8 +1297,8 @@ class User < MainClusterwide::ApplicationRecord 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 cbc60c4a30a..9e558cd91c1 100644 --- a/app/policies/ci/pipeline_schedule_policy.rb +++ b/app/policies/ci/pipeline_schedule_policy.rb @@ -25,7 +25,7 @@ module Ci 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 @@ module Ci 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 538959c92bd..f642c6eab2d 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 45d70e5a65d..e6f633498e9 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 @@ module Ci 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 2fd1173ecce..76b2121c4e1 100644 --- a/app/services/ci/pipeline_schedules/update_service.rb +++ b/app/services/ci/pipeline_schedules/update_service.rb @@ -12,6 +12,12 @@ module Ci @params = params end + def execute + return forbidden_to_save unless allowed_to_save? + + super + end + private def authorize_message diff --git a/package.json b/package.json index 0893f6a6dde..0133e2d8871 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 cd828c956a0..7cd4f43d4da 100644 --- a/spec/controllers/projects/pipeline_schedules_controller_spec.rb +++ b/spec/controllers/projects/pipeline_schedules_controller_spec.rb @@ -65,6 +65,10 @@ RSpec.describe Projects::PipelineSchedulesController, feature_category: :continu 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 3d1f8c12022..cab0778bd13 100644 --- a/spec/controllers/projects/tags_controller_spec.rb +++ b/spec/controllers/projects/tags_controller_spec.rb @@ -52,6 +52,18 @@ RSpec.describe Projects::TagsController do 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 14b01cb63d2..04a9f450b52 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 @@ RSpec.describe 'User adds pages domain', :js, feature_category: :pages do 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/models/ability_spec.rb b/spec/models/ability_spec.rb index a808cb1c823..1f0e074d90b 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/pages_domain_spec.rb b/spec/models/pages_domain_spec.rb index cd740bca502..5f7b5e9c7fb 100644 --- a/spec/models/pages_domain_spec.rb +++ b/spec/models/pages_domain_spec.rb @@ -165,7 +165,7 @@ RSpec.describe PagesDomain do 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 @@ RSpec.describe PagesDomain do 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/policies/ci/pipeline_schedule_policy_spec.rb b/spec/policies/ci/pipeline_schedule_policy_spec.rb index 8fc5c6ca296..1d353b9a35e 100644 --- a/spec/policies/ci/pipeline_schedule_policy_spec.rb +++ b/spec/policies/ci/pipeline_schedule_policy_spec.rb @@ -6,6 +6,7 @@ RSpec.describe Ci::PipelineSchedulePolicy, :models, :clean_gitlab_redis_cache, f 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 @@ RSpec.describe Ci::PipelineSchedulePolicy, :models, :clean_gitlab_redis_cache, f 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 @@ RSpec.describe Ci::PipelineSchedulePolicy, :models, :clean_gitlab_redis_cache, f 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 743d96ee3dd..61d5b11d69d 100644 --- a/spec/policies/issue_policy_spec.rb +++ b/spec/policies/issue_policy_spec.rb @@ -142,50 +142,50 @@ RSpec.describe IssuePolicy, feature_category: :team_planning do 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 834bbcfcfeb..b84afacdcff 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 @@ RSpec.describe Ci::PipelineSchedules::UpdateService, feature_category: :continuo 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 @@ RSpec.describe Ci::PipelineSchedules::UpdateService, feature_category: :continuo 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 @@ RSpec.describe Ci::PipelineSchedules::UpdateService, feature_category: :continuo .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 @@ RSpec.describe Ci::PipelineSchedules::UpdateService, feature_category: :continuo 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 0e6fc9ea1ee..1eecf751d11 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" |