mirror of
https://github.com/discourse/discourse.git
synced 2024-11-25 09:42:07 +08:00
enqueue spam/dmarc failing emails instead of hiding (#8674)
* enqueue spam/dmarc failing emails instead of hiding * add translations for dmarc/spam enqueued reasons * unescape quote * if email_in_authserv_id is blank return gray for all emails
This commit is contained in:
parent
411512c0e3
commit
8883cca373
|
@ -2406,6 +2406,7 @@ en:
|
||||||
fixed_primary_email: "Fixed primary email for staged user"
|
fixed_primary_email: "Fixed primary email for staged user"
|
||||||
same_ip_address: "Same IP address (%{ip_address}) as other users"
|
same_ip_address: "Same IP address (%{ip_address}) as other users"
|
||||||
inactive_user: "Inactive user"
|
inactive_user: "Inactive user"
|
||||||
|
email_in_spam_header: "User's first email was flagged as spam"
|
||||||
|
|
||||||
reviewables_reminder:
|
reviewables_reminder:
|
||||||
submitted:
|
submitted:
|
||||||
|
@ -4661,6 +4662,8 @@ en:
|
||||||
category: "Posts in this category require manual approval by staff. See the category settings."
|
category: "Posts in this category require manual approval by staff. See the category settings."
|
||||||
must_approve_users: "All new users must be approved by staff. See `must_approve_users`."
|
must_approve_users: "All new users must be approved by staff. See `must_approve_users`."
|
||||||
invite_only: "All new users should be invited. See `invite_only`."
|
invite_only: "All new users should be invited. See `invite_only`."
|
||||||
|
email_auth_res_enqueue: "This email failed a DMARC check, it most likely isn't from whom it seems to be from. Check the raw email headers for more information."
|
||||||
|
email_spam: "This email was flagged as spam by the header defined in `email_in_spam_header`."
|
||||||
|
|
||||||
actions:
|
actions:
|
||||||
agree:
|
agree:
|
||||||
|
|
|
@ -2,8 +2,6 @@
|
||||||
|
|
||||||
module Email
|
module Email
|
||||||
class AuthenticationResults
|
class AuthenticationResults
|
||||||
attr_reader :results
|
|
||||||
|
|
||||||
VERDICT = Enum.new(
|
VERDICT = Enum.new(
|
||||||
:gray,
|
:gray,
|
||||||
:pass,
|
:pass,
|
||||||
|
@ -12,11 +10,16 @@ module Email
|
||||||
)
|
)
|
||||||
|
|
||||||
def initialize(headers)
|
def initialize(headers)
|
||||||
authserv_id = SiteSetting.email_in_authserv_id
|
@authserv_id = SiteSetting.email_in_authserv_id
|
||||||
@results = Array(headers).map do |header|
|
@headers = headers
|
||||||
|
@verdict = :gray if @authserv_id.blank?
|
||||||
|
end
|
||||||
|
|
||||||
|
def results
|
||||||
|
@results ||= Array(@headers).map do |header|
|
||||||
parse_header(header.to_s)
|
parse_header(header.to_s)
|
||||||
end.filter do |result|
|
end.filter do |result|
|
||||||
authserv_id.blank? || authserv_id == result[:authserv_id]
|
@authserv_id.blank? || @authserv_id == result[:authserv_id]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -32,7 +35,7 @@ module Email
|
||||||
|
|
||||||
def calc_action
|
def calc_action
|
||||||
if verdict == :fail
|
if verdict == :fail
|
||||||
:hide
|
:enqueue
|
||||||
else
|
else
|
||||||
:accept
|
:accept
|
||||||
end
|
end
|
||||||
|
@ -44,7 +47,7 @@ module Email
|
||||||
|
|
||||||
def calc_dmarc
|
def calc_dmarc
|
||||||
verdict = VERDICT[:gray]
|
verdict = VERDICT[:gray]
|
||||||
@results.each do |result|
|
results.each do |result|
|
||||||
result[:resinfo].each do |resinfo|
|
result[:resinfo].each do |resinfo|
|
||||||
if resinfo[:method] == "dmarc"
|
if resinfo[:method] == "dmarc"
|
||||||
v = VERDICT[resinfo[:result].to_sym].to_i
|
v = VERDICT[resinfo[:result].to_sym].to_i
|
||||||
|
|
|
@ -160,7 +160,6 @@ module Email
|
||||||
create_reply(user: user,
|
create_reply(user: user,
|
||||||
raw: body,
|
raw: body,
|
||||||
elided: elided,
|
elided: elided,
|
||||||
hidden_reason_id: hidden_reason_id,
|
|
||||||
post: post,
|
post: post,
|
||||||
topic: post.topic,
|
topic: post.topic,
|
||||||
skip_validations: user.staged?,
|
skip_validations: user.staged?,
|
||||||
|
@ -170,7 +169,7 @@ module Email
|
||||||
|
|
||||||
destinations.each do |destination|
|
destinations.each do |destination|
|
||||||
begin
|
begin
|
||||||
return process_destination(destination, user, body, elided, hidden_reason_id)
|
return process_destination(destination, user, body, elided)
|
||||||
rescue => e
|
rescue => e
|
||||||
first_exception ||= e
|
first_exception ||= e
|
||||||
end
|
end
|
||||||
|
@ -195,17 +194,6 @@ module Email
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def hidden_reason_id
|
|
||||||
@hidden_reason_id ||=
|
|
||||||
if is_spam?
|
|
||||||
Post.hidden_reasons[:email_spam_header_found]
|
|
||||||
elsif !sent_to_mailinglist_mirror? && auth_res_action == :hide
|
|
||||||
Post.hidden_reasons[:email_authentication_result_header]
|
|
||||||
else
|
|
||||||
nil
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def log_and_validate_user(user)
|
def log_and_validate_user(user)
|
||||||
@incoming_email.update_columns(user_id: user.id)
|
@incoming_email.update_columns(user_id: user.id)
|
||||||
|
|
||||||
|
@ -242,7 +230,6 @@ module Email
|
||||||
create_reply(user: @from_user,
|
create_reply(user: @from_user,
|
||||||
raw: body,
|
raw: body,
|
||||||
elided: elided,
|
elided: elided,
|
||||||
hidden_reason_id: hidden_reason_id,
|
|
||||||
post: post,
|
post: post,
|
||||||
topic: topic,
|
topic: topic,
|
||||||
skip_validations: true,
|
skip_validations: true,
|
||||||
|
@ -667,7 +654,7 @@ module Email
|
||||||
nil
|
nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_destination(destination, user, body, elided, hidden_reason_id)
|
def process_destination(destination, user, body, elided)
|
||||||
return if SiteSetting.forwarded_emails_behaviour != "hide" &&
|
return if SiteSetting.forwarded_emails_behaviour != "hide" &&
|
||||||
has_been_forwarded? &&
|
has_been_forwarded? &&
|
||||||
process_forwarded_email(destination, user)
|
process_forwarded_email(destination, user)
|
||||||
|
@ -679,7 +666,7 @@ module Email
|
||||||
user ||= stage_from_user
|
user ||= stage_from_user
|
||||||
|
|
||||||
group = destination[:obj]
|
group = destination[:obj]
|
||||||
create_group_post(group, user, body, elided, hidden_reason_id)
|
create_group_post(group, user, body, elided)
|
||||||
|
|
||||||
when :category
|
when :category
|
||||||
category = destination[:obj]
|
category = destination[:obj]
|
||||||
|
@ -693,7 +680,6 @@ module Email
|
||||||
create_topic(user: user,
|
create_topic(user: user,
|
||||||
raw: body,
|
raw: body,
|
||||||
elided: elided,
|
elided: elided,
|
||||||
hidden_reason_id: hidden_reason_id,
|
|
||||||
title: subject,
|
title: subject,
|
||||||
category: category.id,
|
category: category.id,
|
||||||
skip_validations: user.staged?)
|
skip_validations: user.staged?)
|
||||||
|
@ -713,7 +699,6 @@ module Email
|
||||||
create_reply(user: user,
|
create_reply(user: user,
|
||||||
raw: body,
|
raw: body,
|
||||||
elided: elided,
|
elided: elided,
|
||||||
hidden_reason_id: hidden_reason_id,
|
|
||||||
post: post,
|
post: post,
|
||||||
topic: post&.topic,
|
topic: post&.topic,
|
||||||
skip_validations: user.staged?,
|
skip_validations: user.staged?,
|
||||||
|
@ -721,7 +706,7 @@ module Email
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def create_group_post(group, user, body, elided, hidden_reason_id)
|
def create_group_post(group, user, body, elided)
|
||||||
message_ids = Email::Receiver.extract_reply_message_ids(@mail, max_message_id_count: 5)
|
message_ids = Email::Receiver.extract_reply_message_ids(@mail, max_message_id_count: 5)
|
||||||
post_ids = []
|
post_ids = []
|
||||||
|
|
||||||
|
@ -739,7 +724,6 @@ module Email
|
||||||
create_reply(user: user,
|
create_reply(user: user,
|
||||||
raw: body,
|
raw: body,
|
||||||
elided: elided,
|
elided: elided,
|
||||||
hidden_reason_id: hidden_reason_id,
|
|
||||||
post: post,
|
post: post,
|
||||||
topic: post.topic,
|
topic: post.topic,
|
||||||
skip_validations: true)
|
skip_validations: true)
|
||||||
|
@ -749,7 +733,6 @@ module Email
|
||||||
create_topic(user: user,
|
create_topic(user: user,
|
||||||
raw: body,
|
raw: body,
|
||||||
elided: elided,
|
elided: elided,
|
||||||
hidden_reason_id: hidden_reason_id,
|
|
||||||
title: subject,
|
title: subject,
|
||||||
archetype: Archetype.private_message,
|
archetype: Archetype.private_message,
|
||||||
target_group_names: [group.name],
|
target_group_names: [group.name],
|
||||||
|
@ -1135,6 +1118,10 @@ module Email
|
||||||
if sent_to_mailinglist_mirror?
|
if sent_to_mailinglist_mirror?
|
||||||
options[:skip_validations] = true
|
options[:skip_validations] = true
|
||||||
options[:skip_guardian] = true
|
options[:skip_guardian] = true
|
||||||
|
else
|
||||||
|
options[:email_spam] = is_spam?
|
||||||
|
options[:first_post_checks] = true if is_spam?
|
||||||
|
options[:email_auth_res_action] = auth_res_action
|
||||||
end
|
end
|
||||||
|
|
||||||
user = options.delete(:user)
|
user = options.delete(:user)
|
||||||
|
|
|
@ -80,8 +80,12 @@ class NewPostManager
|
||||||
def self.post_needs_approval?(manager)
|
def self.post_needs_approval?(manager)
|
||||||
user = manager.user
|
user = manager.user
|
||||||
|
|
||||||
|
return :email_auth_res_enqueue if manager.args[:email_auth_res_action] == :enqueue
|
||||||
|
|
||||||
return :skip if exempt_user?(user)
|
return :skip if exempt_user?(user)
|
||||||
|
|
||||||
|
return :email_spam if manager.args[:email_spam]
|
||||||
|
|
||||||
return :post_count if (
|
return :post_count if (
|
||||||
user.trust_level <= TrustLevel.levels[:basic] &&
|
user.trust_level <= TrustLevel.levels[:basic] &&
|
||||||
user.post_count < SiteSetting.approve_post_count
|
user.post_count < SiteSetting.approve_post_count
|
||||||
|
@ -157,6 +161,8 @@ class NewPostManager
|
||||||
UserSilencer.silence(manager.user, Discourse.system_user, keep_posts: true, reason: I18n.t("user.new_user_typed_too_fast"))
|
UserSilencer.silence(manager.user, Discourse.system_user, keep_posts: true, reason: I18n.t("user.new_user_typed_too_fast"))
|
||||||
elsif matches_auto_silence_regex?(manager)
|
elsif matches_auto_silence_regex?(manager)
|
||||||
UserSilencer.silence(manager.user, Discourse.system_user, keep_posts: true, reason: I18n.t("user.content_matches_auto_silence_regex"))
|
UserSilencer.silence(manager.user, Discourse.system_user, keep_posts: true, reason: I18n.t("user.content_matches_auto_silence_regex"))
|
||||||
|
elsif reason == :email_spam && is_first_post?(manager)
|
||||||
|
UserSilencer.silence(manager.user, Discourse.system_user, keep_posts: true, reason: I18n.t("user.email_in_spam_header"))
|
||||||
end
|
end
|
||||||
|
|
||||||
result
|
result
|
||||||
|
|
|
@ -239,7 +239,7 @@ describe Email::AuthenticationResults do
|
||||||
|
|
||||||
context "with a fail" do
|
context "with a fail" do
|
||||||
let(:headers) { "foobar.com; dmarc=fail" }
|
let(:headers) { "foobar.com; dmarc=fail" }
|
||||||
include_examples "is verdict", :fail
|
include_examples "is verdict", :gray
|
||||||
end
|
end
|
||||||
|
|
||||||
context "with a pass" do
|
context "with a pass" do
|
||||||
|
@ -277,10 +277,10 @@ describe Email::AuthenticationResults do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#action" do
|
describe "#action" do
|
||||||
it "hides a fail verdict" do
|
it "enqueues a fail verdict" do
|
||||||
results = described_class.new("")
|
results = described_class.new("")
|
||||||
results.expects(:verdict).returns(:fail)
|
results.expects(:verdict).returns(:fail)
|
||||||
expect(results.action).to eq (:hide)
|
expect(results.action).to eq (:enqueue)
|
||||||
end
|
end
|
||||||
|
|
||||||
it "accepts a pass verdict" do
|
it "accepts a pass verdict" do
|
||||||
|
|
|
@ -979,59 +979,33 @@ describe Email::Receiver do
|
||||||
it "creates hidden topic for X-Spam-Flag" do
|
it "creates hidden topic for X-Spam-Flag" do
|
||||||
SiteSetting.email_in_spam_header = 'X-Spam-Flag'
|
SiteSetting.email_in_spam_header = 'X-Spam-Flag'
|
||||||
|
|
||||||
Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
|
user = Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
|
||||||
expect { process(:spam_x_spam_flag) }.to change { Topic.count }.by(1) # Topic created
|
expect { process(:spam_x_spam_flag) }.to change { ReviewableQueuedPost.count }.by(1)
|
||||||
|
expect(user.reload.silenced?).to be(true)
|
||||||
topic = Topic.last
|
|
||||||
expect(topic.visible).to eq(false)
|
|
||||||
|
|
||||||
post = Post.last
|
|
||||||
expect(post.hidden).to eq(true)
|
|
||||||
expect(post.hidden_at).not_to eq(nil)
|
|
||||||
expect(post.hidden_reason_id).to eq(Post.hidden_reasons[:email_spam_header_found])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates hidden topic for X-Spam-Status" do
|
it "creates hidden topic for X-Spam-Status" do
|
||||||
SiteSetting.email_in_spam_header = 'X-Spam-Status'
|
SiteSetting.email_in_spam_header = 'X-Spam-Status'
|
||||||
|
|
||||||
Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
|
user = Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
|
||||||
expect { process(:spam_x_spam_status) }.to change { Topic.count }.by(1) # Topic created
|
expect { process(:spam_x_spam_status) }.to change { ReviewableQueuedPost.count }.by(1)
|
||||||
|
expect(user.reload.silenced?).to be(true)
|
||||||
topic = Topic.last
|
|
||||||
expect(topic.visible).to eq(false)
|
|
||||||
|
|
||||||
post = Post.last
|
|
||||||
expect(post.hidden).to eq(true)
|
|
||||||
expect(post.hidden_at).not_to eq(nil)
|
|
||||||
expect(post.hidden_reason_id).to eq(Post.hidden_reasons[:email_spam_header_found])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates hidden topic for X-SES-Spam-Verdict" do
|
it "creates hidden topic for X-SES-Spam-Verdict" do
|
||||||
SiteSetting.email_in_spam_header = 'X-SES-Spam-Verdict'
|
SiteSetting.email_in_spam_header = 'X-SES-Spam-Verdict'
|
||||||
|
|
||||||
Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
|
user = Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
|
||||||
expect { process(:spam_x_ses_spam_verdict) }.to change { Topic.count }.by(1) # Topic created
|
expect { process(:spam_x_ses_spam_verdict) }.to change { ReviewableQueuedPost.count }.by(1)
|
||||||
|
expect(user.reload.silenced?).to be(true)
|
||||||
topic = Topic.last
|
|
||||||
expect(topic.visible).to eq(false)
|
|
||||||
|
|
||||||
post = Post.last
|
|
||||||
expect(post.hidden).to eq(true)
|
|
||||||
expect(post.hidden_at).not_to eq(nil)
|
|
||||||
expect(post.hidden_reason_id).to eq(Post.hidden_reasons[:email_spam_header_found])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "creates hidden topic for failed Authentication-Results header" do
|
it "creates hidden topic for failed Authentication-Results header" do
|
||||||
Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
|
SiteSetting.email_in_authserv_id = 'example.com'
|
||||||
expect { process(:dmarc_fail) }.to change { Topic.count }.by(1) # Topic created
|
|
||||||
|
|
||||||
topic = Topic.last
|
user = Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
|
||||||
expect(topic.visible).to eq(false)
|
expect { process(:dmarc_fail) }.to change { ReviewableQueuedPost.count }.by(1)
|
||||||
|
expect(user.reload.silenced?).to be(false)
|
||||||
post = Post.last
|
|
||||||
expect(post.hidden).to eq(true)
|
|
||||||
expect(post.hidden_at).not_to eq(nil)
|
|
||||||
expect(post.hidden_reason_id).to eq(Post.hidden_reasons[:email_authentication_result_header])
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it "adds the 'elided' part of the original message when always_show_trimmed_content is enabled" do
|
it "adds the 'elided' part of the original message when always_show_trimmed_content is enabled" do
|
||||||
|
|
|
@ -437,4 +437,103 @@ describe NewPostManager do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
context "via email with a spam failure" do
|
||||||
|
let(:user) { Fabricate(:user) }
|
||||||
|
let(:admin) { Fabricate(:admin) }
|
||||||
|
|
||||||
|
it "silences users if its their first post" do
|
||||||
|
manager = NewPostManager.new(
|
||||||
|
user,
|
||||||
|
raw: 'this is emailed content',
|
||||||
|
via_email: true,
|
||||||
|
raw_email: 'raw email contents',
|
||||||
|
email_spam: true,
|
||||||
|
first_post_checks: true
|
||||||
|
)
|
||||||
|
|
||||||
|
result = manager.perform
|
||||||
|
expect(result.action).to eq(:enqueued)
|
||||||
|
expect(user.silenced?).to be(true)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't silence or enqueue exempt users" do
|
||||||
|
manager = NewPostManager.new(
|
||||||
|
admin,
|
||||||
|
raw: 'this is emailed content',
|
||||||
|
via_email: true,
|
||||||
|
raw_email: 'raw email contents',
|
||||||
|
email_spam: true,
|
||||||
|
first_post_checks: true
|
||||||
|
)
|
||||||
|
|
||||||
|
result = manager.perform
|
||||||
|
expect(result.action).to eq(:create_post)
|
||||||
|
expect(admin.silenced?).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "via email with an authentication results failure" do
|
||||||
|
let(:user) { Fabricate(:user) }
|
||||||
|
let(:admin) { Fabricate(:admin) }
|
||||||
|
|
||||||
|
it "doesn't silence users" do
|
||||||
|
manager = NewPostManager.new(
|
||||||
|
user,
|
||||||
|
raw: 'this is emailed content',
|
||||||
|
via_email: true,
|
||||||
|
raw_email: 'raw email contents',
|
||||||
|
email_auth_res_action: :enqueue,
|
||||||
|
first_post_checks: true
|
||||||
|
)
|
||||||
|
|
||||||
|
result = manager.perform
|
||||||
|
expect(result.action).to eq(:enqueued)
|
||||||
|
expect(user.silenced?).to be(false)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "still enqueues exempt users" do
|
||||||
|
manager = NewPostManager.new(
|
||||||
|
admin,
|
||||||
|
raw: 'this is emailed content',
|
||||||
|
via_email: true,
|
||||||
|
raw_email: 'raw email contents',
|
||||||
|
email_auth_res_action: :enqueue
|
||||||
|
)
|
||||||
|
|
||||||
|
result = manager.perform
|
||||||
|
expect(result.action).to eq(:enqueued)
|
||||||
|
expect(user.silenced?).to be(false)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
context "private message via email" do
|
||||||
|
it "doesn't enqueue authentiation results failure" do
|
||||||
|
manager = NewPostManager.new(
|
||||||
|
topic.user,
|
||||||
|
raw: 'this is emailed content',
|
||||||
|
archetype: Archetype.private_message,
|
||||||
|
via_email: true,
|
||||||
|
raw_email: 'raw email contents',
|
||||||
|
email_auth_res_action: :enqueue
|
||||||
|
)
|
||||||
|
|
||||||
|
result = manager.perform
|
||||||
|
expect(result.action).to eq(:create_post)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't enqueue spam failure" do
|
||||||
|
manager = NewPostManager.new(
|
||||||
|
topic.user,
|
||||||
|
raw: 'this is emailed content',
|
||||||
|
archetype: Archetype.private_message,
|
||||||
|
via_email: true,
|
||||||
|
raw_email: 'raw email contents',
|
||||||
|
email_spam: true
|
||||||
|
)
|
||||||
|
|
||||||
|
result = manager.perform
|
||||||
|
expect(result.action).to eq(:create_post)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue
Block a user