Add disposable/high-risk email signup scrutiny via check-mail.org
Classifies each signup's email domain (domain-only, GDPR-friendly) alongside the existing IP scrutiny signal, with matching held-welcome and auto email_domain_block behavior. A report against an account with a flagged domain suspends immediately (no reporter-count threshold) and blocks the domain, classified live from the report payload rather than any signup-time record so it also covers pre-existing accounts. Ships CHECK_MAIL_DRY_RUN=true by default, independent of ABUSE_DRY_RUN, so the new report-triggered suspend path stays inert until watched.
This commit is contained in:
+99
-4
@@ -96,8 +96,8 @@ def routing_tests():
|
||||
})
|
||||
assert r.status_code == 200, r.text
|
||||
assert r.json().get("report") == "55", r.json()
|
||||
# (report_id, target_id, acct, is_local, suspended, silenced, privileged)
|
||||
assert reports == [("55", "777", "spammer", True, False, False, False)], reports
|
||||
# (report_id, target_id, acct, is_local, suspended, silenced, privileged, email)
|
||||
assert reports == [("55", "777", "spammer", True, False, False, False, "")], reports
|
||||
|
||||
# 5b. report against a staff account -> privileged=True extracted from role
|
||||
r = post({
|
||||
@@ -112,7 +112,7 @@ def routing_tests():
|
||||
},
|
||||
},
|
||||
})
|
||||
assert reports[-1] == ("56", "778", "waffles", True, False, False, True), reports[-1]
|
||||
assert reports[-1] == ("56", "778", "waffles", True, False, False, True, ""), reports[-1]
|
||||
|
||||
# 6. account.approved also welcomes — but dedup means alice (id 1001,
|
||||
# already welcomed via account.created above) is not welcomed twice.
|
||||
@@ -267,7 +267,7 @@ def ip_scrutiny_tests():
|
||||
main.process_signup("103", "dc2", "198.51.100.21")
|
||||
assert ("103", "dc2") in sent, "dry-run must not hold the welcome"
|
||||
assert ipblocks == [], "dry-run must not write an ip_block"
|
||||
assert any(d.startswith("[DRY-RUN]") for d in dms), dms
|
||||
assert any("[DRY-RUN]" in d for d in dms), dms
|
||||
|
||||
# D. abuse threshold override: a flagged account needs only
|
||||
# IP_SCRUTINY_ABUSE_THRESHOLD (1) distinct reporter to silence, vs. the
|
||||
@@ -293,8 +293,103 @@ def ip_scrutiny_tests():
|
||||
assert ("101", "silence") not in actions, actions
|
||||
|
||||
|
||||
def email_scrutiny_tests():
|
||||
"""Drive process_signup's email-domain path + the report-triggered
|
||||
suspend+domain-block override, network stubbed out."""
|
||||
sent = []
|
||||
dms = []
|
||||
domain_blocks = []
|
||||
main.send_welcome = lambda account_id, acct: sent.append((account_id, acct))
|
||||
main.dm_moderator = lambda message: dms.append(message)
|
||||
main.register_email_domain_block = lambda domain, acct: domain_blocks.append((domain, acct))
|
||||
|
||||
classifications = {
|
||||
"gmail.com": (False, 5),
|
||||
"temp-mail.org": (True, 99),
|
||||
"sketchy-disposable.example": (True, 95),
|
||||
}
|
||||
main.classify_email_domain = lambda domain: classifications[domain]
|
||||
|
||||
main.CHECK_MAIL_ENABLED = True
|
||||
main.CHECK_MAIL_API_KEY = "test-key" # required for the signup-path gate
|
||||
main.IP_SCRUTINY_ENABLED = False # isolate the email-only signal
|
||||
|
||||
# A. clean domain -> welcomed immediately, no domain-block, not flagged.
|
||||
main.CHECK_MAIL_DRY_RUN = False
|
||||
main.CHECK_MAIL_HOLD_WELCOME = True
|
||||
main.CHECK_MAIL_AUTO_DOMAIN_BLOCK = True
|
||||
main.process_signup("201", "clean1", "", "clean1@gmail.com")
|
||||
assert ("201", "clean1") in sent, sent
|
||||
assert domain_blocks == [], domain_blocks
|
||||
assert main.get_signup_email("201") == ("gmail.com", False)
|
||||
|
||||
# B. disposable domain, HOLD_WELCOME + AUTO_DOMAIN_BLOCK, live -> welcome
|
||||
# held, domain blocked, moderator DM'd; account.approved later releases
|
||||
# the welcome.
|
||||
sent.clear(); dms.clear(); domain_blocks.clear()
|
||||
main.process_signup("202", "spam1", "", "spam1@temp-mail.org")
|
||||
assert ("202", "spam1") not in sent, "welcome should be held for a flagged signup"
|
||||
assert domain_blocks == [("temp-mail.org", "spam1")], domain_blocks
|
||||
assert any("spam1" in d and "held" in d for d in dms), dms
|
||||
assert main.get_signup_email("202") == ("temp-mail.org", True)
|
||||
|
||||
main.send_welcome("202", "spam1") # simulate account.approved's unconditional welcome
|
||||
assert ("202", "spam1") in sent, sent
|
||||
|
||||
# C. same idea, but CHECK_MAIL_DRY_RUN -> welcomed immediately (no hold),
|
||||
# no domain-block write, DM carries the dry-run marker.
|
||||
sent.clear(); dms.clear(); domain_blocks.clear()
|
||||
main.CHECK_MAIL_DRY_RUN = True
|
||||
main.process_signup("203", "spam2", "", "spam2@sketchy-disposable.example")
|
||||
assert ("203", "spam2") in sent, "dry-run must not hold the welcome"
|
||||
assert domain_blocks == [], "dry-run must not write a domain block"
|
||||
assert any("[DRY-RUN]" in d for d in dms), dms
|
||||
|
||||
# D0. CHECK_MAIL_DRY_RUN alone holds the report-triggered suspend even
|
||||
# though ABUSE_DRY_RUN=False in this test env (set in the module
|
||||
# preamble) — the two dry-run switches are independent; either one
|
||||
# holding is enough to keep this brand-new action path inert.
|
||||
actions = []
|
||||
main.apply_action = lambda target_id, action, text: actions.append((target_id, action))
|
||||
main.dm_silenced_user = lambda acct: None
|
||||
domain_blocks.clear()
|
||||
|
||||
main.handle_report("R9", "997", "heldbydryrun", True, False, False, False,
|
||||
"heldbydryrun@temp-mail.org")
|
||||
assert ("997", "suspend") not in actions, actions
|
||||
assert domain_blocks == [], "CHECK_MAIL_DRY_RUN alone must prevent the domain block too"
|
||||
|
||||
# D. report-triggered override: ANY report against an account with a
|
||||
# high-risk email domain suspends immediately (no distinct-reporter
|
||||
# threshold) and blocks the domain — classified LIVE from the report
|
||||
# payload's target_account.email, independent of whatever (if
|
||||
# anything) process_signup recorded at signup time. This account was
|
||||
# never seen by process_signup at all.
|
||||
main.CHECK_MAIL_DRY_RUN = False
|
||||
actions.clear()
|
||||
domain_blocks.clear()
|
||||
|
||||
main.handle_report("R10", "999", "neversignedup", True, False, False, False,
|
||||
"neversignedup@temp-mail.org")
|
||||
assert ("999", "suspend") in actions, actions
|
||||
assert domain_blocks == [("temp-mail.org", "neversignedup")], domain_blocks
|
||||
|
||||
# E. report against an account with a clean email domain -> untouched by
|
||||
# this path, falls through to the normal reporter-count/tier policy.
|
||||
actions.clear(); domain_blocks.clear()
|
||||
noposts = {"has_posts": False, "young": False, "dormant": False,
|
||||
"newest_at": None, "oldest_seen": None, "truncated": False}
|
||||
main.classify_account = lambda tid: noposts
|
||||
main.count_distinct_reporters = lambda tid: 1
|
||||
main.handle_report("R11", "998", "clean2", True, False, False, False,
|
||||
"clean2@gmail.com")
|
||||
assert ("998", "suspend") not in actions, actions
|
||||
assert domain_blocks == [], domain_blocks
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
routing_tests()
|
||||
policy_tests()
|
||||
ip_scrutiny_tests()
|
||||
email_scrutiny_tests()
|
||||
print("ALL TESTS PASSED")
|
||||
|
||||
Reference in New Issue
Block a user