diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-27 22:03:37 +0300 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2022-07-27 22:03:37 +0300 |
commit | 42c5548470596a37ed1a071e3bb72af2c9c35c0e (patch) | |
tree | 460370c3d31272184a5d7f74a5a52e8a25297186 | |
parent | e154e2735d9832994b0499d69732966abe2e3f5d (diff) |
Add latest changes from gitlab-org/security/gitlab@15-0-stable-ee
31 files changed, 444 insertions, 108 deletions
diff --git a/GITLAB_PAGES_VERSION b/GITLAB_PAGES_VERSION index 79f82f6b8e0..69478d187bd 100644 --- a/GITLAB_PAGES_VERSION +++ b/GITLAB_PAGES_VERSION @@ -1 +1 @@ -1.58.0 +1.58.1 diff --git a/app/controllers/autocomplete_controller.rb b/app/controllers/autocomplete_controller.rb index f84d2ed320d..22d7ccbd069 100644 --- a/app/controllers/autocomplete_controller.rb +++ b/app/controllers/autocomplete_controller.rb @@ -5,6 +5,7 @@ class AutocompleteController < ApplicationController skip_before_action :authenticate_user!, only: [:users, :award_emojis, :merge_request_target_branches] before_action :check_search_rate_limit!, only: [:users, :projects] + before_action :authorize_admin_project, only: :deploy_keys_with_owners feature_category :users, [:users, :user] feature_category :projects, [:projects] @@ -67,6 +68,10 @@ class AutocompleteController < ApplicationController private + def authorize_admin_project + render_403 unless Ability.allowed?(current_user, :admin_project, project) + end + def project @project ||= Autocomplete::ProjectFinder .new(current_user, params) diff --git a/app/finders/issuable_finder.rb b/app/finders/issuable_finder.rb index fe07a52cbf0..6e927b661c6 100644 --- a/app/finders/issuable_finder.rb +++ b/app/finders/issuable_finder.rb @@ -480,10 +480,14 @@ class IssuableFinder end def by_crm_contact(items) + return items unless can_filter_by_crm_contact? + Issuables::CrmContactFilter.new(params: original_params).filter(items) end def by_crm_organization(items) + return items unless can_filter_by_crm_organization? + Issuables::CrmOrganizationFilter.new(params: original_params).filter(items) end @@ -496,4 +500,20 @@ class IssuableFinder def feature_flag_scope params.group || params.project end + + def can_filter_by_crm_contact? + current_user&.can?(:read_crm_contact, root_group) + end + + def can_filter_by_crm_organization? + current_user&.can?(:read_crm_organization, root_group) + end + + def root_group + strong_memoize(:root_group) do + base_group = params.group || params.project&.group + + base_group&.root_ancestor + end + end end diff --git a/app/models/hooks/web_hook_log.rb b/app/models/hooks/web_hook_log.rb index 8c0565e4a38..04d6d1ebd5c 100644 --- a/app/models/hooks/web_hook_log.rb +++ b/app/models/hooks/web_hook_log.rb @@ -20,6 +20,7 @@ class WebHookLog < ApplicationRecord validates :web_hook, presence: true before_save :obfuscate_basic_auth + before_save :redact_author_email def self.recent where('created_at >= ?', 2.days.ago.beginning_of_day) @@ -39,4 +40,10 @@ class WebHookLog < ApplicationRecord def obfuscate_basic_auth self.url = safe_url end + + def redact_author_email + return unless self.request_data.dig('commit', 'author', 'email').present? + + self.request_data['commit']['author']['email'] = _('[REDACTED]') + end end diff --git a/app/models/project.rb b/app/models/project.rb index b66ec28b659..cf0dfc33490 100644 --- a/app/models/project.rb +++ b/app/models/project.rb @@ -2018,6 +2018,7 @@ class Project < ApplicationRecord end def after_import + repository.remove_prohibited_branches repository.expire_content_cache wiki.repository.expire_content_cache diff --git a/app/models/repository.rb b/app/models/repository.rb index dc0b5b54fb0..d10f4089061 100644 --- a/app/models/repository.rb +++ b/app/models/repository.rb @@ -1167,6 +1167,16 @@ class Repository @cache ||= Gitlab::RepositoryCache.new(self) end + def remove_prohibited_branches + return unless exists? + + prohibited_branches = raw_repository.branch_names.select { |name| name.match(/\A\h{40}\z/) } + + return if prohibited_branches.blank? + + prohibited_branches.each { |name| raw_repository.delete_branch(name) } + end + private # TODO Genericize finder, later split this on finders by Ref or Oid diff --git a/app/models/snippet.rb b/app/models/snippet.rb index cf4b83d44c2..2001577c88b 100644 --- a/app/models/snippet.rb +++ b/app/models/snippet.rb @@ -70,8 +70,6 @@ class Snippet < ApplicationRecord }, if: :content_changed? - validates :visibility_level, inclusion: { in: Gitlab::VisibilityLevel.values } - after_create :create_statistics # Scopes diff --git a/app/serializers/build_details_entity.rb b/app/serializers/build_details_entity.rb index 5f72259f34a..dc7b5e95361 100644 --- a/app/serializers/build_details_entity.rb +++ b/app/serializers/build_details_entity.rb @@ -151,7 +151,7 @@ class BuildDetailsEntity < Ci::JobEntity # We do not return the invalid_dependencies for all scenarios see https://gitlab.com/gitlab-org/gitlab/-/issues/287772#note_914406387 punctuation = invalid_dependencies.empty? ? '.' : ': ' _("This job could not start because it could not retrieve the needed artifacts%{punctuation}%{invalid_dependencies}") % - { invalid_dependencies: invalid_dependencies, punctuation: punctuation } + { invalid_dependencies: html_escape(invalid_dependencies), punctuation: punctuation } end def help_message(docs_url) diff --git a/app/services/concerns/update_visibility_level.rb b/app/services/concerns/update_visibility_level.rb index 4cd14a2fb53..debcff0295c 100644 --- a/app/services/concerns/update_visibility_level.rb +++ b/app/services/concerns/update_visibility_level.rb @@ -5,7 +5,7 @@ module UpdateVisibilityLevel def valid_visibility_level_change?(target, new_visibility) return true unless new_visibility - new_visibility_level = Gitlab::VisibilityLevel.level_value(new_visibility) + new_visibility_level = Gitlab::VisibilityLevel.level_value(new_visibility, fallback_value: nil) if new_visibility_level != target.visibility_level_value unless can?(current_user, :change_visibility_level, target) && diff --git a/config/feature_flags/development/ci_yaml_limit_size.yml b/config/feature_flags/development/ci_yaml_limit_size.yml deleted file mode 100644 index 222dc409c45..00000000000 --- a/config/feature_flags/development/ci_yaml_limit_size.yml +++ /dev/null @@ -1,8 +0,0 @@ ---- -name: ci_yaml_limit_size -introduced_by_url: https://dev.gitlab.org/gitlab/gitlabhq/-/merge_requests/3126 -rollout_issue_url: https://gitlab.com/gitlab-org/gitlab/-/issues/29875 -milestone: '12.0' -type: development -group: group::pipeline authoring -default_enabled: true diff --git a/config/gitlab_loose_foreign_keys.yml b/config/gitlab_loose_foreign_keys.yml index ae6c2bde081..24166535ca8 100644 --- a/config/gitlab_loose_foreign_keys.yml +++ b/config/gitlab_loose_foreign_keys.yml @@ -144,6 +144,9 @@ ci_subscriptions_projects: - table: projects column: upstream_project_id on_delete: async_delete + - table: users + column: author_id + on_delete: async_delete ci_triggers: - table: users column: owner_id diff --git a/db/migrate/20220329130330_add_author_to_ci_subscriptions_projects.rb b/db/migrate/20220329130330_add_author_to_ci_subscriptions_projects.rb new file mode 100644 index 00000000000..b1d0ac64d42 --- /dev/null +++ b/db/migrate/20220329130330_add_author_to_ci_subscriptions_projects.rb @@ -0,0 +1,20 @@ +# frozen_string_literal: true + +class AddAuthorToCiSubscriptionsProjects < Gitlab::Database::Migration[2.0] + disable_ddl_transaction! + + INDEX_NAME = 'index_ci_subscriptions_projects_author_id' + + def up + unless column_exists?(:ci_subscriptions_projects, :author_id) + add_column :ci_subscriptions_projects, :author_id, :bigint + end + + add_concurrent_index :ci_subscriptions_projects, :author_id, name: INDEX_NAME + end + + def down + remove_concurrent_index_by_name :ci_subscriptions_projects, INDEX_NAME + remove_column :ci_subscriptions_projects, :author_id + end +end diff --git a/db/schema_migrations/20220329130330 b/db/schema_migrations/20220329130330 new file mode 100644 index 00000000000..367d43a89a2 --- /dev/null +++ b/db/schema_migrations/20220329130330 @@ -0,0 +1 @@ +8726707f9f4bb8d256886c592b6a11ba8487de24f5340c837800f5ce0c32df9d
\ No newline at end of file diff --git a/db/structure.sql b/db/structure.sql index f5d926d9ba7..0ebc4e4e587 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -13162,7 +13162,8 @@ ALTER SEQUENCE ci_stages_id_seq OWNED BY ci_stages.id; CREATE TABLE ci_subscriptions_projects ( id bigint NOT NULL, downstream_project_id bigint NOT NULL, - upstream_project_id bigint NOT NULL + upstream_project_id bigint NOT NULL, + author_id bigint ); CREATE SEQUENCE ci_subscriptions_projects_id_seq @@ -27304,6 +27305,8 @@ CREATE INDEX index_ci_stages_on_pipeline_id_and_position ON ci_stages USING btre CREATE INDEX index_ci_stages_on_project_id ON ci_stages USING btree (project_id); +CREATE INDEX index_ci_subscriptions_projects_author_id ON ci_subscriptions_projects USING btree (author_id); + CREATE INDEX index_ci_subscriptions_projects_on_upstream_project_id ON ci_subscriptions_projects USING btree (upstream_project_id); CREATE UNIQUE INDEX index_ci_subscriptions_projects_unique_subscription ON ci_subscriptions_projects USING btree (downstream_project_id, upstream_project_id); diff --git a/doc/administration/instance_limits.md b/doc/administration/instance_limits.md index 8d3fec3b19e..4353a5f36b0 100644 --- a/doc/administration/instance_limits.md +++ b/doc/administration/instance_limits.md @@ -686,12 +686,6 @@ You can change these limits in the [GitLab Rails console](operations/rails_conso ApplicationSetting.update!(max_yaml_depth: 125) ``` -To disable this limitation entirely, disable the feature flag in the console: - -```ruby -Feature.disable(:ci_yaml_limit_size) -``` - ### Limit dotenv variables > [Introduced](https://gitlab.com/gitlab-org/gitlab/-/issues/321552) in GitLab 14.5. diff --git a/lib/gitlab/config/loader/yaml.rb b/lib/gitlab/config/loader/yaml.rb index 0559c85647d..7b87b5b8f97 100644 --- a/lib/gitlab/config/loader/yaml.rb +++ b/lib/gitlab/config/loader/yaml.rb @@ -41,8 +41,6 @@ module Gitlab end def too_big? - return false unless Feature.enabled?(:ci_yaml_limit_size) - !deep_size.valid? end diff --git a/lib/gitlab/visibility_level.rb b/lib/gitlab/visibility_level.rb index d378e558b8a..049e3befe64 100644 --- a/lib/gitlab/visibility_level.rb +++ b/lib/gitlab/visibility_level.rb @@ -18,6 +18,8 @@ module Gitlab scope :public_to_user, -> (user = nil) do where(visibility_level: VisibilityLevel.levels_for_user(user)) end + + alias_method :visibility_level=, :visibility= end PRIVATE = 0 unless const_defined?(:PRIVATE) @@ -108,10 +110,10 @@ module Gitlab options.key(level.to_i) || s_('VisibilityLevel|Unknown') end - def level_value(level) + def level_value(level, fallback_value: PRIVATE) return level.to_i if level.to_i.to_s == level.to_s && string_options.key(level.to_i) - string_options[level] || PRIVATE + string_options[level] || fallback_value end def string_level(level) diff --git a/scripts/changed-feature-flags b/scripts/changed-feature-flags index 86214e86788..ded6156bfa8 100755 --- a/scripts/changed-feature-flags +++ b/scripts/changed-feature-flags @@ -10,8 +10,8 @@ require_relative 'api/default_options' # Each desired feature flag state is specified as 'feature-flag=state'. This allows us to run package-and-qa with the # feature flag set to the desired state. # -# For example, if the specified files included `config/feature_flags/development/ci_yaml_limit_size.yml` and the desired -# state as specified by the second argument was enabled, the value returned would be `ci_yaml_limit_size=enabled` +# For example, if the specified files included `config/feature_flags/development/ci_awesome_feature.yml` and the desired +# state as specified by the second argument was enabled, the value returned would be `ci_awesome_feature=enabled` class GetFeatureFlagsFromFiles def initialize(options) diff --git a/spec/controllers/autocomplete_controller_spec.rb b/spec/controllers/autocomplete_controller_spec.rb index 0a809e80fcd..1df685e3e5a 100644 --- a/spec/controllers/autocomplete_controller_spec.rb +++ b/spec/controllers/autocomplete_controller_spec.rb @@ -378,61 +378,72 @@ RSpec.describe AutocompleteController do end context 'GET deploy_keys_with_owners' do - let!(:deploy_key) { create(:deploy_key, user: user) } - let!(:deploy_keys_project) { create(:deploy_keys_project, :write_access, project: project, deploy_key: deploy_key) } + let_it_be(:public_project) { create(:project, :public) } + let_it_be(:user) { create(:user) } + let_it_be(:deploy_key) { create(:deploy_key, user: user) } + let_it_be(:deploy_keys_project) do + create(:deploy_keys_project, :write_access, project: public_project, deploy_key: deploy_key) + end context 'unauthorized user' do it 'returns a not found response' do - get(:deploy_keys_with_owners, params: { project_id: project.id }) + get(:deploy_keys_with_owners, params: { project_id: public_project.id }) expect(response).to have_gitlab_http_status(:redirect) end end - context 'when the user who can read the project is logged in' do + context 'when the user is logged in' do before do sign_in(user) end - context 'and they cannot read the project' do + context 'with a non-existing project' do it 'returns a not found response' do - allow(Ability).to receive(:allowed?).and_call_original - allow(Ability).to receive(:allowed?).with(user, :read_project, project).and_return(false) - - get(:deploy_keys_with_owners, params: { project_id: project.id }) + get(:deploy_keys_with_owners, params: { project_id: 9999 }) expect(response).to have_gitlab_http_status(:not_found) end end - it 'renders the deploy key in a json payload, with its owner' do - get(:deploy_keys_with_owners, params: { project_id: project.id }) + context 'with an existing project' do + context 'when user cannot admin project' do + it 'returns a forbidden response' do + get(:deploy_keys_with_owners, params: { project_id: public_project.id }) - expect(json_response.count).to eq(1) - expect(json_response.first['title']).to eq(deploy_key.title) - expect(json_response.first['owner']['id']).to eq(deploy_key.user.id) - end + expect(response).to have_gitlab_http_status(:forbidden) + end + end - context 'with an unknown project' do - it 'returns a not found response' do - get(:deploy_keys_with_owners, params: { project_id: 9999 }) + context 'when user can admin project' do + before do + public_project.add_maintainer(user) + end - expect(response).to have_gitlab_http_status(:not_found) - end - end + context 'and user can read owner of key' do + it 'renders the deploy keys in a json payload, with owner' do + get(:deploy_keys_with_owners, params: { project_id: public_project.id }) - context 'and the user cannot read the owner of the key' do - before do - allow(Ability).to receive(:allowed?).and_call_original - allow(Ability).to receive(:allowed?).with(user, :read_user, deploy_key.user).and_return(false) - end + expect(json_response.count).to eq(1) + expect(json_response.first['title']).to eq(deploy_key.title) + expect(json_response.first['owner']['id']).to eq(deploy_key.user.id) + end + end + + context 'and user cannot read owner of key' do + before do + allow(Ability).to receive(:allowed?).and_call_original + allow(Ability).to receive(:allowed?).with(user, :read_user, deploy_key.user).and_return(false) + end - it 'returns a payload without owner' do - get(:deploy_keys_with_owners, params: { project_id: project.id }) + it 'returns a payload without owner' do + get(:deploy_keys_with_owners, params: { project_id: public_project.id }) - expect(json_response.count).to eq(1) - expect(json_response.first['title']).to eq(deploy_key.title) - expect(json_response.first['owner']).to be_nil + expect(json_response.count).to eq(1) + expect(json_response.first['title']).to eq(deploy_key.title) + expect(json_response.first['owner']).to be_nil + end + end end end end diff --git a/spec/finders/issues_finder_spec.rb b/spec/finders/issues_finder_spec.rb index 3f5a55410d2..be779129971 100644 --- a/spec/finders/issues_finder_spec.rb +++ b/spec/finders/issues_finder_spec.rb @@ -893,42 +893,65 @@ RSpec.describe IssuesFinder do end end - context 'filtering by crm contact' do - let_it_be(:contact1) { create(:contact, group: group) } - let_it_be(:contact2) { create(:contact, group: group) } + context 'crm filtering' do + let_it_be(:root_group) { create(:group) } + let_it_be(:group) { create(:group, parent: root_group) } + let_it_be(:project_crm) { create(:project, :public, group: group) } + let_it_be(:organization) { create(:organization, group: root_group) } + let_it_be(:contact1) { create(:contact, group: root_group, organization: organization) } + let_it_be(:contact2) { create(:contact, group: root_group, organization: organization) } - let_it_be(:contact1_issue1) { create(:issue, project: project1) } - let_it_be(:contact1_issue2) { create(:issue, project: project1) } - let_it_be(:contact2_issue1) { create(:issue, project: project1) } + let_it_be(:contact1_item1) { create(:issue, project: project_crm) } + let_it_be(:contact1_item2) { create(:issue, project: project_crm) } + let_it_be(:contact2_item1) { create(:issue, project: project_crm) } + let_it_be(:item_no_contact) { create(:issue, project: project_crm) } - let(:params) { { crm_contact_id: contact1.id } } + let_it_be(:all_project_issues) do + [contact1_item1, contact1_item2, contact2_item1, item_no_contact] + end - it 'returns for that contact' do - create(:issue_customer_relations_contact, issue: contact1_issue1, contact: contact1) - create(:issue_customer_relations_contact, issue: contact1_issue2, contact: contact1) - create(:issue_customer_relations_contact, issue: contact2_issue1, contact: contact2) + before do + create(:crm_settings, group: root_group, enabled: true) - expect(issues).to contain_exactly(contact1_issue1, contact1_issue2) + create(:issue_customer_relations_contact, issue: contact1_item1, contact: contact1) + create(:issue_customer_relations_contact, issue: contact1_item2, contact: contact1) + create(:issue_customer_relations_contact, issue: contact2_item1, contact: contact2) end - end - context 'filtering by crm organization' do - let_it_be(:organization) { create(:organization, group: group) } - let_it_be(:contact1) { create(:contact, group: group, organization: organization) } - let_it_be(:contact2) { create(:contact, group: group, organization: organization) } + context 'filtering by crm contact' do + let(:params) { { project_id: project_crm.id, crm_contact_id: contact1.id } } - let_it_be(:contact1_issue1) { create(:issue, project: project1) } - let_it_be(:contact1_issue2) { create(:issue, project: project1) } - let_it_be(:contact2_issue1) { create(:issue, project: project1) } + context 'when the user can read crm contacts' do + it 'returns for that contact' do + root_group.add_reporter(user) - let(:params) { { crm_organization_id: organization.id } } + expect(issues).to contain_exactly(contact1_item1, contact1_item2) + end + end - it 'returns for that contact' do - create(:issue_customer_relations_contact, issue: contact1_issue1, contact: contact1) - create(:issue_customer_relations_contact, issue: contact1_issue2, contact: contact1) - create(:issue_customer_relations_contact, issue: contact2_issue1, contact: contact2) + context 'when the user can not read crm contacts' do + it 'does not filter by contact' do + expect(issues).to match_array(all_project_issues) + end + end + end - expect(issues).to contain_exactly(contact1_issue1, contact1_issue2, contact2_issue1) + context 'filtering by crm organization' do + let(:params) { { project_id: project_crm.id, crm_organization_id: organization.id } } + + context 'when the user can read crm organization' do + it 'returns for that organization' do + root_group.add_reporter(user) + + expect(issues).to contain_exactly(contact1_item1, contact1_item2, contact2_item1) + end + end + + context 'when the user can not read crm organization' do + it 'does not filter by organization' do + expect(issues).to match_array(all_project_issues) + end + end end end diff --git a/spec/graphql/resolvers/issues_resolver_spec.rb b/spec/graphql/resolvers/issues_resolver_spec.rb index e6ec9d8c895..609484639cb 100644 --- a/spec/graphql/resolvers/issues_resolver_spec.rb +++ b/spec/graphql/resolvers/issues_resolver_spec.rb @@ -30,6 +30,9 @@ RSpec.describe Resolvers::IssuesResolver do before_all do project.add_developer(current_user) project.add_reporter(reporter) + + create(:crm_settings, group: group, enabled: true) + create(:label_link, label: label1, target: issue1) create(:label_link, label: label1, target: issue2) create(:label_link, label: label2, target: issue2) @@ -399,6 +402,8 @@ RSpec.describe Resolvers::IssuesResolver do let_it_be(:crm_issue3) { create(:issue, project: project) } before_all do + group.add_developer(current_user) + create(:issue_customer_relations_contact, issue: crm_issue1, contact: contact1) create(:issue_customer_relations_contact, issue: crm_issue2, contact: contact2) create(:issue_customer_relations_contact, issue: crm_issue3, contact: contact3) @@ -631,13 +636,13 @@ RSpec.describe Resolvers::IssuesResolver do end it 'finds a specific issue with iid', :request_store do - result = batch_sync(max_queries: 7) { resolve_issues(iid: issue1.iid).to_a } + result = batch_sync(max_queries: 8) { resolve_issues(iid: issue1.iid).to_a } expect(result).to contain_exactly(issue1) end it 'batches queries that only include IIDs', :request_store do - result = batch_sync(max_queries: 7) do + result = batch_sync(max_queries: 8) do [issue1, issue2] .map { |issue| resolve_issues(iid: issue.iid.to_s) } .flat_map(&:to_a) @@ -647,7 +652,7 @@ RSpec.describe Resolvers::IssuesResolver do end it 'finds a specific issue with iids', :request_store do - result = batch_sync(max_queries: 7) do + result = batch_sync(max_queries: 8) do resolve_issues(iids: [issue1.iid]).to_a end diff --git a/spec/lib/gitlab/config/loader/yaml_spec.rb b/spec/lib/gitlab/config/loader/yaml_spec.rb index 66ea931a42c..c7f84cd583c 100644 --- a/spec/lib/gitlab/config/loader/yaml_spec.rb +++ b/spec/lib/gitlab/config/loader/yaml_spec.rb @@ -120,12 +120,6 @@ RSpec.describe Gitlab::Config::Loader::Yaml do it 'returns false' do expect(loader).not_to be_valid end - - it 'returns true if "ci_yaml_limit_size" feature flag is disabled' do - stub_feature_flags(ci_yaml_limit_size: false) - - expect(loader).to be_valid - end end describe '#load!' do diff --git a/spec/lib/gitlab/import_export/all_models.yml b/spec/lib/gitlab/import_export/all_models.yml index 1546b6a26c8..a342d1f6c1b 100644 --- a/spec/lib/gitlab/import_export/all_models.yml +++ b/spec/lib/gitlab/import_export/all_models.yml @@ -565,7 +565,6 @@ project: - remove_source_branch_after_merge - deleting_user - upstream_projects -- downstream_projects - upstream_project_subscriptions - downstream_project_subscriptions - service_desk_setting diff --git a/spec/lib/gitlab/visibility_level_spec.rb b/spec/lib/gitlab/visibility_level_spec.rb index 0d34d22cbbe..9555c0afba2 100644 --- a/spec/lib/gitlab/visibility_level_spec.rb +++ b/spec/lib/gitlab/visibility_level_spec.rb @@ -4,20 +4,52 @@ require 'spec_helper' RSpec.describe Gitlab::VisibilityLevel do describe '.level_value' do - it 'converts "public" to integer value' do - expect(described_class.level_value('public')).to eq(Gitlab::VisibilityLevel::PUBLIC) + where(:string_value, :integer_value) do + [ + ['private', described_class::PRIVATE], + ['internal', described_class::INTERNAL], + ['public', described_class::PUBLIC] + ] end - it 'converts string integer to integer value' do - expect(described_class.level_value('20')).to eq(20) + with_them do + it "converts '#{params[:string_value]}' to integer value #{params[:integer_value]}" do + expect(described_class.level_value(string_value)).to eq(integer_value) + end + + it "converts string integer '#{params[:integer_value]}' to integer value #{params[:integer_value]}" do + expect(described_class.level_value(integer_value.to_s)).to eq(integer_value) + end + + it 'defaults to PRIVATE when string integer value is not valid' do + expect(described_class.level_value(integer_value.to_s + 'r')).to eq(described_class::PRIVATE) + expect(described_class.level_value(integer_value.to_s + ' ')).to eq(described_class::PRIVATE) + expect(described_class.level_value('r' + integer_value.to_s)).to eq(described_class::PRIVATE) + end + + it 'defaults to PRIVATE when string value is not valid' do + expect(described_class.level_value(string_value.capitalize)).to eq(described_class::PRIVATE) + expect(described_class.level_value(string_value + ' ')).to eq(described_class::PRIVATE) + expect(described_class.level_value(string_value + 'r')).to eq(described_class::PRIVATE) + end end it 'defaults to PRIVATE when string value is not valid' do - expect(described_class.level_value('invalid')).to eq(Gitlab::VisibilityLevel::PRIVATE) + expect(described_class.level_value('invalid')).to eq(described_class::PRIVATE) end it 'defaults to PRIVATE when integer value is not valid' do - expect(described_class.level_value(100)).to eq(Gitlab::VisibilityLevel::PRIVATE) + expect(described_class.level_value(100)).to eq(described_class::PRIVATE) + end + + context 'when `fallback_value` is set to `nil`' do + it 'returns `nil` when string value is not valid' do + expect(described_class.level_value('invalid', fallback_value: nil)).to be_nil + end + + it 'returns `nil` when integer value is not valid' do + expect(described_class.level_value(100, fallback_value: nil)).to be_nil + end end end diff --git a/spec/models/hooks/web_hook_log_spec.rb b/spec/models/hooks/web_hook_log_spec.rb index 9cfbb14e087..25df569c461 100644 --- a/spec/models/hooks/web_hook_log_spec.rb +++ b/spec/models/hooks/web_hook_log_spec.rb @@ -30,15 +30,12 @@ RSpec.describe WebHookLog do end describe '#save' do - let(:web_hook_log) { build(:web_hook_log, url: url) } - let(:url) { 'http://example.com' } - - subject { web_hook_log.save! } + context 'with basic auth credentials' do + let(:web_hook_log) { build(:web_hook_log, url: 'http://test:123@example.com') } - it { is_expected.to eq(true) } + subject { web_hook_log.save! } - context 'with basic auth credentials' do - let(:url) { 'http://test:123@example.com'} + it { is_expected.to eq(true) } it 'obfuscates the basic auth credentials' do subject @@ -46,6 +43,30 @@ RSpec.describe WebHookLog do expect(web_hook_log.url).to eq('http://*****:*****@example.com') end end + + context 'with author email' do + let(:author) { create(:user) } + let(:web_hook_log) { create(:web_hook_log, request_data: data) } + let(:data) do + { + commit: { + author: { + name: author.name, + email: author.email + } + } + }.deep_stringify_keys + end + + it "redacts author's email" do + expect(web_hook_log.request_data['commit']).to match a_hash_including( + 'author' => { + 'name' => author.name, + 'email' => _('[REDACTED]') + } + ) + end + end end describe '#success?' do diff --git a/spec/models/project_spec.rb b/spec/models/project_spec.rb index ed5b3d4e0be..990189a10a1 100644 --- a/spec/models/project_spec.rb +++ b/spec/models/project_spec.rb @@ -5623,6 +5623,7 @@ RSpec.describe Project, factory_default: :keep do let(:import_state) { create(:import_state, project: project) } it 'runs the correct hooks' do + expect(project.repository).to receive(:remove_prohibited_branches) expect(project.repository).to receive(:expire_content_cache) expect(project.wiki.repository).to receive(:expire_content_cache) expect(import_state).to receive(:finish) diff --git a/spec/models/repository_spec.rb b/spec/models/repository_spec.rb index 215f83adf5d..e24adc20143 100644 --- a/spec/models/repository_spec.rb +++ b/spec/models/repository_spec.rb @@ -3375,4 +3375,62 @@ RSpec.describe Repository do end end end + + describe '#remove_prohibited_branches' do + let(:branch_name) { '37fd3601be4c25497a39fa2e6a206e09e759d597' } + + before do + allow(repository.raw_repository).to receive(:branch_names).and_return([branch_name]) + end + + context 'when prohibited branch exists' do + it 'deletes prohibited branch' do + expect(repository.raw_repository).to receive(:delete_branch).with(branch_name) + + repository.remove_prohibited_branches + end + end + + shared_examples 'does not delete branch' do + it 'returns without removing the branch' do + expect(repository.raw_repository).not_to receive(:delete_branch) + + repository.remove_prohibited_branches + end + end + + context 'when branch name is 40-characters long but not hexadecimal' do + let(:branch_name) { '37fd3601be4c25497a39fa2e6a206e09e759d59s' } + + include_examples 'does not delete branch' + end + + context 'when branch name is hexadecimal' do + context 'when branch name is less than 40-characters long' do + let(:branch_name) { '37fd3601be4c25497a39fa2e6a206e09e759d' } + + include_examples 'does not delete branch' + end + + context 'when branch name is more than 40-characters long' do + let(:branch_name) { '37fd3601be4c25497a39fa2e6a206e09e759dfdfd' } + + include_examples 'does not delete branch' + end + end + + context 'when prohibited branch does not exist' do + let(:branch_name) { 'main' } + + include_examples 'does not delete branch' + end + + context 'when raw repository does not exist' do + before do + allow(repository).to receive(:exists?).and_return(false) + end + + include_examples 'does not delete branch' + end + end end diff --git a/spec/models/snippet_spec.rb b/spec/models/snippet_spec.rb index 70afafce132..a54edc8510e 100644 --- a/spec/models/snippet_spec.rb +++ b/spec/models/snippet_spec.rb @@ -36,8 +36,6 @@ RSpec.describe Snippet do it { is_expected.to validate_presence_of(:content) } - it { is_expected.to validate_inclusion_of(:visibility_level).in_array(Gitlab::VisibilityLevel.values) } - it do allow(Gitlab::CurrentSettings).to receive(:snippet_size_limit).and_return(1) diff --git a/spec/serializers/build_details_entity_spec.rb b/spec/serializers/build_details_entity_spec.rb index dd8238456aa..916798c669c 100644 --- a/spec/serializers/build_details_entity_spec.rb +++ b/spec/serializers/build_details_entity_spec.rb @@ -170,6 +170,24 @@ RSpec.describe BuildDetailsEntity do expect(message).to include('could not retrieve the needed artifacts.') end end + + context 'when dependency contains invalid dependency names' do + invalid_name = 'XSS<a href=# data-disable-with="<img src=x onerror=alert(document.domain)>">' + let!(:test1) { create(:ci_build, :success, :expired, pipeline: pipeline, name: invalid_name, stage_idx: 0) } + let!(:build) { create(:ci_build, :pending, pipeline: pipeline, stage_idx: 1, options: { dependencies: [invalid_name] }) } + + before do + build.pipeline.unlocked! + build.drop!(:missing_dependency_failure) + end + + it { is_expected.to include(failure_reason: 'missing_dependency_failure') } + + it 'escapes the invalid dependency names' do + escaped_name = html_escape(invalid_name) + expect(message).to include(escaped_name) + end + end end context 'when a build has environment with latest deployment' do diff --git a/spec/services/groups/update_service_spec.rb b/spec/services/groups/update_service_spec.rb index 46c5e2a9818..c9f247be04c 100644 --- a/spec/services/groups/update_service_spec.rb +++ b/spec/services/groups/update_service_spec.rb @@ -242,6 +242,69 @@ RSpec.describe Groups::UpdateService do end end + context 'when user is not group owner' do + context 'when group is private' do + before do + private_group.add_maintainer(user) + end + + it 'does not update the group to public' do + result = described_class.new(private_group, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC).execute + + expect(result).to eq(false) + expect(private_group.errors.count).to eq(1) + expect(private_group).to be_private + end + + it 'does not update the group to public with tricky value' do + result = described_class.new(private_group, user, visibility_level: Gitlab::VisibilityLevel::PUBLIC.to_s + 'r').execute + + expect(result).to eq(false) + expect(private_group.errors.count).to eq(1) + expect(private_group).to be_private + end + end + + context 'when group is public' do + before do + public_group.add_maintainer(user) + end + + it 'does not update the group to private' do + result = described_class.new(public_group, user, visibility_level: Gitlab::VisibilityLevel::PRIVATE).execute + + expect(result).to eq(false) + expect(public_group.errors.count).to eq(1) + expect(public_group).to be_public + end + + it 'does not update the group to private with invalid string value' do + result = described_class.new(public_group, user, visibility_level: 'invalid').execute + + expect(result).to eq(false) + expect(public_group.errors.count).to eq(1) + expect(public_group).to be_public + end + + it 'does not update the group to private with valid string value' do + result = described_class.new(public_group, user, visibility_level: 'private').execute + + expect(result).to eq(false) + expect(public_group.errors.count).to eq(1) + expect(public_group).to be_public + end + + # See https://gitlab.com/gitlab-org/gitlab/-/issues/359910 + it 'does not update the group to private because of Active Record typecasting' do + result = described_class.new(public_group, user, visibility_level: 'public').execute + + expect(result).to eq(true) + expect(public_group.errors.count).to eq(0) + expect(public_group).to be_public + end + end + end + context 'when updating #emails_disabled' do let(:service) { described_class.new(internal_group, user, emails_disabled: true) } diff --git a/spec/services/projects/update_service_spec.rb b/spec/services/projects/update_service_spec.rb index 7b5bf1db030..7e2c7c89276 100644 --- a/spec/services/projects/update_service_spec.rb +++ b/spec/services/projects/update_service_spec.rb @@ -120,6 +120,65 @@ RSpec.describe Projects::UpdateService do end end + context 'when user is not project owner' do + let_it_be(:maintainer) { create(:user) } + + before do + project.add_maintainer(maintainer) + end + + context 'when project is private' do + it 'does not update the project to public' do + result = update_project(project, maintainer, visibility_level: Gitlab::VisibilityLevel::PUBLIC) + + expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' }) + expect(project).to be_private + end + + it 'does not update the project to public with tricky value' do + result = update_project(project, maintainer, visibility_level: Gitlab::VisibilityLevel::PUBLIC.to_s + 'r') + + expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' }) + expect(project).to be_private + end + end + + context 'when project is public' do + before do + project.update!(visibility_level: Gitlab::VisibilityLevel::PUBLIC) + end + + it 'does not update the project to private' do + result = update_project(project, maintainer, visibility_level: Gitlab::VisibilityLevel::PRIVATE) + + expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' }) + expect(project).to be_public + end + + it 'does not update the project to private with invalid string value' do + result = update_project(project, maintainer, visibility_level: 'invalid') + + expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' }) + expect(project).to be_public + end + + it 'does not update the project to private with valid string value' do + result = update_project(project, maintainer, visibility_level: 'private') + + expect(result).to eq({ status: :error, message: 'New visibility level not allowed!' }) + expect(project).to be_public + end + + # See https://gitlab.com/gitlab-org/gitlab/-/issues/359910 + it 'does not update the project to private because of Active Record typecasting' do + result = update_project(project, maintainer, visibility_level: 'public') + + expect(result).to eq({ status: :success }) + expect(project).to be_public + end + end + end + context 'when updating shared runners' do context 'can enable shared runners' do let(:group) { create(:group, shared_runners_enabled: true) } |