diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 10:34:06 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2021-02-18 10:34:06 +0000 |
commit | 859a6fb938bb9ee2a317c46dfa4fcc1af49608f0 (patch) | |
tree | d7f2700abe6b4ffcb2dcfc80631b2d87d0609239 /app/models/concerns | |
parent | 446d496a6d000c73a304be52587cd9bbc7493136 (diff) | |
download | gitlab-ce-13.9.0-rc42.tar.gz |
Add latest changes from gitlab-org/gitlab@13-9-stable-eev13.9.0-rc42
Diffstat (limited to 'app/models/concerns')
20 files changed, 345 insertions, 48 deletions
diff --git a/app/models/concerns/analytics/cycle_analytics/stage.rb b/app/models/concerns/analytics/cycle_analytics/stage.rb index f1c39dda49d..080ff07ec0c 100644 --- a/app/models/concerns/analytics/cycle_analytics/stage.rb +++ b/app/models/concerns/analytics/cycle_analytics/stage.rb @@ -49,6 +49,14 @@ module Analytics end end + def start_event_identifier + backward_compatible_identifier(:start_event_identifier) || super + end + + def end_event_identifier + backward_compatible_identifier(:end_event_identifier) || super + end + def start_event_label_based? start_event_identifier && start_event.label_based? end @@ -128,6 +136,17 @@ module Analytics .id_in(label_id) .exists? end + + # Temporary, will be removed in 13.10 + def backward_compatible_identifier(attribute_name) + removed_identifier = 6 # References IssueFirstMentionedInCommit removed on https://gitlab.com/gitlab-org/gitlab/-/merge_requests/51975 + replacement_identifier = :issue_first_mentioned_in_commit + + # ActiveRecord returns nil if the column value is not part of the Enum definition + if self[attribute_name].nil? && read_attribute_before_type_cast(attribute_name) == removed_identifier + replacement_identifier + end + end end end end diff --git a/app/models/concerns/atomic_internal_id.rb b/app/models/concerns/atomic_internal_id.rb index baa99fa5a7f..bbf9ecbcfe9 100644 --- a/app/models/concerns/atomic_internal_id.rb +++ b/app/models/concerns/atomic_internal_id.rb @@ -26,20 +26,31 @@ module AtomicInternalId extend ActiveSupport::Concern + MissingValueError = Class.new(StandardError) + class_methods do def has_internal_id( # rubocop:disable Naming/PredicateName - column, scope:, init: :not_given, ensure_if: nil, track_if: nil, - presence: true, backfill: false, hook_names: :create) + column, scope:, init: :not_given, ensure_if: nil, track_if: nil, presence: true, hook_names: :create) raise "has_internal_id init must not be nil if given." if init.nil? raise "has_internal_id needs to be defined on association." unless self.reflect_on_association(scope) init = infer_init(scope) if init == :not_given - before_validation :"track_#{scope}_#{column}!", on: hook_names, if: track_if - before_validation :"ensure_#{scope}_#{column}!", on: hook_names, if: ensure_if - validates column, presence: presence + callback_names = Array.wrap(hook_names).map { |hook_name| :"before_#{hook_name}" } + callback_names.each do |callback_name| + # rubocop:disable GitlabSecurity/PublicSend + public_send(callback_name, :"track_#{scope}_#{column}!", if: track_if) + public_send(callback_name, :"ensure_#{scope}_#{column}!", if: ensure_if) + # rubocop:enable GitlabSecurity/PublicSend + end + after_rollback :"clear_#{scope}_#{column}!", on: hook_names, if: ensure_if + + if presence + before_create :"validate_#{column}_exists!" + before_update :"validate_#{column}_exists!" + end define_singleton_internal_id_methods(scope, column, init) - define_instance_internal_id_methods(scope, column, init, backfill) + define_instance_internal_id_methods(scope, column, init) end private @@ -62,10 +73,8 @@ module AtomicInternalId # - track_{scope}_{column}! # - reset_{scope}_{column} # - {column}= - def define_instance_internal_id_methods(scope, column, init, backfill) + def define_instance_internal_id_methods(scope, column, init) define_method("ensure_#{scope}_#{column}!") do - return if backfill && self.class.where(column => nil).exists? - scope_value = internal_id_read_scope(scope) value = read_attribute(column) return value unless scope_value @@ -79,6 +88,8 @@ module AtomicInternalId internal_id_scope_usage, init) write_attribute(column, value) + + @internal_id_set_manually = false end value @@ -110,6 +121,7 @@ module AtomicInternalId super(value).tap do |v| # Indicate the iid was set from externally @internal_id_needs_tracking = true + @internal_id_set_manually = true end end @@ -128,6 +140,20 @@ module AtomicInternalId read_attribute(column) end + + define_method("clear_#{scope}_#{column}!") do + return if @internal_id_set_manually + + return unless public_send(:"#{column}_previously_changed?") # rubocop:disable GitlabSecurity/PublicSend + + write_attribute(column, nil) + end + + define_method("validate_#{column}_exists!") do + value = read_attribute(column) + + raise MissingValueError, "#{column} was unexpectedly blank!" if value.blank? + end end # Defines class methods: diff --git a/app/models/concerns/cacheable_attributes.rb b/app/models/concerns/cacheable_attributes.rb index de176ffde5c..ee56322cce7 100644 --- a/app/models/concerns/cacheable_attributes.rb +++ b/app/models/concerns/cacheable_attributes.rb @@ -83,6 +83,6 @@ module CacheableAttributes end def cache! - self.class.cache_backend.write(self.class.cache_key, self, expires_in: 1.minute) + self.class.cache_backend.write(self.class.cache_key, self, expires_in: Gitlab.config.gitlab['application_settings_cache_seconds'] || 60) end end diff --git a/app/models/concerns/can_move_repository_storage.rb b/app/models/concerns/can_move_repository_storage.rb index 52c3a4106e3..1132e4e79ac 100644 --- a/app/models/concerns/can_move_repository_storage.rb +++ b/app/models/concerns/can_move_repository_storage.rb @@ -16,10 +16,10 @@ module CanMoveRepositoryStorage !skip_git_transfer_check && git_transfer_in_progress? raise RepositoryReadOnlyError, _('Repository already read-only') if - self.class.where(id: id).pick(:repository_read_only) + _safe_read_repository_read_only_column raise ActiveRecord::RecordNotSaved, _('Database update failed') unless - update_column(:repository_read_only, true) + _update_repository_read_only_column(true) nil end @@ -30,7 +30,7 @@ module CanMoveRepositoryStorage def set_repository_writable! with_lock do raise ActiveRecord::RecordNotSaved, _('Database update failed') unless - update_column(:repository_read_only, false) + _update_repository_read_only_column(false) nil end @@ -43,4 +43,19 @@ module CanMoveRepositoryStorage def reference_counter(type:) Gitlab::ReferenceCounter.new(type.identifier_for_container(self)) end + + private + + # Not all resources that can move repositories have the `repository_read_only` + # in their table, for example groups. We need these methods to override the + # behavior in those classes in order to access the column. + def _safe_read_repository_read_only_column + # This was added originally this way because of + # https://gitlab.com/gitlab-org/gitlab/-/commit/43f9b98302d3985312c9f8b66018e2835d8293d2 + self.class.where(id: id).pick(:repository_read_only) + end + + def _update_repository_read_only_column(value) + update_column(:repository_read_only, value) + end end diff --git a/app/models/concerns/enums/ci/pipeline.rb b/app/models/concerns/enums/ci/pipeline.rb index e1f07fa162c..f8314d8b429 100644 --- a/app/models/concerns/enums/ci/pipeline.rb +++ b/app/models/concerns/enums/ci/pipeline.rb @@ -10,6 +10,9 @@ module Enums unknown_failure: 0, config_error: 1, external_validation_failure: 2, + activity_limit_exceeded: 20, + size_limit_exceeded: 21, + job_activity_limit_exceeded: 22, deployments_limit_exceeded: 23 } end @@ -71,11 +74,10 @@ module Enums remote_source: 4, external_project_source: 5, bridge_source: 6, - parameter_source: 7 + parameter_source: 7, + compliance_source: 8 } end end end end - -Enums::Ci::Pipeline.prepend_if_ee('EE::Enums::Ci::Pipeline') diff --git a/app/models/concerns/featurable.rb b/app/models/concerns/featurable.rb index 20b72957ec2..ed9bce87da1 100644 --- a/app/models/concerns/featurable.rb +++ b/app/models/concerns/featurable.rb @@ -88,9 +88,6 @@ module Featurable end def feature_available?(feature, user) - # This feature might not be behind a feature flag at all, so default to true - return false unless ::Feature.enabled?(feature, user, default_enabled: true) - get_permission(user, feature) end diff --git a/app/models/concerns/has_repository.rb b/app/models/concerns/has_repository.rb index 9692941d8b2..b9ad78c14fd 100644 --- a/app/models/concerns/has_repository.rb +++ b/app/models/concerns/has_repository.rb @@ -15,15 +15,6 @@ module HasRepository delegate :base_dir, :disk_path, to: :storage - class_methods do - def pick_repository_storage - # We need to ensure application settings are fresh when we pick - # a repository storage to use. - Gitlab::CurrentSettings.expire_current_application_settings - Gitlab::CurrentSettings.pick_repository_storage - end - end - def valid_repo? repository.exists? rescue diff --git a/app/models/concerns/nullify_if_blank.rb b/app/models/concerns/nullify_if_blank.rb new file mode 100644 index 00000000000..5a5cc51509b --- /dev/null +++ b/app/models/concerns/nullify_if_blank.rb @@ -0,0 +1,48 @@ +# frozen_string_literal: true + +# Helper that sets attributes to nil prior to validation if they +# are blank (are false, empty or contain only whitespace), to avoid +# unnecessarily persisting empty strings. +# +# Model usage: +# +# class User < ApplicationRecord +# include NullifyIfBlank +# +# nullify_if_blank :name, :email +# end +# +# +# Test usage: +# +# RSpec.describe User do +# it { is_expected.to nullify_if_blank(:name) } +# it { is_expected.to nullify_if_blank(:email) } +# end +# +module NullifyIfBlank + extend ActiveSupport::Concern + + class_methods do + def nullify_if_blank(*attributes) + self.attributes_to_nullify += attributes + end + end + + included do + class_attribute :attributes_to_nullify, + instance_accessor: false, + instance_predicate: false, + default: Set.new + + before_validation :nullify_blank_attributes + end + + private + + def nullify_blank_attributes + self.class.attributes_to_nullify.each do |attribute| + assign_attributes(attribute => nil) if read_attribute(attribute).blank? + end + end +end diff --git a/app/models/concerns/optimized_issuable_label_filter.rb b/app/models/concerns/optimized_issuable_label_filter.rb index 82055822cfb..c7af841e450 100644 --- a/app/models/concerns/optimized_issuable_label_filter.rb +++ b/app/models/concerns/optimized_issuable_label_filter.rb @@ -13,7 +13,7 @@ module OptimizedIssuableLabelFilter def by_label(items) return items unless params.labels? - return super if Feature.disabled?(:optimized_issuable_label_filter) + return super if Feature.disabled?(:optimized_issuable_label_filter, default_enabled: :yaml) target_model = items.model @@ -29,7 +29,7 @@ module OptimizedIssuableLabelFilter # Taken from IssuableFinder def count_by_state return super if root_namespace.nil? - return super if Feature.disabled?(:optimized_issuable_label_filter) + return super if Feature.disabled?(:optimized_issuable_label_filter, default_enabled: :yaml) count_params = params.merge(state: nil, sort: nil, force_cte: true) finder = self.class.new(current_user, count_params) diff --git a/app/models/concerns/packages/debian/architecture.rb b/app/models/concerns/packages/debian/architecture.rb index 4aa633e0357..760ebb49980 100644 --- a/app/models/concerns/packages/debian/architecture.rb +++ b/app/models/concerns/packages/debian/architecture.rb @@ -7,6 +7,12 @@ module Packages included do belongs_to :distribution, class_name: "Packages::Debian::#{container_type.capitalize}Distribution", inverse_of: :architectures + # files must be destroyed by ruby code in order to properly remove carrierwave uploads + has_many :files, + class_name: "Packages::Debian::#{container_type.capitalize}ComponentFile", + foreign_key: :architecture_id, + inverse_of: :architecture, + dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent validates :distribution, presence: true diff --git a/app/models/concerns/packages/debian/component.rb b/app/models/concerns/packages/debian/component.rb new file mode 100644 index 00000000000..7b342c7b684 --- /dev/null +++ b/app/models/concerns/packages/debian/component.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Packages + module Debian + module Component + extend ActiveSupport::Concern + + included do + belongs_to :distribution, class_name: "Packages::Debian::#{container_type.capitalize}Distribution", inverse_of: :components + # files must be destroyed by ruby code in order to properly remove carrierwave uploads + has_many :files, + class_name: "Packages::Debian::#{container_type.capitalize}ComponentFile", + foreign_key: :component_id, + inverse_of: :component, + dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + + validates :distribution, + presence: true + + validates :name, + presence: true, + length: { maximum: 255 }, + uniqueness: { scope: %i[distribution_id] }, + format: { with: Gitlab::Regex.debian_component_regex } + + scope :with_distribution, ->(distribution) { where(distribution: distribution) } + scope :with_name, ->(name) { where(name: name) } + end + end + end +end diff --git a/app/models/concerns/packages/debian/component_file.rb b/app/models/concerns/packages/debian/component_file.rb new file mode 100644 index 00000000000..3cc2c291e96 --- /dev/null +++ b/app/models/concerns/packages/debian/component_file.rb @@ -0,0 +1,101 @@ +# frozen_string_literal: true + +module Packages + module Debian + module ComponentFile + extend ActiveSupport::Concern + + included do + include Sortable + include FileStoreMounter + + def self.container_foreign_key + "#{container_type}_id".to_sym + end + + def self.distribution_class + "::Packages::Debian::#{container_type.capitalize}Distribution".constantize + end + + belongs_to :component, class_name: "Packages::Debian::#{container_type.capitalize}Component", inverse_of: :files + belongs_to :architecture, class_name: "Packages::Debian::#{container_type.capitalize}Architecture", inverse_of: :files, optional: true + + enum file_type: { packages: 1, source: 2, di_packages: 3 } + enum compression_type: { gz: 1, bz2: 2, xz: 3 } + + validates :component, presence: true + validates :file_type, presence: true + validates :architecture, presence: true, unless: :source? + validates :architecture, absence: true, if: :source? + validates :file, length: { minimum: 0, allow_nil: false } + validates :size, presence: true + validates :file_store, presence: true + validates :file_md5, presence: true + validates :file_sha256, presence: true + + scope :with_container, ->(container) do + joins(component: :distribution) + .where("packages_debian_#{container_type}_distributions" => { container_foreign_key => container.id }) + end + + scope :with_codename_or_suite, ->(codename_or_suite) do + joins(component: :distribution) + .merge(distribution_class.with_codename_or_suite(codename_or_suite)) + end + + scope :with_component_name, ->(component_name) do + joins(:component) + .where("packages_debian_#{container_type}_components" => { name: component_name }) + end + + scope :with_file_type, ->(file_type) { where(file_type: file_type) } + + scope :with_architecture_name, ->(architecture_name) do + left_outer_joins(:architecture) + .where("packages_debian_#{container_type}_architectures" => { name: architecture_name }) + end + + scope :with_compression_type, ->(compression_type) { where(compression_type: compression_type) } + scope :with_file_sha256, ->(file_sha256) { where(file_sha256: file_sha256) } + + scope :preload_distribution, -> { includes(component: :distribution) } + + mount_file_store_uploader Packages::Debian::ComponentFileUploader + + before_validation :update_size_from_file + + def file_name + case file_type + when 'di_packages' + 'Packages' + else + file_type.capitalize + end + end + + def relative_path + case file_type + when 'packages' + "#{component.name}/binary-#{architecture.name}/#{file_name}#{extension}" + when 'source' + "#{component.name}/source/#{file_name}#{extension}" + when 'di_packages' + "#{component.name}/debian-installer/binary-#{architecture.name}/#{file_name}#{extension}" + end + end + + private + + def extension + return '' unless compression_type + + ".#{compression_type}" + end + + def update_size_from_file + self.size ||= file.size + end + end + end + end +end diff --git a/app/models/concerns/packages/debian/distribution.rb b/app/models/concerns/packages/debian/distribution.rb index 285d293c9ee..08fb9ccf3ea 100644 --- a/app/models/concerns/packages/debian/distribution.rb +++ b/app/models/concerns/packages/debian/distribution.rb @@ -18,6 +18,16 @@ module Packages belongs_to container_type belongs_to :creator, class_name: 'User' + # component_files must be destroyed by ruby code in order to properly remove carrierwave uploads + has_many :components, + class_name: "Packages::Debian::#{container_type.capitalize}Component", + foreign_key: :distribution_id, + inverse_of: :distribution, + dependent: :destroy # rubocop:disable Cop/ActiveRecordDependent + has_many :component_files, + through: :components, + source: :files, + class_name: "Packages::Debian::#{container_type.capitalize}ComponentFile" has_many :architectures, class_name: "Packages::Debian::#{container_type.capitalize}Architecture", foreign_key: :distribution_id, diff --git a/app/models/concerns/protected_ref.rb b/app/models/concerns/protected_ref.rb index 65195a8d5aa..cf23a27244c 100644 --- a/app/models/concerns/protected_ref.rb +++ b/app/models/concerns/protected_ref.rb @@ -40,20 +40,26 @@ module ProtectedRef end def protected_ref_accessible_to?(ref, user, project:, action:, protected_refs: nil) - access_levels_for_ref(ref, action: action, protected_refs: protected_refs).any? do |access_level| + all_matching_rules_allow?(ref, action: action, protected_refs: protected_refs) do |access_level| access_level.check_access(user) end end def developers_can?(action, ref, protected_refs: nil) - access_levels_for_ref(ref, action: action, protected_refs: protected_refs).any? do |access_level| + all_matching_rules_allow?(ref, action: action, protected_refs: protected_refs) do |access_level| access_level.access_level == Gitlab::Access::DEVELOPER end end - def access_levels_for_ref(ref, action:, protected_refs: nil) - self.matching(ref, protected_refs: protected_refs) - .flat_map(&:"#{action}_access_levels") + def all_matching_rules_allow?(ref, action:, protected_refs: nil, &block) + access_levels_groups = + self.matching(ref, protected_refs: protected_refs).map(&:"#{action}_access_levels") + + return false if access_levels_groups.blank? + + access_levels_groups.all? do |access_levels| + access_levels.any?(&block) + end end # Returns all protected refs that match the given ref name. diff --git a/app/models/concerns/repositories/can_housekeep_repository.rb b/app/models/concerns/repositories/can_housekeep_repository.rb index 2b79851a07c..946f82c5f36 100644 --- a/app/models/concerns/repositories/can_housekeep_repository.rb +++ b/app/models/concerns/repositories/can_housekeep_repository.rb @@ -16,6 +16,10 @@ module Repositories Gitlab::Redis::SharedState.with { |redis| redis.del(pushes_since_gc_redis_shared_state_key) } end + def git_garbage_collect_worker_klass + raise NotImplementedError + end + private def pushes_since_gc_redis_shared_state_key diff --git a/app/models/concerns/repository_storage_movable.rb b/app/models/concerns/repository_storage_movable.rb index a45b4626628..8607f0d94f4 100644 --- a/app/models/concerns/repository_storage_movable.rb +++ b/app/models/concerns/repository_storage_movable.rb @@ -20,7 +20,7 @@ module RepositoryStorageMovable validate :container_repository_writable, on: :create default_value_for(:destination_storage_name, allows_nil: false) do - pick_repository_storage + Repository.pick_storage_shard end state_machine initial: :initial do @@ -68,6 +68,18 @@ module RepositoryStorageMovable storage_move.update_repository_storage(storage_move.destination_storage_name) end + after_transition started: :replicated do |storage_move| + # We have several scripts in place that replicate some statistics information + # to other databases. Some of them depend on the updated_at column + # to identify the models they need to extract. + # + # If we don't update the `updated_at` of the container after a repository storage move, + # the scripts won't know that they need to sync them. + # + # See https://gitlab.com/gitlab-data/analytics/-/issues/7868 + storage_move.container.touch + end + before_transition started: :failed do |storage_move| storage_move.container.set_repository_writable! end @@ -82,16 +94,6 @@ module RepositoryStorageMovable end end - class_methods do - private - - def pick_repository_storage - container_klass = reflect_on_association(:container).class_name.constantize - - container_klass.pick_repository_storage - end - end - # Projects, snippets, and group wikis has different db structure. In projects, # we need to update some columns in this step, but we don't with the other resources. # diff --git a/app/models/concerns/spammable.rb b/app/models/concerns/spammable.rb index 9cd1a22b203..2daea388939 100644 --- a/app/models/concerns/spammable.rb +++ b/app/models/concerns/spammable.rb @@ -45,6 +45,17 @@ module Spammable self.needs_recaptcha = true end + ## + # Indicates if a recaptcha should be rendered before allowing this model to be saved. + # + def render_recaptcha? + return false unless Gitlab::Recaptcha.enabled? + + return false if self.errors.count > 1 # captcha should not be rendered if are still other errors + + self.needs_recaptcha? + end + def spam! self.spam = true end diff --git a/app/models/concerns/suppress_composite_primary_key_warning.rb b/app/models/concerns/suppress_composite_primary_key_warning.rb new file mode 100644 index 00000000000..32634e7bc72 --- /dev/null +++ b/app/models/concerns/suppress_composite_primary_key_warning.rb @@ -0,0 +1,19 @@ +# frozen_string_literal: true + +# When extended, silences this warning below: +# WARNING: Active Record does not support composite primary key. +# +# project_authorizations has composite primary key. Composite primary key is ignored. +# +# See https://gitlab.com/gitlab-org/gitlab/-/issues/292909 +module SuppressCompositePrimaryKeyWarning + extend ActiveSupport::Concern + + private + + def suppress_composite_primary_key(pk) + silence_warnings do + super + end + end +end diff --git a/app/models/concerns/token_authenticatable_strategies/encrypted.rb b/app/models/concerns/token_authenticatable_strategies/encrypted.rb index 4728cb658dc..672402ee4d6 100644 --- a/app/models/concerns/token_authenticatable_strategies/encrypted.rb +++ b/app/models/concerns/token_authenticatable_strategies/encrypted.rb @@ -85,10 +85,18 @@ module TokenAuthenticatableStrategies end def find_by_encrypted_token(token, unscoped) - encrypted_value = Gitlab::CryptoHelper.aes256_gcm_encrypt(token) + nonce = Feature.enabled?(:dynamic_nonce_creation) ? find_hashed_iv(token) : Gitlab::CryptoHelper::AES256_GCM_IV_STATIC + encrypted_value = Gitlab::CryptoHelper.aes256_gcm_encrypt(token, nonce: nonce) + relation(unscoped).find_by(encrypted_field => encrypted_value) end + def find_hashed_iv(token) + token_record = TokenWithIv.find_by_plaintext_token(token) + + token_record&.iv || Gitlab::CryptoHelper::AES256_GCM_IV_STATIC + end + def insecure_strategy @insecure_strategy ||= TokenAuthenticatableStrategies::Insecure .new(klass, token_field, options) diff --git a/app/models/concerns/triggerable_hooks.rb b/app/models/concerns/triggerable_hooks.rb index 473b430bb04..db5df6c2c9f 100644 --- a/app/models/concerns/triggerable_hooks.rb +++ b/app/models/concerns/triggerable_hooks.rb @@ -16,7 +16,8 @@ module TriggerableHooks deployment_hooks: :deployment_events, feature_flag_hooks: :feature_flag_events, release_hooks: :releases_events, - member_hooks: :member_events + member_hooks: :member_events, + subgroup_hooks: :subgroup_events }.freeze extend ActiveSupport::Concern |