summaryrefslogtreecommitdiff
path: root/lib/gitlab/checks/diff_check.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/checks/diff_check.rb')
-rw-r--r--lib/gitlab/checks/diff_check.rb99
1 files changed, 99 insertions, 0 deletions
diff --git a/lib/gitlab/checks/diff_check.rb b/lib/gitlab/checks/diff_check.rb
new file mode 100644
index 00000000000..49d361fcef7
--- /dev/null
+++ b/lib/gitlab/checks/diff_check.rb
@@ -0,0 +1,99 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Checks
+ class DiffCheck < BaseChecker
+ include Gitlab::Utils::StrongMemoize
+
+ LOG_MESSAGES = {
+ validate_file_paths: "Validating diffs' file paths...",
+ diff_content_check: "Validating diff contents..."
+ }.freeze
+
+ def validate!
+ return unless should_run_diff_validations?
+ return if commits.empty?
+ return unless uses_raw_delta_validations?
+
+ file_paths = []
+ process_raw_deltas do |diff|
+ file_paths << (diff.new_path || diff.old_path)
+
+ validate_diff(diff)
+ end
+
+ validate_file_paths(file_paths)
+ end
+
+ private
+
+ def should_run_diff_validations?
+ newrev && oldrev && !deletion? && validate_lfs_file_locks?
+ end
+
+ def validate_lfs_file_locks?
+ strong_memoize(:validate_lfs_file_locks) do
+ project.lfs_enabled? && project.any_lfs_file_locks?
+ end
+ end
+
+ def uses_raw_delta_validations?
+ validations_for_diff.present? || path_validations.present?
+ end
+
+ def validate_diff(diff)
+ validations_for_diff.each do |validation|
+ if error = validation.call(diff)
+ raise ::Gitlab::GitAccess::UnauthorizedError, error
+ end
+ end
+ end
+
+ # Method overwritten in EE to inject custom validations
+ def validations_for_diff
+ []
+ end
+
+ def path_validations
+ validate_lfs_file_locks? ? [lfs_file_locks_validation] : []
+ end
+
+ def process_raw_deltas
+ logger.log_timed(LOG_MESSAGES[:diff_content_check]) do
+ # n+1: https://gitlab.com/gitlab-org/gitlab-ee/issues/3593
+ ::Gitlab::GitalyClient.allow_n_plus_1_calls do
+ commits.each do |commit|
+ logger.check_timeout_reached
+
+ commit.raw_deltas.each do |diff|
+ yield(diff)
+ end
+ end
+ end
+ end
+ end
+
+ def validate_file_paths(file_paths)
+ logger.log_timed(LOG_MESSAGES[__method__]) do
+ path_validations.each do |validation|
+ if error = validation.call(file_paths)
+ raise ::Gitlab::GitAccess::UnauthorizedError, error
+ end
+ end
+ end
+ end
+
+ # rubocop: disable CodeReuse/ActiveRecord
+ def lfs_file_locks_validation
+ lambda do |paths|
+ lfs_lock = project.lfs_file_locks.where(path: paths).where.not(user_id: user_access.user.id).take
+
+ if lfs_lock
+ return "The path '#{lfs_lock.path}' is locked in Git LFS by #{lfs_lock.user.name}"
+ end
+ end
+ end
+ # rubocop: enable CodeReuse/ActiveRecord
+ end
+ end
+end