discourse/app/controllers/admin/email_controller.rb
Kelv b4a89ea610
FEAT: add cc addresses and post_id to sent email logs (#25014)
* add cc addresses and post_id to sent email logs
* sort cc addresses by email address filter value and collapse additional addreses into tooltip
* add slice helper for use in ember tempaltes
2024-01-03 09:27:25 +08:00

299 lines
9.0 KiB
Ruby

# frozen_string_literal: true
class Admin::EmailController < Admin::AdminController
def index
data = { delivery_method: delivery_method, settings: delivery_settings }
render_json_dump(data)
end
def test
params.require(:email_address)
begin
message = TestMailer.send_test(params[:email_address])
Email::Sender.new(message, :test_message).send
render json: { sent_test_email_message: I18n.t("admin.email.sent_test") }
rescue => e
render json: { errors: [e.message] }, status: 422
end
end
def sent
email_logs = EmailLog.joins(<<~SQL)
LEFT JOIN post_reply_keys
ON post_reply_keys.post_id = email_logs.post_id
AND post_reply_keys.user_id = email_logs.user_id
SQL
email_logs = filter_logs(email_logs, params, include_ccs: params[:type] == "group_smtp")
if (reply_key = params[:reply_key]).present?
email_logs =
if reply_key.length == 32
email_logs.where("post_reply_keys.reply_key = ?", reply_key)
else
email_logs.where(
"replace(post_reply_keys.reply_key::VARCHAR, '-', '') ILIKE ?",
"%#{reply_key}%",
)
end
end
email_logs = email_logs.to_a
tuples = email_logs.map { |email_log| [email_log.post_id, email_log.user_id] }
reply_keys = {}
if tuples.present?
PostReplyKey
.where("(post_id,user_id) IN (#{(["(?)"] * tuples.size).join(", ")})", *tuples)
.pluck(:post_id, :user_id, "reply_key::text")
.each { |post_id, user_id, key| reply_keys[[post_id, user_id]] = key }
end
render_serialized(email_logs, EmailLogSerializer, reply_keys: reply_keys)
end
def skipped
skipped_email_logs = filter_logs(SkippedEmailLog, params)
render_serialized(skipped_email_logs, SkippedEmailLogSerializer)
end
def bounced
email_logs = filter_logs(EmailLog.bounced, params)
render_serialized(email_logs, EmailLogSerializer)
end
def received
incoming_emails = filter_incoming_emails(IncomingEmail, params)
render_serialized(incoming_emails, IncomingEmailSerializer)
end
def rejected
incoming_emails = filter_incoming_emails(IncomingEmail.errored, params)
render_serialized(incoming_emails, IncomingEmailSerializer)
end
def preview_digest
params.require(:last_seen_at)
params.require(:username)
user = User.find_by_username(params[:username])
raise Discourse::InvalidParameters unless user
renderer = Email::Renderer.new(UserNotifications.digest(user, since: params[:last_seen_at]))
render json: MultiJson.dump(html_content: renderer.html, text_content: renderer.text)
end
def advanced_test
params.require(:email)
receiver = Email::Receiver.new(params["email"])
text, elided, format = receiver.select_body
render json: success_json.merge!(text: text, elided: elided, format: format)
end
def send_digest
params.require(:last_seen_at)
params.require(:username)
params.require(:email)
user = User.find_by_username(params[:username])
message, skip_reason =
UserNotifications.public_send(:digest, user, since: params[:last_seen_at])
if message
message.to = params[:email]
begin
Email::Sender.new(message, :digest).send
render json: success_json
rescue => e
render json: { errors: [e.message] }, status: 422
end
else
render json: { errors: skip_reason }
end
end
def smtp_should_reject
params.require(:from)
params.require(:to)
# These strings aren't localized; they are sent to an anonymous SMTP user.
if !User.with_email(Email.downcase(params[:from])).exists? && !SiteSetting.enable_staged_users
render json: {
reject: true,
reason: "Mail from your address is not accepted. Do you have an account here?",
}
elsif Email::Receiver.check_address(Email.downcase(params[:to])).nil?
render json: {
reject: true,
reason:
"Mail to this address is not accepted. Check the address and try to send again?",
}
else
render json: { reject: false }
end
end
def handle_mail
deprecated_email_param_used = false
if params[:email_encoded].present?
email_raw = Base64.strict_decode64(params[:email_encoded])
elsif params[:email].present?
deprecated_email_param_used = true
email_raw = params[:email]
else
raise ActionController::ParameterMissing.new("email_encoded or email")
end
retry_count = 0
begin
Jobs.enqueue(
:process_email,
mail: email_raw,
retry_on_rate_limit: true,
source: "handle_mail",
)
rescue JSON::GeneratorError, Encoding::UndefinedConversionError => e
if retry_count == 0
email_raw = email_raw.force_encoding("iso-8859-1").encode("UTF-8")
retry_count += 1
retry
else
raise e
end
end
if deprecated_email_param_used
warning =
"warning: the email parameter is deprecated. all POST requests to this route should be sent with a base64 strict encoded email_encoded parameter instead. email has been received and is queued for processing"
Discourse.deprecate(warning, drop_from: "3.3.0")
render plain: warning
else
render plain: "email has been received and is queued for processing"
end
end
def incoming
params.require(:id)
incoming_email = IncomingEmail.find(params[:id].to_i)
serializer = IncomingEmailDetailsSerializer.new(incoming_email, root: false)
render_json_dump(serializer)
end
def incoming_from_bounced
params.require(:id)
begin
email_log = EmailLog.find_by(id: params[:id].to_i, bounced: true)
raise Discourse::InvalidParameters if email_log&.bounce_key.blank?
if Email::Sender.bounceable_reply_address?
bounced_to_address = Email::Sender.bounce_address(email_log.bounce_key)
incoming_email = IncomingEmail.find_by(to_addresses: bounced_to_address)
end
if incoming_email.nil?
email_local_part, email_domain = SiteSetting.notification_email.split("@")
bounced_to_address = "#{email_local_part}+verp-#{email_log.bounce_key}@#{email_domain}"
incoming_email = IncomingEmail.find_by(to_addresses: bounced_to_address)
end
# Temporary fix until all old format of emails has been purged via lib/email/cleaner.rb
if incoming_email.nil?
email_local_part, email_domain = SiteSetting.reply_by_email_address.split("@")
subdomain, root_domain, extension = email_domain&.split(".")
bounced_to_address = "#{subdomain}+verp-#{email_log.bounce_key}@#{root_domain}.#{extension}"
incoming_email = IncomingEmail.find_by(to_addresses: bounced_to_address)
end
raise Discourse::NotFound if incoming_email.nil?
serializer = IncomingEmailDetailsSerializer.new(incoming_email, root: false)
render_json_dump(serializer)
rescue => e
render json: { errors: [e.message] }, status: 404
end
end
private
def filter_logs(logs, params, include_ccs: false)
table_name = logs.table_name
logs =
logs
.includes(:user, post: :topic)
.references(:user)
.order(created_at: :desc)
.offset(params[:offset] || 0)
.limit(50)
logs = logs.where("users.username ILIKE ?", "%#{params[:user]}%") if params[:user].present?
if params[:address].present?
query = "#{table_name}.to_address ILIKE :address"
query += " OR #{table_name}.cc_addresses ILIKE :address" if include_ccs
logs = logs.where(query, { address: "%#{params[:address]}%" })
end
logs = logs.where("#{table_name}.email_type ILIKE ?", "%#{params[:type]}%") if params[
:type
].present?
if table_name == "email_logs" && params[:smtp_transaction_response].present?
logs =
logs.where(
"#{table_name}.smtp_transaction_response ILIKE ?",
"%#{params[:smtp_transaction_response]}%",
)
end
logs
end
def filter_incoming_emails(incoming_emails, params)
incoming_emails =
incoming_emails
.includes(:user, post: :topic)
.order(created_at: :desc)
.offset(params[:offset] || 0)
.limit(50)
incoming_emails = incoming_emails.where("from_address ILIKE ?", "%#{params[:from]}%") if params[
:from
].present?
incoming_emails =
incoming_emails.where(
"to_addresses ILIKE :to OR cc_addresses ILIKE :to",
to: "%#{params[:to]}%",
) if params[:to].present?
incoming_emails = incoming_emails.where("subject ILIKE ?", "%#{params[:subject]}%") if params[
:subject
].present?
incoming_emails = incoming_emails.where("error ILIKE ?", "%#{params[:error]}%") if params[
:error
].present?
incoming_emails
end
def delivery_settings
action_mailer_settings.reject { |k, _| k == :password }.map { |k, v| { name: k, value: v } }
end
def delivery_method
ActionMailer::Base.delivery_method
end
def action_mailer_settings
ActionMailer::Base.public_send "#{delivery_method}_settings"
end
end