summaryrefslogtreecommitdiff
path: root/lib/gitlab/search/abuse_detection.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/gitlab/search/abuse_detection.rb')
-rw-r--r--lib/gitlab/search/abuse_detection.rb81
1 files changed, 81 insertions, 0 deletions
diff --git a/lib/gitlab/search/abuse_detection.rb b/lib/gitlab/search/abuse_detection.rb
new file mode 100644
index 00000000000..7b5377bce88
--- /dev/null
+++ b/lib/gitlab/search/abuse_detection.rb
@@ -0,0 +1,81 @@
+# frozen_string_literal: true
+
+module Gitlab
+ module Search
+ class AbuseDetection
+ include ActiveModel::Validations
+ include AbuseValidators
+
+ ABUSIVE_TERM_SIZE = 100
+ ALLOWED_CHARS_REGEX = %r{\A[[:alnum:]_\-\/\.!]+\z}.freeze
+ MINIMUM_SEARCH_CHARS = 2
+
+ ALLOWED_SCOPES = %w(
+ blobs
+ code
+ commits
+ epics
+ issues
+ merge_requests
+ milestones
+ notes
+ projects
+ snippet_titles
+ users
+ wiki_blobs
+ ).freeze
+
+ READABLE_PARAMS = %i(
+ group_id
+ project_id
+ project_ref
+ query_string
+ repository_ref
+ scope
+ ).freeze
+
+ STOP_WORDS = %w(
+ a an and are as at be but by for if in into is it no not of on or such that the their then there these they this to was will with
+ ).freeze
+
+ validates :project_id, :group_id,
+ numericality: { only_integer: true, message: "abusive ID detected" }, allow_blank: true
+
+ validates :scope, inclusion: { in: ALLOWED_SCOPES, message: 'abusive scope detected' }, allow_blank: true
+
+ validates :repository_ref, :project_ref,
+ format: { with: ALLOWED_CHARS_REGEX, message: "abusive characters detected" }, allow_blank: true
+
+ validates :query_string,
+ exclusion: { in: STOP_WORDS, message: 'stopword only abusive search detected' }, allow_blank: true
+
+ validates :query_string,
+ length: { minimum: MINIMUM_SEARCH_CHARS, message: 'abusive tiny search detected' }, unless: :skip_tiny_search_validation?, allow_blank: true
+
+ validates :query_string,
+ no_abusive_term_length: { maximum: ABUSIVE_TERM_SIZE, maximum_for_url: ABUSIVE_TERM_SIZE * 2 }
+
+ validates :query_string, :repository_ref, :project_ref, no_abusive_coercion_from_string: true
+
+ attr_reader(*READABLE_PARAMS)
+
+ def initialize(params)
+ READABLE_PARAMS.each { |p| instance_variable_set("@#{p}", params[p]) }
+ end
+
+ private
+
+ def skip_tiny_search_validation?
+ wildcard_search? || stop_word_search?
+ end
+
+ def wildcard_search?
+ query_string == '*'
+ end
+
+ def stop_word_search?
+ STOP_WORDS.include? query_string
+ end
+ end
+ end
+end