This commit is contained in:
2022-12-30 16:23:27 +00:00
parent 02776e8478
commit 20da343c54
1304 changed files with 870224 additions and 0 deletions

View File

View File

@ -0,0 +1,111 @@
import random
from email.message import Message
import pytest
from app.config import (
ALERT_COMPLAINT_FORWARD_PHASE,
ALERT_COMPLAINT_REPLY_PHASE,
ALERT_COMPLAINT_TRANSACTIONAL_PHASE,
POSTMASTER,
)
from app.db import Session
from app.email_utils import generate_verp_email
from app.handler.provider_complaint import (
handle_hotmail_complaint,
handle_yahoo_complaint,
)
from app.mail_sender import mail_sender
from app.models import (
Alias,
ProviderComplaint,
SentAlert,
EmailLog,
VerpType,
Contact,
)
from tests.utils import create_new_user, load_eml_file
origins = [
[handle_yahoo_complaint, "yahoo"],
[handle_hotmail_complaint, "hotmail"],
]
def prepare_complaint(
provider_name: str, alias: Alias, rcpt_address: str, sender_address: str
) -> Message:
contact = Contact.create(
user_id=alias.user.id,
alias_id=alias.id,
website_email=f"contact{random.random()}@mailbox.test",
reply_email="d@e.f",
commit=True,
)
elog = EmailLog.create(
user_id=alias.user.id,
mailbox_id=alias.user.default_mailbox_id,
contact_id=contact.id,
commit=True,
bounced=True,
)
return_path = generate_verp_email(VerpType.bounce_forward, elog.id)
return load_eml_file(
f"{provider_name}_complaint.eml",
{
"postmaster": POSTMASTER,
"return_path": return_path,
"rcpt": rcpt_address,
"sender": sender_address,
"rcpt_comma_list": f"{rcpt_address},other_rcpt@somwhere.net",
},
)
@mail_sender.store_emails_test_decorator
@pytest.mark.parametrize("handle_ftor,provider", origins)
def test_provider_to_user(flask_client, handle_ftor, provider):
user = create_new_user()
alias = Alias.create_new_random(user)
Session.commit()
complaint = prepare_complaint(provider, alias, user.email, "nobody@nowhere.net")
assert handle_ftor(complaint)
found = ProviderComplaint.filter_by(user_id=user.id).all()
assert len(found) == 0
alerts = SentAlert.filter_by(user_id=user.id).all()
assert len(alerts) == 1
sent_mails = mail_sender.get_stored_emails()
assert len(sent_mails) == 1
assert alerts[0].alert_type == f"{ALERT_COMPLAINT_TRANSACTIONAL_PHASE}_{provider}"
@pytest.mark.parametrize("handle_ftor,provider", origins)
def test_provider_forward_phase(flask_client, handle_ftor, provider):
user = create_new_user()
alias = Alias.create_new_random(user)
Session.commit()
complaint = prepare_complaint(provider, alias, "nobody@nowhere.net", alias.email)
assert handle_ftor(complaint)
found = ProviderComplaint.filter_by(user_id=user.id).all()
assert len(found) == 1
alerts = SentAlert.filter_by(user_id=user.id).all()
assert len(alerts) == 1
assert alerts[0].alert_type == f"{ALERT_COMPLAINT_REPLY_PHASE}_{provider}"
@mail_sender.store_emails_test_decorator
@pytest.mark.parametrize("handle_ftor,provider", origins)
def test_provider_reply_phase(flask_client, handle_ftor, provider):
mail_sender.store_emails_instead_of_sending()
mail_sender.purge_stored_emails()
user = create_new_user()
alias = Alias.create_new_random(user)
Session.commit()
complaint = prepare_complaint(provider, alias, alias.email, "no@no.no")
assert handle_ftor(complaint)
found = ProviderComplaint.filter_by(user_id=user.id).all()
assert len(found) == 0
alerts = SentAlert.filter_by(user_id=user.id).all()
assert len(alerts) == 1
sent_mails = mail_sender.get_stored_emails()
assert len(sent_mails) == 1
assert alerts[0].alert_type == f"{ALERT_COMPLAINT_FORWARD_PHASE}_{provider}"

View File

@ -0,0 +1,46 @@
from app.handler.spamd_result import DmarcCheckResult, SpamdResult
from tests.utils import load_eml_file
def test_dmarc_result_softfail():
msg = load_eml_file("dmarc_gmail_softfail.eml")
assert DmarcCheckResult.soft_fail == SpamdResult.extract_from_headers(msg).dmarc
assert SpamdResult.extract_from_headers(msg).rspamd_score == 0.5
def test_dmarc_result_quarantine():
msg = load_eml_file("dmarc_quarantine.eml")
assert DmarcCheckResult.quarantine == SpamdResult.extract_from_headers(msg).dmarc
def test_dmarc_result_reject():
msg = load_eml_file("dmarc_reject.eml")
assert DmarcCheckResult.reject == SpamdResult.extract_from_headers(msg).dmarc
def test_dmarc_result_allow():
msg = load_eml_file("dmarc_allow.eml")
assert DmarcCheckResult.allow == SpamdResult.extract_from_headers(msg).dmarc
def test_dmarc_result_na():
msg = load_eml_file("dmarc_na.eml")
assert DmarcCheckResult.not_available == SpamdResult.extract_from_headers(msg).dmarc
def test_dmarc_result_bad_policy():
msg = load_eml_file("dmarc_bad_policy.eml")
assert SpamdResult._get_from_message(msg) is None
assert DmarcCheckResult.bad_policy == SpamdResult.extract_from_headers(msg).dmarc
assert SpamdResult._get_from_message(msg) is not None
def test_parse_rspamd_score():
msg = load_eml_file("dmarc_gmail_softfail.eml")
assert SpamdResult.extract_from_headers(msg).rspamd_score == 0.5
def test_cannot_parse_rspamd_score():
msg = load_eml_file("dmarc_cannot_parse_rspamd_score.eml")
# use the default score when cannot parse
assert SpamdResult.extract_from_headers(msg).rspamd_score == -1

View File

@ -0,0 +1,116 @@
import pytest
from app import config
from app.handler.unsubscribe_encoder import (
UnsubscribeData,
UnsubscribeAction,
UnsubscribeEncoder,
UnsubscribeOriginalData,
)
legacy_subject_test_data = [
("3=", UnsubscribeData(UnsubscribeAction.DisableAlias, 3)),
("438_", UnsubscribeData(UnsubscribeAction.DisableContact, 438)),
("4325*", UnsubscribeData(UnsubscribeAction.UnsubscribeNewsletter, 4325)),
]
@pytest.mark.parametrize("expected_subject, expected_deco", legacy_subject_test_data)
def test_legacy_unsub_subject(expected_subject, expected_deco):
info = UnsubscribeEncoder.decode_subject(expected_subject)
assert info == expected_deco
legacy_url_test_data = [
(
f"{config.URL}/dashboard/unsubscribe/3",
UnsubscribeData(UnsubscribeAction.DisableAlias, 3),
),
(
f"{config.URL}/dashboard/block_contact/5",
UnsubscribeData(UnsubscribeAction.DisableContact, 5),
),
]
@pytest.mark.parametrize("expected_url, unsub_data", legacy_url_test_data)
def test_encode_decode_unsub_subject(expected_url, unsub_data):
url = UnsubscribeEncoder.encode_url(unsub_data.action, unsub_data.data)
assert expected_url == url
legacy_mail_or_link_test_data = [
(
f"{config.URL}/dashboard/unsubscribe/3",
False,
UnsubscribeData(UnsubscribeAction.DisableAlias, 3),
),
(
"mailto:me@nowhere.net?subject=un.WzIsIDld.ONeJMiTW6CosJg4PMR1MPcDs-6GWoTOQFMfA2A",
True,
UnsubscribeData(UnsubscribeAction.DisableAlias, 9),
),
(
f"{config.URL}/dashboard/block_contact/8",
False,
UnsubscribeData(UnsubscribeAction.DisableContact, 8),
),
(
"mailto:me@nowhere.net?subject=un.WzMsIDhd.eo_Ynk0eNyPtsHXMpTqw7HMFgYmm1Up_wWUc3g",
True,
UnsubscribeData(UnsubscribeAction.DisableContact, 8),
),
(
"mailto:me@nowhere.net?subject=un.WzEsIDgzXQ.NZAWqfpCmLEszwc5nWuQwDSLJ3TXO3rcOe_73Q",
True,
UnsubscribeData(UnsubscribeAction.UnsubscribeNewsletter, 83),
),
(
f"{config.URL}/dashboard/unsubscribe/encoded?data=un.WzQsIFswLCAxLCAiYUBiLmMiLCAic3ViamVjdCJdXQ.aU3T5XNzJIG4LDm6-pqJk4vxxJxpgVYzc9MEFQ",
False,
UnsubscribeData(
UnsubscribeAction.OriginalUnsubscribeMailto,
UnsubscribeOriginalData(1, "a@b.c", "subject"),
),
),
(
"mailto:me@nowhere.net?subject=un.WzQsIFswLCAxLCAiYUBiLmMiLCAic3ViamVjdCJdXQ.aU3T5XNzJIG4LDm6-pqJk4vxxJxpgVYzc9MEFQ",
True,
UnsubscribeData(
UnsubscribeAction.OriginalUnsubscribeMailto,
UnsubscribeOriginalData(1, "a@b.c", "subject"),
),
),
]
@pytest.mark.parametrize(
"expected_link, via_mail, unsub_data", legacy_mail_or_link_test_data
)
def test_encode_legacy_link(expected_link, via_mail, unsub_data):
if via_mail:
config.UNSUBSCRIBER = "me@nowhere.net"
else:
config.UNSUBSCRIBER = None
link_info = UnsubscribeEncoder.encode(unsub_data.action, unsub_data.data)
assert via_mail == link_info.via_email
assert expected_link == link_info.link
encode_decode_test_data = [
UnsubscribeData(UnsubscribeAction.DisableContact, 3),
UnsubscribeData(UnsubscribeAction.DisableContact, 10),
UnsubscribeData(UnsubscribeAction.DisableAlias, 101),
UnsubscribeData(
UnsubscribeAction.OriginalUnsubscribeMailto,
UnsubscribeOriginalData(323, "a@b.com", "some subject goes here"),
),
]
@pytest.mark.parametrize("unsub_data", encode_decode_test_data)
def test_encode_decode_unsub(unsub_data):
encoded = UnsubscribeEncoder.encode_subject(unsub_data.action, unsub_data.data)
decoded = UnsubscribeEncoder.decode_subject(encoded)
assert unsub_data.action == decoded.action
assert unsub_data.data == decoded.data

View File

@ -0,0 +1,206 @@
from email.message import Message
from typing import Iterable
import pytest
from app import config
from app.db import Session
from app.email import headers
from app.handler.unsubscribe_encoder import (
UnsubscribeAction,
UnsubscribeEncoder,
UnsubscribeOriginalData,
)
from app.handler.unsubscribe_generator import UnsubscribeGenerator
from app.models import Alias, Contact, UnsubscribeBehaviourEnum
from tests.utils import create_new_user
TEST_UNSUB_EMAIL = "unsub@sl.com"
def generate_unsub_block_contact_data() -> Iterable:
user = create_new_user()
user.unsub_behaviour = UnsubscribeBehaviourEnum.BlockContact
alias = Alias.create_new_random(user)
Session.commit()
contact = Contact.create(
user_id=user.id,
alias_id=alias.id,
website_email="contact@example.com",
reply_email="rep@sl.local",
commit=True,
)
subject = UnsubscribeEncoder.encode_subject(
UnsubscribeAction.DisableContact, contact.id
)
yield (
alias.id,
contact.id,
True,
"<https://lol.com>, <mailto:somewhere@not.net>",
f"<mailto:{TEST_UNSUB_EMAIL}?subject={subject}>",
)
yield (
alias.id,
contact.id,
False,
"<https://lol.com>, <mailto:somewhere@not.net>",
f"<{config.URL}/dashboard/block_contact/{contact.id}>",
)
yield (
alias.id,
contact.id,
False,
None,
f"<{config.URL}/dashboard/block_contact/{contact.id}>",
)
@pytest.mark.parametrize(
"alias_id, contact_id, unsub_via_mail, original_header, expected_header",
generate_unsub_block_contact_data(),
)
def test_unsub_disable_contact(
alias_id, contact_id, unsub_via_mail, original_header, expected_header
):
alias = Alias.get(alias_id)
contact = Contact.get(contact_id)
config.UNSUBSCRIBER = TEST_UNSUB_EMAIL if unsub_via_mail else None
message = Message()
message[headers.LIST_UNSUBSCRIBE] = original_header
message = UnsubscribeGenerator().add_header_to_message(alias, contact, message)
assert expected_header == message[headers.LIST_UNSUBSCRIBE]
if not expected_header or expected_header.find("<http") == -1:
assert message[headers.LIST_UNSUBSCRIBE_POST] is None
else:
assert "List-Unsubscribe=One-Click" == message[headers.LIST_UNSUBSCRIBE_POST]
def generate_unsub_disable_alias_data() -> Iterable:
user = create_new_user()
user.unsub_behaviour = UnsubscribeBehaviourEnum.DisableAlias
alias = Alias.create_new_random(user)
Session.commit()
contact = Contact.create(
user_id=user.id,
alias_id=alias.id,
website_email="contact@example.com",
reply_email="rep@sl.local",
commit=True,
)
subject = UnsubscribeEncoder.encode_subject(
UnsubscribeAction.DisableAlias, alias.id
)
yield (
alias.id,
contact.id,
True,
"<https://lol.com>, <mailto:somewhere@not.net>",
f"<mailto:{TEST_UNSUB_EMAIL}?subject={subject}>",
)
yield (
alias.id,
contact.id,
False,
"<https://lol.com>, <mailto:somewhere@not.net>",
f"<{config.URL}/dashboard/unsubscribe/{alias.id}>",
)
yield (
alias.id,
contact.id,
False,
None,
f"<{config.URL}/dashboard/unsubscribe/{alias.id}>",
)
@pytest.mark.parametrize(
"alias_id, contact_id, unsub_via_mail, original_header, expected_header",
generate_unsub_disable_alias_data(),
)
def test_unsub_disable_alias(
alias_id, contact_id, unsub_via_mail, original_header, expected_header
):
alias = Alias.get(alias_id)
contact = Contact.get(contact_id)
config.UNSUBSCRIBER = TEST_UNSUB_EMAIL if unsub_via_mail else None
message = Message()
message[headers.LIST_UNSUBSCRIBE] = original_header
message = UnsubscribeGenerator().add_header_to_message(alias, contact, message)
assert expected_header == message[headers.LIST_UNSUBSCRIBE]
if not expected_header or expected_header.find("<http") == -1:
assert message[headers.LIST_UNSUBSCRIBE_POST] is None
else:
assert "List-Unsubscribe=One-Click" == message[headers.LIST_UNSUBSCRIBE_POST]
def generate_unsub_preserve_original_data() -> Iterable:
user = create_new_user()
user.unsub_behaviour = UnsubscribeBehaviourEnum.PreserveOriginal
alias = Alias.create_new_random(user)
Session.commit()
contact = Contact.create(
user_id=user.id,
alias_id=alias.id,
website_email="contact@example.com",
reply_email="rep@sl.local",
commit=True,
)
yield (
alias.id,
contact.id,
True,
"<https://lol.com>, <mailto:somewhere@not.net>",
"<https://lol.com>",
)
yield (
alias.id,
contact.id,
False,
"<https://lol.com>, <mailto:somewhere@not.net>",
"<https://lol.com>",
)
unsub_data = UnsubscribeEncoder.encode_subject(
UnsubscribeAction.OriginalUnsubscribeMailto,
UnsubscribeOriginalData(alias.id, "test@test.com", "hello"),
)
yield (
alias.id,
contact.id,
True,
"<mailto:test@test.com?subject=hello>",
f"<mailto:{TEST_UNSUB_EMAIL}?subject={unsub_data}>",
)
yield (
alias.id,
contact.id,
False,
"<mailto:test@test.com?subject=hello>",
f"<{config.URL}/dashboard/unsubscribe/encoded?data={unsub_data}>",
)
yield (alias.id, contact.id, True, None, None)
yield (alias.id, contact.id, False, None, None)
@pytest.mark.parametrize(
"alias_id, contact_id, unsub_via_mail, original_header, expected_header",
generate_unsub_preserve_original_data(),
)
def test_unsub_preserve_original(
alias_id, contact_id, unsub_via_mail, original_header, expected_header
):
alias = Alias.get(alias_id)
contact = Contact.get(contact_id)
config.UNSUBSCRIBER = TEST_UNSUB_EMAIL if unsub_via_mail else None
message = Message()
message[headers.LIST_UNSUBSCRIBE] = original_header
message = UnsubscribeGenerator().add_header_to_message(alias, contact, message)
assert expected_header == message[headers.LIST_UNSUBSCRIBE]
if not expected_header or expected_header.find("<http") == -1:
assert message[headers.LIST_UNSUBSCRIBE_POST] is None
else:
assert "List-Unsubscribe=One-Click" == message[headers.LIST_UNSUBSCRIBE_POST]

View File

@ -0,0 +1,231 @@
from email.message import Message
from random import random
from aiosmtpd.smtp import Envelope
from flask import url_for
from app.db import Session
from app.email import headers, status
from app.email_utils import parse_full_address
from app.handler.unsubscribe_encoder import (
UnsubscribeEncoder,
UnsubscribeAction,
UnsubscribeOriginalData,
)
from app.handler.unsubscribe_handler import (
UnsubscribeHandler,
)
from app.mail_sender import mail_sender
from app.models import Alias, Contact, User
from tests.utils import create_new_user, login
def _get_envelope_and_message(user: User, subject: str) -> (Envelope, Message):
envelope = Envelope()
envelope.mail_from = user.email
message = Message()
message[headers.SUBJECT] = subject
return envelope, message
@mail_sender.store_emails_test_decorator
def test_old_subject_disable_alias():
user = create_new_user()
alias = Alias.create_new_random(user)
Session.commit()
envelope, message = _get_envelope_and_message(user, f"{alias.id}=")
response = UnsubscribeHandler().handle_unsubscribe_from_message(envelope, message)
assert status.E202 == response
assert not Alias.get(alias.id).enabled
assert 1 == len(mail_sender.get_stored_emails())
@mail_sender.store_emails_test_decorator
def test_old_subject_block_contact():
user = create_new_user()
alias = Alias.create_new_random(user)
Session.commit()
contact = Contact.create(
user_id=user.id,
alias_id=alias.id,
website_email="contact@example.com",
reply_email=f"{random()}@sl.local",
block_forward=False,
commit=True,
)
envelope, message = _get_envelope_and_message(user, f"{contact.id}_")
response = UnsubscribeHandler().handle_unsubscribe_from_message(envelope, message)
assert status.E202 == response
assert Contact.get(contact.id).block_forward
assert 1 == len(mail_sender.get_stored_emails())
@mail_sender.store_emails_test_decorator
def test_old_subject_disable_newsletter():
user = create_new_user()
envelope, message = _get_envelope_and_message(user, f"{user.id}*")
response = UnsubscribeHandler().handle_unsubscribe_from_message(envelope, message)
assert status.E202 == response
assert not User.get(user.id).notification
assert 1 == len(mail_sender.get_stored_emails())
@mail_sender.store_emails_test_decorator
def test_new_subject_disable_alias():
user = create_new_user()
alias = Alias.create_new_random(user)
Session.commit()
header = UnsubscribeEncoder.encode_subject(UnsubscribeAction.DisableAlias, alias.id)
envelope, message = _get_envelope_and_message(user, header)
response = UnsubscribeHandler().handle_unsubscribe_from_message(envelope, message)
assert status.E202 == response
assert not Alias.get(alias.id).enabled
assert 1 == len(mail_sender.get_stored_emails())
@mail_sender.store_emails_test_decorator
def test_new_subject_block_contact():
user = create_new_user()
alias = Alias.create_new_random(user)
Session.commit()
contact = Contact.create(
user_id=user.id,
alias_id=alias.id,
website_email="contact@example.com",
reply_email=f"{random()}@sl.local",
block_forward=False,
commit=True,
)
header = UnsubscribeEncoder.encode_subject(
UnsubscribeAction.DisableContact, contact.id
)
envelope, message = _get_envelope_and_message(user, header)
response = UnsubscribeHandler().handle_unsubscribe_from_message(envelope, message)
assert status.E202 == response
assert Contact.get(contact.id).block_forward
assert 1 == len(mail_sender.get_stored_emails())
@mail_sender.store_emails_test_decorator
def test_new_subject_disable_newsletter():
user = create_new_user()
header = UnsubscribeEncoder.encode_subject(
UnsubscribeAction.UnsubscribeNewsletter, user.id
)
envelope, message = _get_envelope_and_message(user, header)
response = UnsubscribeHandler().handle_unsubscribe_from_message(envelope, message)
assert status.E202 == response
assert not User.get(user.id).notification
assert 1 == len(mail_sender.get_stored_emails())
@mail_sender.store_emails_test_decorator
def test_new_subject_original_unsub():
user = create_new_user()
alias = Alias.create_new_random(user)
Session.commit()
envelope = Envelope()
envelope.mail_from = user.email
message = Message()
original_recipient = f"{random()}@out.com"
original_subject = f"Unsubsomehow{random()}"
message[headers.SUBJECT] = UnsubscribeEncoder.encode_subject(
UnsubscribeAction.OriginalUnsubscribeMailto,
UnsubscribeOriginalData(alias.id, original_recipient, original_subject),
)
response = UnsubscribeHandler().handle_unsubscribe_from_message(envelope, message)
assert status.E202 == response
assert 1 == len(mail_sender.get_stored_emails())
mail_sent = mail_sender.get_stored_emails()[0]
assert mail_sent.envelope_to == original_recipient
name, address = parse_full_address(mail_sent.msg[headers.FROM])
assert name == ""
assert alias.email == address
assert mail_sent.msg[headers.TO] == original_recipient
assert mail_sent.msg[headers.SUBJECT] == original_subject
@mail_sender.store_emails_test_decorator
def test_request_disable_alias(flask_client):
user = login(flask_client)
alias = Alias.create_new_random(user)
Session.commit()
req_data = UnsubscribeEncoder.encode_subject(
UnsubscribeAction.DisableAlias, alias.id
)
req = flask_client.get(
url_for("dashboard.encoded_unsubscribe", encoded_request=req_data),
follow_redirects=True,
)
assert 200 == req.status_code
assert not Alias.get(alias.id).enabled
assert 1 == len(mail_sender.get_stored_emails())
@mail_sender.store_emails_test_decorator
def test_request_disable_contact(flask_client):
user = login(flask_client)
alias = Alias.create_new_random(user)
Session.commit()
contact = Contact.create(
user_id=user.id,
alias_id=alias.id,
website_email="contact@example.com",
reply_email=f"{random()}@sl.local",
block_forward=False,
commit=True,
)
req_data = UnsubscribeEncoder.encode_subject(
UnsubscribeAction.DisableContact, contact.id
)
req = flask_client.get(
url_for("dashboard.encoded_unsubscribe", encoded_request=req_data),
follow_redirects=True,
)
assert 200 == req.status_code
assert Contact.get(contact.id).block_forward
assert 1 == len(mail_sender.get_stored_emails())
@mail_sender.store_emails_test_decorator
def test_request_disable_newsletter(flask_client):
user = login(flask_client)
req_data = UnsubscribeEncoder.encode_subject(
UnsubscribeAction.UnsubscribeNewsletter, user.id
)
req = flask_client.get(
url_for("dashboard.encoded_unsubscribe", encoded_request=req_data),
follow_redirects=True,
)
assert 200 == req.status_code
assert not User.get(user.id).notification
assert 1 == len(mail_sender.get_stored_emails())
@mail_sender.store_emails_test_decorator
def test_request_original_unsub(flask_client):
user = login(flask_client)
alias = Alias.create_new_random(user)
Session.commit()
original_recipient = f"{random()}@out.com"
original_subject = f"Unsubsomehow{random()}"
mail_sender.purge_stored_emails()
req_data = UnsubscribeEncoder.encode_subject(
UnsubscribeAction.OriginalUnsubscribeMailto,
UnsubscribeOriginalData(alias.id, original_recipient, original_subject),
)
req = flask_client.get(
url_for("dashboard.encoded_unsubscribe", encoded_request=req_data),
follow_redirects=True,
)
assert 200 == req.status_code
assert 1 == len(mail_sender.get_stored_emails())
mail_sent = mail_sender.get_stored_emails()[0]
assert mail_sent.envelope_to == original_recipient
name, address = parse_full_address(mail_sent.msg[headers.FROM])
assert name == ""
assert alias.email == address
assert mail_sent.msg[headers.TO] == original_recipient
assert mail_sent.msg[headers.SUBJECT] == original_subject