1# frozen_string_literal: true 2 3module Gitlab 4 module Email 5 module Handler 6 module ReplyProcessing 7 # rubocop:disable Gitlab/ModuleWithInstanceVariables 8 def project 9 return @project if instance_variable_defined?(:@project) 10 11 if project_id 12 @project = Project.find_by_id(project_id) 13 @project = nil unless valid_project_slug?(@project) 14 else 15 @project = Project.find_by_full_path(project_path) 16 end 17 18 @project 19 end 20 # rubocop:enable Gitlab/ModuleWithInstanceVariables 21 22 private 23 24 attr_reader :project_id, :project_slug, :project_path, :incoming_email_token 25 26 def author 27 raise NotImplementedError 28 end 29 30 def message 31 @message ||= process_message 32 end 33 34 def message_including_reply 35 @message_with_reply ||= process_message(trim_reply: false) 36 end 37 38 def message_including_reply_or_only_quotes 39 @message_including_reply_or_only_quotes ||= process_message(trim_reply: false, allow_only_quotes: true) 40 end 41 42 def message_with_appended_reply 43 @message_with_appended_reply ||= process_message(append_reply: true) 44 end 45 46 def process_message(**kwargs) 47 message, stripped_text = ReplyParser.new(mail, **kwargs).execute 48 message = message.strip 49 50 message_with_attachments = add_attachments(message) 51 # Support bot is specifically forbidden from using slash commands. 52 message = strip_quick_actions(message_with_attachments) 53 return message unless kwargs[:append_reply] 54 55 append_reply(message, stripped_text) 56 end 57 58 def add_attachments(reply) 59 attachments = Email::AttachmentUploader.new(mail).execute(**upload_params) 60 61 reply + attachments.map do |link| 62 "\n\n#{link[:markdown]}" 63 end.join 64 end 65 66 def upload_params 67 { 68 upload_parent: project, 69 uploader_class: FileUploader 70 } 71 end 72 73 def validate_permission!(permission) 74 raise UserNotFoundError unless author 75 raise UserBlockedError if author.blocked? 76 77 if project 78 raise ProjectNotFound unless author.can?(:read_project, project) 79 end 80 81 raise UserNotAuthorizedError unless author.can?(permission, try(:noteable) || project) 82 end 83 84 def verify_record!(record:, invalid_exception:, record_name:) 85 return if record.persisted? 86 return if record.errors.key?(:commands_only) 87 88 error_title = "The #{record_name} could not be created for the following reasons:" 89 90 msg = error_title + record.errors.full_messages.map do |error| 91 "\n\n- #{error}" 92 end.join 93 94 raise invalid_exception, msg 95 end 96 97 def valid_project_slug?(found_project) 98 return false unless found_project 99 100 project_slug == found_project.full_path_slug 101 end 102 103 def strip_quick_actions(content) 104 return content unless author.support_bot? 105 106 quick_actions_extractor.redact_commands(content) 107 end 108 109 def quick_actions_extractor 110 command_definitions = ::QuickActions::InterpretService.command_definitions 111 ::Gitlab::QuickActions::Extractor.new(command_definitions) 112 end 113 114 def append_reply(message, reply) 115 return message if message.blank? || reply.blank? 116 117 # Do not append if message only contains slash commands 118 body, _commands = quick_actions_extractor.extract_commands(message) 119 return message if body.empty? 120 121 message + "\n\n<details><summary>...</summary>\n\n#{reply}\n\n</details>" 122 end 123 end 124 end 125 end 126end 127 128Gitlab::Email::Handler::ReplyProcessing.prepend_mod_with('Gitlab::Email::Handler::ReplyProcessing') 129