mirror of
https://github.com/discourse/discourse.git
synced 2024-11-24 08:56:36 +08:00
28dd7fb562
* Add possibility to add hidden posts with PostCreator * FEATURE: Create hidden posts for received spam emails Spamchecker usually have 3 results: HAM, SPAM and PROBABLY_SPAM SPAM gets usually directly rejected and needs no further handling. HAM is good message and usually gets passed unmodified. PROBABLY_SPAM gets an additional header to allow further processing. This change addes processing capabilities for such headers and marks new posts created as hidden when received via email.
981 lines
40 KiB
Ruby
981 lines
40 KiB
Ruby
require "rails_helper"
|
||
require "email/receiver"
|
||
|
||
describe Email::Receiver do
|
||
|
||
before do
|
||
SiteSetting.email_in = true
|
||
SiteSetting.reply_by_email_address = "reply+%{reply_key}@bar.com"
|
||
SiteSetting.alternative_reply_by_email_addresses = "alt+%{reply_key}@bar.com"
|
||
end
|
||
|
||
def process(email_name)
|
||
Email::Receiver.new(email(email_name)).process!
|
||
end
|
||
|
||
it "raises an EmptyEmailError when 'mail_string' is blank" do
|
||
expect { Email::Receiver.new(nil) }.to raise_error(Email::Receiver::EmptyEmailError)
|
||
expect { Email::Receiver.new("") }.to raise_error(Email::Receiver::EmptyEmailError)
|
||
end
|
||
|
||
it "raises a ScreenedEmailError when email address is screened" do
|
||
ScreenedEmail.expects(:should_block?).with("screened@mail.com").returns(true)
|
||
expect { process(:screened_email) }.to raise_error(Email::Receiver::ScreenedEmailError)
|
||
end
|
||
|
||
it "raises EmailNotAllowed when email address is not on whitelist" do
|
||
SiteSetting.email_domains_whitelist = "example.com|bar.com"
|
||
expect { process(:blacklist_whitelist_email) }.to raise_error(Email::Receiver::EmailNotAllowed)
|
||
end
|
||
|
||
it "raises EmailNotAllowed when email address is on blacklist" do
|
||
SiteSetting.email_domains_blacklist = "email.com|mail.com"
|
||
expect { process(:blacklist_whitelist_email) }.to raise_error(Email::Receiver::EmailNotAllowed)
|
||
end
|
||
|
||
it "raises an UserNotFoundError when staged users are disabled" do
|
||
SiteSetting.enable_staged_users = false
|
||
expect { process(:user_not_found) }.to raise_error(Email::Receiver::UserNotFoundError)
|
||
end
|
||
|
||
it "raises an AutoGeneratedEmailError when the mail is auto generated" do
|
||
expect { process(:auto_generated_precedence) }.to raise_error(Email::Receiver::AutoGeneratedEmailError)
|
||
expect { process(:auto_generated_header) }.to raise_error(Email::Receiver::AutoGeneratedEmailError)
|
||
end
|
||
|
||
it "raises a NoBodyDetectedError when the body is blank" do
|
||
expect { process(:no_body) }.to raise_error(Email::Receiver::NoBodyDetectedError)
|
||
end
|
||
|
||
it "raises a NoSenderDetectedError when the From header is missing" do
|
||
expect { process(:no_from) }.to raise_error(Email::Receiver::NoSenderDetectedError)
|
||
end
|
||
|
||
it "raises an InactiveUserError when the sender is inactive" do
|
||
Fabricate(:user, email: "inactive@bar.com", active: false)
|
||
expect { process(:inactive_sender) }.to raise_error(Email::Receiver::InactiveUserError)
|
||
end
|
||
|
||
it "raises a SilencedUserError when the sender has been silenced" do
|
||
Fabricate(:user, email: "silenced@bar.com", silenced_till: 1.year.from_now)
|
||
expect { process(:silenced_sender) }.to raise_error(Email::Receiver::SilencedUserError)
|
||
end
|
||
|
||
it "doesn't raise an InactiveUserError when the sender is staged" do
|
||
user = Fabricate(:user, email: "staged@bar.com", active: false, staged: true)
|
||
|
||
email_log = Fabricate(:email_log,
|
||
to_address: 'reply+aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa@bar.com',
|
||
reply_key: 'aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa',
|
||
user: user,
|
||
post: Fabricate(:post)
|
||
)
|
||
|
||
expect { process(:staged_sender) }.not_to raise_error
|
||
end
|
||
|
||
it "raises a BadDestinationAddress when destinations aren't matching any of the incoming emails" do
|
||
expect { process(:bad_destinations) }.to raise_error(Email::Receiver::BadDestinationAddress)
|
||
end
|
||
|
||
it "raises an OldDestinationError when notification is too old" do
|
||
topic = Fabricate(:topic, id: 424242)
|
||
post = Fabricate(:post, topic: topic, id: 123456, created_at: 1.year.ago)
|
||
|
||
expect { process(:old_destination) }.to raise_error(Email::Receiver::OldDestinationError)
|
||
end
|
||
|
||
it "raises a BouncerEmailError when email is a bounced email" do
|
||
expect { process(:bounced_email) }.to raise_error(Email::Receiver::BouncedEmailError)
|
||
expect(IncomingEmail.last.is_bounce).to eq(true)
|
||
|
||
expect { process(:bounced_email_multiple_status_codes) }.to raise_error(Email::Receiver::BouncedEmailError)
|
||
expect(IncomingEmail.last.is_bounce).to eq(true)
|
||
end
|
||
|
||
it "logs a blank error" do
|
||
Email::Receiver.any_instance.stubs(:process_internal).raises(RuntimeError, "")
|
||
process(:existing_user) rescue RuntimeError
|
||
expect(IncomingEmail.last.error).to eq("RuntimeError")
|
||
end
|
||
|
||
context "bounces to VERP" do
|
||
|
||
let(:bounce_key) { "14b08c855160d67f2e0c2f8ef36e251e" }
|
||
let(:bounce_key_2) { "b542fb5a9bacda6d28cc061d18e4eb83" }
|
||
let!(:user) { Fabricate(:user, email: "foo@bar.com") }
|
||
let!(:email_log) { Fabricate(:email_log, user: user, bounce_key: bounce_key) }
|
||
let!(:email_log_2) { Fabricate(:email_log, user: user, bounce_key: bounce_key_2) }
|
||
|
||
it "deals with soft bounces" do
|
||
expect { process(:soft_bounce_via_verp) }.to raise_error(Email::Receiver::BouncedEmailError)
|
||
|
||
email_log.reload
|
||
expect(email_log.bounced).to eq(true)
|
||
expect(email_log.user.user_stat.bounce_score).to eq(SiteSetting.soft_bounce_score)
|
||
end
|
||
|
||
it "deals with hard bounces" do
|
||
expect { process(:hard_bounce_via_verp) }.to raise_error(Email::Receiver::BouncedEmailError)
|
||
|
||
email_log.reload
|
||
expect(email_log.bounced).to eq(true)
|
||
expect(email_log.user.user_stat.bounce_score).to eq(SiteSetting.hard_bounce_score)
|
||
|
||
expect { process(:hard_bounce_via_verp_2) }.to raise_error(Email::Receiver::BouncedEmailError)
|
||
|
||
email_log_2.reload
|
||
expect(email_log_2.user.user_stat.bounce_score).to eq(SiteSetting.hard_bounce_score * 2)
|
||
expect(email_log_2.bounced).to eq(true)
|
||
end
|
||
|
||
it "automatically deactive users once they reach the 'bounce_score_threshold_deactivate' threshold" do
|
||
expect(user.active).to eq(true)
|
||
|
||
user.user_stat.bounce_score = SiteSetting.bounce_score_threshold_deactivate - 1
|
||
user.user_stat.save!
|
||
|
||
expect { process(:soft_bounce_via_verp) }.to raise_error(Email::Receiver::BouncedEmailError)
|
||
|
||
user.reload
|
||
email_log.reload
|
||
|
||
expect(email_log.bounced).to eq(true)
|
||
expect(user.active).to eq(false)
|
||
end
|
||
|
||
end
|
||
|
||
context "reply" do
|
||
|
||
let(:reply_key) { "4f97315cc828096c9cb34c6f1a0d6fe8" }
|
||
let(:category) { Fabricate(:category) }
|
||
let(:user) { Fabricate(:user, email: "discourse@bar.com") }
|
||
let(:topic) { create_topic(category: category, user: user) }
|
||
let(:post) { create_post(topic: topic, user: user) }
|
||
let!(:email_log) { Fabricate(:email_log, reply_key: reply_key, user: user, topic: topic, post: post) }
|
||
|
||
it "uses MD5 of 'mail_string' there is no message_id" do
|
||
mail_string = email(:missing_message_id)
|
||
expect { Email::Receiver.new(mail_string).process! }.to change { IncomingEmail.count }
|
||
expect(IncomingEmail.last.message_id).to eq(Digest::MD5.hexdigest(mail_string))
|
||
end
|
||
|
||
it "raises a ReplyUserNotMatchingError when the email address isn't matching the one we sent the notification to" do
|
||
expect { process(:reply_user_not_matching) }.to raise_error(Email::Receiver::ReplyUserNotMatchingError)
|
||
end
|
||
|
||
it "raises a FromReplyByAddressError when the email is from the reply by email address" do
|
||
expect { process(:from_reply_by_email_address) }.to raise_error(Email::Receiver::FromReplyByAddressError)
|
||
end
|
||
|
||
it "raises a TopicNotFoundError when the topic was deleted" do
|
||
topic.update_columns(deleted_at: 1.day.ago)
|
||
expect { process(:reply_user_matching) }.to raise_error(Email::Receiver::TopicNotFoundError)
|
||
end
|
||
|
||
it "raises a TopicClosedError when the topic was closed" do
|
||
topic.update_columns(closed: true)
|
||
expect { process(:reply_user_matching) }.to raise_error(Email::Receiver::TopicClosedError)
|
||
end
|
||
|
||
it "does not raise TopicClosedError when performing a like action" do
|
||
topic.update_columns(closed: true)
|
||
expect { process(:like) }.to change(PostAction, :count)
|
||
end
|
||
|
||
it "raises an InvalidPost when there was an error while creating the post" do
|
||
expect { process(:too_small) }.to raise_error(Email::Receiver::InvalidPost)
|
||
end
|
||
|
||
it "raises an InvalidPost when there are too may mentions" do
|
||
SiteSetting.max_mentions_per_post = 1
|
||
Fabricate(:user, username: "user1")
|
||
Fabricate(:user, username: "user2")
|
||
expect { process(:too_many_mentions) }.to raise_error(Email::Receiver::InvalidPost)
|
||
end
|
||
|
||
it "raises an InvalidPostAction when they aren't allowed to like a post" do
|
||
topic.update_columns(archived: true)
|
||
expect { process(:like) }.to raise_error(Email::Receiver::InvalidPostAction)
|
||
end
|
||
|
||
it "works" do
|
||
expect { process(:text_reply) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("This is a text reply :)\n\nEmail parsing should not break because of a UTF-8 character: ’")
|
||
expect(topic.posts.last.via_email).to eq(true)
|
||
expect(topic.posts.last.cooked).not_to match(/<br/)
|
||
|
||
expect { process(:html_reply) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("This is a **HTML** reply ;)")
|
||
end
|
||
|
||
it "automatically elides gmail quotes" do
|
||
SiteSetting.always_show_trimmed_content = true
|
||
expect { process(:gmail_html_reply) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("This is a **GMAIL** reply ;)\n\n<details class='elided'>\n<summary title='Show trimmed content'>···</summary>\n\nThis is the *elided* part!\n\n</details>")
|
||
end
|
||
|
||
it "doesn't process email with same message-id more than once" do
|
||
expect do
|
||
process(:text_reply)
|
||
process(:text_reply)
|
||
end.to change { topic.posts.count }.by(1)
|
||
end
|
||
|
||
it "handles different encodings correctly" do
|
||
expect { process(:hebrew_reply) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("שלום! מה שלומך היום?")
|
||
|
||
expect { process(:chinese_reply) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("您好! 你今天好吗?")
|
||
|
||
expect { process(:reply_with_weird_encoding) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("This is a reply with a weird encoding.")
|
||
|
||
expect { process(:reply_with_8bit_encoding) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("hab vergessen kritische zeichen einzufügen:\näöüÄÖÜß")
|
||
end
|
||
|
||
it "prefers text over html when site setting is disabled" do
|
||
SiteSetting.incoming_email_prefer_html = false
|
||
expect { process(:text_and_html_reply) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("This is the *text* part.")
|
||
end
|
||
|
||
it "prefers html over text when site setting is enabled" do
|
||
SiteSetting.incoming_email_prefer_html = true
|
||
expect { process(:text_and_html_reply) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq('This is the **html** part.')
|
||
end
|
||
|
||
it "uses text when prefer_html site setting is enabled but no html is available" do
|
||
SiteSetting.incoming_email_prefer_html = true
|
||
expect { process(:text_reply) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("This is a text reply :)\n\nEmail parsing should not break because of a UTF-8 character: ’")
|
||
end
|
||
|
||
it "removes the 'on <date>, <contact> wrote' quoting line" do
|
||
expect { process(:on_date_contact_wrote) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("This is the actual reply.")
|
||
end
|
||
|
||
it "removes the 'Previous Replies' marker" do
|
||
expect { process(:previous_replies) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("This will not include the previous discussion that is present in this email.")
|
||
end
|
||
|
||
it "handles multiple paragraphs" do
|
||
expect { process(:paragraphs) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("Do you like liquorice?\n\nI really like them. One could even say that I am *addicted* to liquorice. Anf if\nyou can mix it up with some anise, then I'm in heaven ;)")
|
||
end
|
||
|
||
it "handles invalid from header" do
|
||
expect { process(:invalid_from_1) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("This email was sent with an invalid from header field.")
|
||
end
|
||
|
||
it "raises a NoSenderDetectedError when the From header doesn't contain an email address" do
|
||
expect { process(:invalid_from_2) }.to raise_error(Email::Receiver::NoSenderDetectedError)
|
||
end
|
||
|
||
it "doesn't raise an AutoGeneratedEmailError when the mail is auto generated but is whitelisted" do
|
||
SiteSetting.auto_generated_whitelist = "foo@bar.com|discourse@bar.com"
|
||
expect { process(:auto_generated_whitelisted) }.to change { topic.posts.count }
|
||
end
|
||
|
||
it "doesn't raise an AutoGeneratedEmailError when block_auto_generated_emails is disabled" do
|
||
SiteSetting.block_auto_generated_emails = false
|
||
expect { process(:auto_generated_unblocked) }.to change { topic.posts.count }
|
||
end
|
||
|
||
it "allows staged users to reply to a restricted category" do
|
||
user.update_columns(staged: true)
|
||
|
||
category.email_in = "category@bar.com"
|
||
category.email_in_allow_strangers = true
|
||
category.set_permissions(Group[:trust_level_4] => :full)
|
||
category.save
|
||
|
||
expect { process(:staged_reply_restricted) }.to change { topic.posts.count }
|
||
end
|
||
|
||
describe 'Unsubscribing via email' do
|
||
let(:last_email) { ActionMailer::Base.deliveries.last }
|
||
|
||
describe 'unsubscribe_subject.eml' do
|
||
it 'sends an email asking the user to confirm the unsubscription' do
|
||
expect { process("unsubscribe_subject") }.to change { ActionMailer::Base.deliveries.count }.by(1)
|
||
expect(last_email.to.length).to eq 1
|
||
expect(last_email.from.length).to eq 1
|
||
expect(last_email.from).to include "noreply@#{Discourse.current_hostname}"
|
||
expect(last_email.to).to include "discourse@bar.com"
|
||
expect(last_email.subject).to eq I18n.t(:"unsubscribe_mailer.subject_template").gsub("%{site_title}", SiteSetting.title)
|
||
end
|
||
|
||
it 'does nothing unless unsubscribe_via_email is turned on' do
|
||
SiteSetting.unsubscribe_via_email = false
|
||
before_deliveries = ActionMailer::Base.deliveries.count
|
||
expect { process("unsubscribe_subject") }.to raise_error { Email::Receiver::BadDestinationAddress }
|
||
expect(before_deliveries).to eq ActionMailer::Base.deliveries.count
|
||
end
|
||
end
|
||
|
||
describe 'unsubscribe_body.eml' do
|
||
it 'sends an email asking the user to confirm the unsubscription' do
|
||
expect { process("unsubscribe_body") }.to change { ActionMailer::Base.deliveries.count }.by(1)
|
||
expect(last_email.to.length).to eq 1
|
||
expect(last_email.from.length).to eq 1
|
||
expect(last_email.from).to include "noreply@#{Discourse.current_hostname}"
|
||
expect(last_email.to).to include "discourse@bar.com"
|
||
expect(last_email.subject).to eq I18n.t(:"unsubscribe_mailer.subject_template").gsub("%{site_title}", SiteSetting.title)
|
||
end
|
||
|
||
it 'does nothing unless unsubscribe_via_email is turned on' do
|
||
SiteSetting.unsubscribe_via_email = false
|
||
before_deliveries = ActionMailer::Base.deliveries.count
|
||
expect { process("unsubscribe_body") }.to raise_error { Email::Receiver::InvalidPost }
|
||
expect(before_deliveries).to eq ActionMailer::Base.deliveries.count
|
||
end
|
||
end
|
||
|
||
it "raises an UnsubscribeNotAllowed and does not send an unsubscribe email" do
|
||
before_deliveries = ActionMailer::Base.deliveries.count
|
||
expect { process(:unsubscribe_new_user) }.to raise_error { Email::Receiver::UnsubscribeNotAllowed }
|
||
expect(before_deliveries).to eq ActionMailer::Base.deliveries.count
|
||
end
|
||
end
|
||
|
||
it "handles inline reply" do
|
||
expect { process(:inline_reply) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("And this is *my* reply :+1:")
|
||
end
|
||
|
||
it "retrieves the first part of multiple replies" do
|
||
expect { process(:inline_mixed_replies) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("> WAT <https://bar.com/users/wat> November 28\n>\n> This is the previous post.\n\nAnd this is *my* reply :+1:\n\n> This is another post.\n\nAnd this is **another** reply.")
|
||
end
|
||
|
||
it "strips mobile/webmail signatures" do
|
||
expect { process(:iphone_signature) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("This is not the signature you're looking for.")
|
||
end
|
||
|
||
it "strips 'original message' context" do
|
||
expect { process(:original_message) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("This is a reply :)")
|
||
end
|
||
|
||
it "add the 'elided' part of the original message only for private messages" do
|
||
topic.update_columns(category_id: nil, archetype: Archetype.private_message)
|
||
topic.allowed_users << user
|
||
topic.save
|
||
|
||
expect { process(:original_message) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to eq("This is a reply :)\n\n<details class='elided'>\n<summary title='Show trimmed content'>···</summary>\n\n---Original Message---\nThis part should not be included\n\n</details>")
|
||
end
|
||
|
||
it "doesn't include the 'elided' part of the original message when always_show_trimmed_content is disabled" do
|
||
SiteSetting.always_show_trimmed_content = false
|
||
expect { process(:original_message) }.to change { topic.posts.count }.from(1).to(2)
|
||
expect(topic.posts.last.raw).to eq("This is a reply :)")
|
||
end
|
||
|
||
it "adds the 'elided' part of the original message for public replies when always_show_trimmed_content is enabled" do
|
||
SiteSetting.always_show_trimmed_content = true
|
||
expect { process(:original_message) }.to change { topic.posts.count }.from(1).to(2)
|
||
expect(topic.posts.last.raw).to eq("This is a reply :)\n\n<details class='elided'>\n<summary title='Show trimmed content'>···</summary>\n\n---Original Message---\nThis part should not be included\n\n</details>")
|
||
end
|
||
|
||
it "supports attached images in TEXT part" do
|
||
SiteSetting.incoming_email_prefer_html = false
|
||
|
||
expect { process(:no_body_with_image) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to match(/<img/)
|
||
|
||
expect { process(:inline_image) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to match(/Before\s+<img.+>\s+After/)
|
||
end
|
||
|
||
it "supports attached images in HTML part" do
|
||
SiteSetting.incoming_email_prefer_html = true
|
||
|
||
expect { process(:inline_image) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to match(/\*\*Before\*\*\s+<img.+>\s+\*After\*/)
|
||
end
|
||
|
||
it "supports attachments" do
|
||
SiteSetting.authorized_extensions = "txt"
|
||
expect { process(:attached_txt_file) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to match(/text\.txt/)
|
||
|
||
SiteSetting.authorized_extensions = "csv"
|
||
expect { process(:attached_txt_file_2) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to_not match(/text\.txt/)
|
||
end
|
||
|
||
it "supports emails with just an attachment" do
|
||
SiteSetting.authorized_extensions = "pdf"
|
||
expect { process(:attached_pdf_file) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.raw).to match(/discourse\.pdf/)
|
||
end
|
||
|
||
it "supports liking via email" do
|
||
expect { process(:like) }.to change(PostAction, :count)
|
||
end
|
||
|
||
it "ensures posts aren't dated in the future" do
|
||
expect { process(:from_the_future) }.to change { topic.posts.count }
|
||
expect(topic.posts.last.created_at).to be_within(1.minute).of(DateTime.now)
|
||
end
|
||
|
||
it "accepts emails with wrong reply key if the system knows about the forwarded email" do
|
||
Fabricate(:incoming_email,
|
||
raw: <<~RAW,
|
||
Return-Path: <discourse@bar.com>
|
||
From: Alice <discourse@bar.com>
|
||
To: dave@bar.com, reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com
|
||
CC: carol@bar.com, bob@bar.com
|
||
Subject: Hello world
|
||
Date: Fri, 15 Jan 2016 00:12:43 +0100
|
||
Message-ID: <10@foo.bar.mail>
|
||
Mime-Version: 1.0
|
||
Content-Type: text/plain; charset=UTF-8
|
||
Content-Transfer-Encoding: quoted-printable
|
||
|
||
This post was created by email.
|
||
RAW
|
||
from_address: "discourse@bar.com",
|
||
to_addresses: "dave@bar.com;reply+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com",
|
||
cc_addresses: "carol@bar.com;bob@bar.com",
|
||
topic: topic,
|
||
post: post,
|
||
user: user)
|
||
|
||
expect { process(:reply_user_not_matching_but_known) }.to change { topic.posts.count }
|
||
end
|
||
end
|
||
|
||
context "new message to a group" do
|
||
|
||
let!(:group) { Fabricate(:group, incoming_email: "team@bar.com|meat@bar.com") }
|
||
|
||
it "handles encoded display names" do
|
||
expect { process(:encoded_display_name) }.to change(Topic, :count)
|
||
|
||
topic = Topic.last
|
||
expect(topic.title).to eq("I need help")
|
||
expect(topic.private_message?).to eq(true)
|
||
expect(topic.allowed_groups).to include(group)
|
||
|
||
user = topic.user
|
||
expect(user.staged).to eq(true)
|
||
expect(user.username).to eq("random.name")
|
||
expect(user.name).to eq("Случайная Имя")
|
||
end
|
||
|
||
it "handles email with no subject" do
|
||
expect { process(:no_subject) }.to change(Topic, :count)
|
||
expect(Topic.last.title).to eq("This topic needs a title")
|
||
end
|
||
|
||
it "invites everyone in the chain but emails configured as 'incoming' (via reply, group or category)" do
|
||
expect { process(:cc) }.to change(Topic, :count)
|
||
|
||
topic = Topic.last
|
||
|
||
emails = topic.allowed_users.joins(:user_emails).pluck(:"user_emails.email")
|
||
expect(emails).to contain_exactly("someone@else.com", "discourse@bar.com", "wat@bar.com")
|
||
|
||
expect(topic.topic_users.count).to eq(3)
|
||
end
|
||
|
||
it "cap the number of staged users created per email" do
|
||
SiteSetting.maximum_staged_users_per_email = 1
|
||
expect { process(:cc) }.to change(Topic, :count)
|
||
expect(Topic.last.ordered_posts[-1].post_type).to eq(Post.types[:moderator_action])
|
||
end
|
||
|
||
it "associates email replies using both 'In-Reply-To' and 'References' headers when 'find_related_post_with_key' is disabled" do
|
||
SiteSetting.find_related_post_with_key = false
|
||
|
||
expect { process(:email_reply_1) }.to change(Topic, :count)
|
||
|
||
topic = Topic.last
|
||
|
||
expect { process(:email_reply_2) }.to change { topic.posts.count }
|
||
expect { process(:email_reply_3) }.to change { topic.posts.count }
|
||
|
||
# Why 5 when we only processed 3 emails?
|
||
# - 3 of them are indeed "regular" posts generated from the emails
|
||
# - The 2 others are "small action" posts automatically added because
|
||
# we invited 2 users (two@foo.com and three@foo.com)
|
||
expect(topic.posts.count).to eq(5)
|
||
|
||
# trash all but the 1st post
|
||
topic.ordered_posts[1..-1].each(&:trash!)
|
||
|
||
expect { process(:email_reply_4) }.to change { topic.posts.count }
|
||
end
|
||
|
||
it "supports any kind of attachments when 'allow_all_attachments_for_group_messages' is enabled" do
|
||
SiteSetting.allow_all_attachments_for_group_messages = true
|
||
expect { process(:attached_rb_file) }.to change(Topic, :count)
|
||
expect(Post.last.raw).to match(/discourse\.rb/)
|
||
end
|
||
|
||
context "with forwarded emails enabled" do
|
||
before { SiteSetting.enable_forwarded_emails = true }
|
||
|
||
it "handles forwarded emails" do
|
||
expect { process(:forwarded_email_1) }.to change(Topic, :count)
|
||
|
||
forwarded_post, last_post = *Post.last(2)
|
||
|
||
expect(forwarded_post.user.email).to eq("some@one.com")
|
||
expect(last_post.user.email).to eq("ba@bar.com")
|
||
|
||
expect(forwarded_post.raw).to match(/XoXo/)
|
||
expect(last_post.raw).to match(/can you have a look at this email below/)
|
||
|
||
expect(last_post.post_type).to eq(Post.types[:regular])
|
||
end
|
||
|
||
it "handles weirdly forwarded emails" do
|
||
group.add(Fabricate(:user, email: "ba@bar.com"))
|
||
group.save
|
||
|
||
SiteSetting.enable_forwarded_emails = true
|
||
expect { process(:forwarded_email_2) }.to change(Topic, :count)
|
||
|
||
forwarded_post, last_post = *Post.last(2)
|
||
|
||
expect(forwarded_post.user.email).to eq("some@one.com")
|
||
expect(last_post.user.email).to eq("ba@bar.com")
|
||
|
||
expect(forwarded_post.raw).to match(/XoXo/)
|
||
expect(last_post.raw).to match(/can you have a look at this email below/)
|
||
|
||
expect(last_post.post_type).to eq(Post.types[:whisper])
|
||
end
|
||
|
||
# Who thought this was a good idea?!
|
||
it "doesn't blow up with localized email headers" do
|
||
expect { process(:forwarded_email_3) }.to change(Topic, :count)
|
||
end
|
||
|
||
end
|
||
|
||
context "when message sent to a group has no key and find_related_post_with_key is enabled" do
|
||
let!(:topic) do
|
||
SiteSetting.find_related_post_with_key = true
|
||
process(:email_reply_1)
|
||
Topic.last
|
||
end
|
||
|
||
it "creates a reply when the sender and referenced message id are known" do
|
||
expect { process(:email_reply_2) }.to change { topic.posts.count }.by(1).and change { Topic.count }.by(0)
|
||
end
|
||
|
||
it "creates a new topic when the sender is not known" do
|
||
IncomingEmail.where(message_id: '34@foo.bar.mail').update(cc_addresses: 'three@foo.com')
|
||
expect { process(:email_reply_2) }.to change { topic.posts.count }.by(0).and change { Topic.count }.by(1)
|
||
end
|
||
|
||
it "creates a new topic when the referenced message id is not known" do
|
||
IncomingEmail.where(message_id: '34@foo.bar.mail').update(message_id: '99@foo.bar.mail')
|
||
expect { process(:email_reply_2) }.to change { topic.posts.count }.by(0).and change { Topic.count }.by(1)
|
||
end
|
||
end
|
||
end
|
||
|
||
context "new topic in a category" do
|
||
|
||
let!(:category) { Fabricate(:category, email_in: "category@bar.com|category@foo.com", email_in_allow_strangers: false) }
|
||
|
||
it "raises a StrangersNotAllowedError when 'email_in_allow_strangers' is disabled" do
|
||
expect { process(:new_user) }.to raise_error(Email::Receiver::StrangersNotAllowedError)
|
||
end
|
||
|
||
it "raises an InsufficientTrustLevelError when user's trust level isn't enough" do
|
||
Fabricate(:user, email: "existing@bar.com", trust_level: 3)
|
||
SiteSetting.email_in_min_trust = 4
|
||
expect { process(:existing_user) }.to raise_error(Email::Receiver::InsufficientTrustLevelError)
|
||
end
|
||
|
||
it "works" do
|
||
user = Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
|
||
group = Fabricate(:group)
|
||
|
||
group.add(user)
|
||
group.save
|
||
|
||
category.set_permissions(group => :create_post)
|
||
category.save
|
||
|
||
# raises an InvalidAccess when the user doesn't have the privileges to create a topic
|
||
expect { process(:existing_user) }.to raise_error(Discourse::InvalidAccess)
|
||
|
||
category.update_columns(email_in_allow_strangers: true)
|
||
|
||
# allows new user to create a topic
|
||
expect { process(:new_user) }.to change(Topic, :count)
|
||
end
|
||
|
||
it "creates visible topic for ham" do
|
||
SiteSetting.email_in_spam_header = 'none'
|
||
|
||
Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
|
||
expect { process(:existing_user) }.to change { Topic.count }.by(1) # Topic created
|
||
|
||
topic = Topic.last
|
||
expect(topic.visible).to eq(true)
|
||
|
||
post = Post.last
|
||
expect(post.hidden).to eq(false)
|
||
expect(post.hidden_at).to eq(nil)
|
||
expect(post.hidden_reason_id).to eq(nil)
|
||
end
|
||
|
||
it "creates hidden topic for X-Spam-Flag" do
|
||
SiteSetting.email_in_spam_header = 'X-Spam-Flag'
|
||
|
||
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
|
||
|
||
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
|
||
|
||
it "creates hidden topic for X-Spam-Status" do
|
||
SiteSetting.email_in_spam_header = 'X-Spam-Status'
|
||
|
||
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
|
||
|
||
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
|
||
|
||
it "adds the 'elided' part of the original message when always_show_trimmed_content is enabled" do
|
||
SiteSetting.always_show_trimmed_content = true
|
||
|
||
Fabricate(:user, email: "existing@bar.com", trust_level: SiteSetting.email_in_min_trust)
|
||
expect { process(:forwarded_email_to_category) }.to change { Topic.count }.by(1) # Topic created
|
||
|
||
new_post, = Post.last
|
||
expect(new_post.raw).to include("Hi everyone, can you have a look at the email below?", "<summary title='Show trimmed content'>···</summary>", "Discoursing much today?")
|
||
end
|
||
|
||
it "works when approving is enabled" do
|
||
SiteSetting.approve_unless_trust_level = 4
|
||
|
||
Fabricate(:user, email: "tl3@bar.com", trust_level: TrustLevel[3])
|
||
Fabricate(:user, email: "tl4@bar.com", trust_level: TrustLevel[4])
|
||
|
||
category.set_permissions(Group[:trust_level_4] => :full)
|
||
category.save
|
||
|
||
Group.refresh_automatic_group!(:trust_level_4)
|
||
|
||
expect { process(:tl3_user) }.to_not change(Topic, :count)
|
||
expect { process(:tl4_user) }.to change(Topic, :count)
|
||
end
|
||
|
||
it "ignores by case-insensitive title" do
|
||
SiteSetting.ignore_by_title = "foo"
|
||
expect { process(:ignored) }.to_not change(Topic, :count)
|
||
end
|
||
end
|
||
|
||
context "new topic in a category that allows strangers" do
|
||
|
||
let!(:category) { Fabricate(:category, email_in: "category@bar.com|category@foo.com", email_in_allow_strangers: true) }
|
||
|
||
it "lets an email in from a stranger" do
|
||
expect { process(:new_user) }.to change(Topic, :count)
|
||
end
|
||
|
||
it "lets an email in from a high-TL user" do
|
||
Fabricate(:user, email: "tl4@bar.com", trust_level: TrustLevel[4])
|
||
expect { process(:tl4_user) }.to change(Topic, :count)
|
||
end
|
||
|
||
it "fails on email from a low-TL user" do
|
||
SiteSetting.email_in_min_trust = 4
|
||
Fabricate(:user, email: "tl3@bar.com", trust_level: TrustLevel[3])
|
||
expect { process(:tl3_user) }.to raise_error(Email::Receiver::InsufficientTrustLevelError)
|
||
end
|
||
|
||
end
|
||
|
||
context "#reply_by_email_address_regex" do
|
||
|
||
before do
|
||
SiteSetting.reply_by_email_address = nil
|
||
SiteSetting.alternative_reply_by_email_addresses = nil
|
||
end
|
||
|
||
it "it maches nothing if there is not reply_by_email_address" do
|
||
expect(Email::Receiver.reply_by_email_address_regex).to eq(/$a/)
|
||
end
|
||
|
||
it "uses 'reply_by_email_address' site setting" do
|
||
SiteSetting.reply_by_email_address = "foo+%{reply_key}@bar.com"
|
||
expect(Email::Receiver.reply_by_email_address_regex).to eq(/foo\+?(\h{32})?@bar\.com/)
|
||
end
|
||
|
||
it "uses 'alternative_reply_by_email_addresses' site setting" do
|
||
SiteSetting.alternative_reply_by_email_addresses = "alt.foo+%{reply_key}@bar.com"
|
||
expect(Email::Receiver.reply_by_email_address_regex).to eq(/alt\.foo\+?(\h{32})?@bar\.com/)
|
||
end
|
||
|
||
it "combines both 'reply_by_email' settings" do
|
||
SiteSetting.reply_by_email_address = "foo+%{reply_key}@bar.com"
|
||
SiteSetting.alternative_reply_by_email_addresses = "alt.foo+%{reply_key}@bar.com"
|
||
expect(Email::Receiver.reply_by_email_address_regex).to eq(/foo\+?(\h{32})?@bar\.com|alt\.foo\+?(\h{32})?@bar\.com/)
|
||
end
|
||
|
||
end
|
||
|
||
context "check_address" do
|
||
before do
|
||
SiteSetting.reply_by_email_address = "foo+%{reply_key}@bar.com"
|
||
end
|
||
|
||
it "returns nil when the key is invalid" do
|
||
expect(Email::Receiver.check_address('fake@fake.com')).to be_nil
|
||
expect(Email::Receiver.check_address('foo+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com')).to be_nil
|
||
end
|
||
|
||
context "with a valid reply" do
|
||
it "returns the destination when the key is valid" do
|
||
Fabricate(:email_log, reply_key: '4f97315cc828096c9cb34c6f1a0d6fe8')
|
||
|
||
dest = Email::Receiver.check_address('foo+4f97315cc828096c9cb34c6f1a0d6fe8@bar.com')
|
||
expect(dest).to be_present
|
||
expect(dest[:type]).to eq(:reply)
|
||
expect(dest[:obj]).to be_present
|
||
end
|
||
end
|
||
end
|
||
|
||
context "staged users" do
|
||
before do
|
||
SiteSetting.enable_staged_users = true
|
||
end
|
||
|
||
shared_examples "no staged users" do |email_name, expected_exception|
|
||
it "does not create staged users" do
|
||
staged_user_count = User.where(staged: true).count
|
||
expect { process(email_name) }.to raise_error(expected_exception)
|
||
expect(User.where(staged: true).count).to eq(staged_user_count)
|
||
end
|
||
end
|
||
|
||
context "when email address is screened" do
|
||
before do
|
||
ScreenedEmail.expects(:should_block?).with("screened@mail.com").returns(true)
|
||
end
|
||
|
||
include_examples "no staged users", :screened_email, Email::Receiver::ScreenedEmailError
|
||
end
|
||
|
||
context "when the mail is auto generated" do
|
||
include_examples "no staged users", :auto_generated_header, Email::Receiver::AutoGeneratedEmailError
|
||
end
|
||
|
||
context "when email is a bounced email" do
|
||
include_examples "no staged users", :bounced_email, Email::Receiver::BouncedEmailError
|
||
end
|
||
|
||
context "when the body is blank" do
|
||
include_examples "no staged users", :no_body, Email::Receiver::NoBodyDetectedError
|
||
end
|
||
|
||
context "when unsubscribe via email is not allowed" do
|
||
include_examples "no staged users", :unsubscribe_new_user, Email::Receiver::UnsubscribeNotAllowed
|
||
end
|
||
|
||
context "when From email address is not on whitelist" do
|
||
before do
|
||
SiteSetting.email_domains_whitelist = "example.com|bar.com"
|
||
end
|
||
|
||
include_examples "no staged users", :blacklist_whitelist_email, Email::Receiver::EmailNotAllowed
|
||
end
|
||
|
||
context "when From email address is on blacklist" do
|
||
before do
|
||
SiteSetting.email_domains_blacklist = "email.com|mail.com"
|
||
end
|
||
|
||
include_examples "no staged users", :blacklist_whitelist_email, Email::Receiver::EmailNotAllowed
|
||
end
|
||
|
||
context "blacklist and whitelist for To and Cc" do
|
||
before do
|
||
Fabricate(:group, incoming_email: "some_group@bar.com")
|
||
end
|
||
|
||
it "does not create staged users for email addresses not on whitelist" do
|
||
SiteSetting.email_domains_whitelist = "mail.com|example.com"
|
||
process(:blacklist_whitelist_email)
|
||
|
||
expect(User.find_by_email("alice@foo.com")).to be_nil
|
||
expect(User.find_by_email("bob@foo.com")).to be_nil
|
||
expect(User.find_by_email("carol@example.com")).to be_present
|
||
end
|
||
|
||
it "does not create staged users for email addresses on blacklist" do
|
||
SiteSetting.email_domains_blacklist = "email.com|foo.com"
|
||
process(:blacklist_whitelist_email)
|
||
|
||
expect(User.find_by_email("alice@foo.com")).to be_nil
|
||
expect(User.find_by_email("bob@foo.com")).to be_nil
|
||
expect(User.find_by_email("carol@example.com")).to be_present
|
||
end
|
||
end
|
||
|
||
context "when destinations aren't matching any of the incoming emails" do
|
||
include_examples "no staged users", :bad_destinations, Email::Receiver::BadDestinationAddress
|
||
end
|
||
|
||
context "when email is sent to category" do
|
||
context "when email is sent by a new user and category does not allow strangers" do
|
||
let!(:category) { Fabricate(:category, email_in: "category@foo.com", email_in_allow_strangers: false) }
|
||
|
||
include_examples "no staged users", :new_user, Email::Receiver::StrangersNotAllowedError
|
||
end
|
||
|
||
context "when email has no date" do
|
||
let!(:category) { Fabricate(:category, email_in: "category@foo.com", email_in_allow_strangers: true) }
|
||
|
||
include_examples "no staged users", :no_date, Email::Receiver::InvalidPost
|
||
end
|
||
end
|
||
|
||
context "email is a reply" do
|
||
let(:reply_key) { "4f97315cc828096c9cb34c6f1a0d6fe8" }
|
||
let(:category) { Fabricate(:category) }
|
||
let(:user) { Fabricate(:user, email: "discourse@bar.com") }
|
||
let(:topic) { create_topic(category: category, user: user) }
|
||
let(:post) { create_post(topic: topic, user: user) }
|
||
let!(:email_log) { Fabricate(:email_log, reply_key: reply_key, user: user, topic: topic, post: post) }
|
||
|
||
context "when the email address isn't matching the one we sent the notification to" do
|
||
include_examples "no staged users", :reply_user_not_matching, Email::Receiver::ReplyUserNotMatchingError
|
||
end
|
||
end
|
||
|
||
context "replying without key is allowed" do
|
||
let!(:group) { Fabricate(:group, incoming_email: "team@bar.com") }
|
||
let!(:topic) do
|
||
SiteSetting.find_related_post_with_key = false
|
||
process(:email_reply_1)
|
||
Topic.last
|
||
end
|
||
|
||
context "when the topic was deleted" do
|
||
before do
|
||
topic.update_columns(deleted_at: 1.day.ago)
|
||
end
|
||
|
||
include_examples "no staged users", :email_reply_staged, Email::Receiver::TopicNotFoundError
|
||
end
|
||
|
||
context "when the topic was closed" do
|
||
before do
|
||
topic.update_columns(closed: true)
|
||
end
|
||
|
||
include_examples "no staged users", :email_reply_staged, Email::Receiver::TopicClosedError
|
||
end
|
||
|
||
context "when they aren't allowed to like a post" do
|
||
before do
|
||
topic.update_columns(archived: true)
|
||
end
|
||
|
||
include_examples "no staged users", :email_reply_like, Email::Receiver::InvalidPostAction
|
||
end
|
||
end
|
||
|
||
it "does not remove the incoming email record when staged users are deleted" do
|
||
expect { process(:bad_destinations) }.to change { IncomingEmail.count }
|
||
.and raise_error(Email::Receiver::BadDestinationAddress)
|
||
expect(IncomingEmail.last.message_id).to eq("9@foo.bar.mail")
|
||
end
|
||
end
|
||
|
||
context "mailing list mirror" do
|
||
let!(:category) { Fabricate(:mailinglist_mirror_category) }
|
||
|
||
before do
|
||
SiteSetting.block_auto_generated_emails = true
|
||
SiteSetting.find_related_post_with_key = true
|
||
end
|
||
|
||
it "should allow creating topic even when email is autogenerated" do
|
||
expect { process(:mailinglist) }.to change { Topic.count }
|
||
expect(IncomingEmail.last.is_auto_generated).to eq(false)
|
||
end
|
||
|
||
it "should allow replying without reply key" do
|
||
process(:mailinglist)
|
||
topic = Topic.last
|
||
|
||
expect { process(:mailinglist_reply) }.to change { topic.posts.count }
|
||
end
|
||
|
||
it "should skip validations for staged users" do
|
||
Fabricate(:user, email: "alice@foo.com", staged: true)
|
||
expect { process(:mailinglist_short_message) }.to change { Topic.count }
|
||
end
|
||
|
||
it "should skip validations for regular users" do
|
||
Fabricate(:user, email: "alice@foo.com")
|
||
expect { process(:mailinglist_short_message) }.to change { Topic.count }
|
||
end
|
||
|
||
context "read-only category" do
|
||
before do
|
||
category.set_permissions(everyone: :readonly)
|
||
category.save
|
||
|
||
Fabricate(:user, email: "alice@foo.com")
|
||
Fabricate(:user, email: "bob@bar.com")
|
||
end
|
||
|
||
it "should allow creating topic within read-only category" do
|
||
expect { process(:mailinglist) }.to change { Topic.count }
|
||
end
|
||
|
||
it "should allow replying within read-only category" do
|
||
process(:mailinglist)
|
||
topic = Topic.last
|
||
|
||
expect { process(:mailinglist_reply) }.to change { topic.posts.count }
|
||
end
|
||
end
|
||
end
|
||
|
||
it "tries to fix unparsable email addresses in To and CC headers" do
|
||
expect { process(:unparsable_email_addresses) }.to raise_error(Email::Receiver::BadDestinationAddress)
|
||
|
||
email = IncomingEmail.last
|
||
expect(email.to_addresses).to eq("foo@bar.com")
|
||
expect(email.cc_addresses).to eq("bob@example.com;carol@example.com")
|
||
end
|
||
end
|