-
-
Notifications
You must be signed in to change notification settings - Fork 576
[Feature] Introduce sender-blacklists (on both global/admin and user level) #2694
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
chrisblech
wants to merge
17
commits into
simple-login:master
Choose a base branch
from
chrisblech:feature/user-blacklists
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
cc17c20
Add global sender blacklist (regex) with admin UI and SMTP blocking
lio-chrisblech 90bd0cb
Blacklist: only apply when no Contact exists; create disabled contact
lio-chrisblech 2b8f535
Refactor contact creation + add pattern help text
lio-chrisblech e298007
Add per-user sender blacklist
lio-chrisblech c39dca2
DB: add user_id to global_sender_blacklist
lio-chrisblech 96c1fbc
Fix sender blacklist UI text, show global entries, and make migration…
lio-chrisblech cfdbbfb
cleanup, only show active global entries
chrisblech 1aeab12
Rename GlobalSenderBlacklist model to ForbiddenEnvelopeSender
lio-chrisblech 0011360
Admin: show all forbidden envelope sender entries
lio-chrisblech 203f8dd
Sender blacklist: cache patterns 5min, use fullmatch and explicit types
lio-chrisblech 2ca7a77
Email handler: apply sender blacklist before creating contacts
lio-chrisblech 851f58e
Dashboard: comment, validation and audit log for sender blacklist
lio-chrisblech 28554a8
Deps: add cachetools
lio-chrisblech 1f6496e
Tests: update model name for sender blacklist
lio-chrisblech 43173d1
Adjust sender blacklist validation, contact precedence, and cache sizing
lio-chrisblech 32223c3
Merge pull request #3 from chrisblech/feature/user-blacklists-review-…
chrisblech 748227c
Merge branch 'master' into feature/user-blacklists
chrisblech File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| from flask_admin.form import SecureForm | ||
|
|
||
| from app.admin.base import SLModelView | ||
|
|
||
|
|
||
| class GlobalSenderBlacklistAdmin(SLModelView): | ||
| form_base_class = SecureForm | ||
|
|
||
| can_create = True | ||
| can_edit = True | ||
| can_delete = True | ||
|
|
||
| column_searchable_list = ("pattern", "comment") | ||
| column_filters = ("enabled", "user_id") | ||
| column_editable_list = ("enabled", "comment") | ||
|
|
||
| # Help text for admins when adding patterns | ||
| form_args = { | ||
| "pattern": { | ||
| "description": r"Regex, i.e. `@domain\.com$`", | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,89 @@ | ||
| from __future__ import annotations | ||
|
|
||
| from cachetools import TTLCache, cached | ||
chrisblech marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| from app.db import Session | ||
| from app.log import LOG | ||
| from app.models import ForbiddenEnvelopeSender | ||
| from app.regex_utils import regex_match | ||
|
|
||
|
|
||
| # Cache enabled patterns to avoid a DB query per inbound email. | ||
| # | ||
| # TTL: keep changes reasonably fresh while avoiding hammering the DB. | ||
| # Memory: cachetools.TTLCache is an in-process dict with an upper bound (maxsize). | ||
| _GLOBAL_PATTERNS_CACHE = TTLCache(maxsize=128, ttl=300) | ||
| _USER_PATTERNS_CACHE = TTLCache(maxsize=128, ttl=300) | ||
|
|
||
|
|
||
| @cached(cache=_GLOBAL_PATTERNS_CACHE) | ||
| def _get_enabled_global_patterns() -> list[str]: | ||
| return [ | ||
| r.pattern | ||
| for r in Session.query(ForbiddenEnvelopeSender) | ||
| .filter( | ||
| ForbiddenEnvelopeSender.enabled.is_(True), | ||
| ForbiddenEnvelopeSender.user_id.is_(None), | ||
| ) | ||
| .order_by(ForbiddenEnvelopeSender.id.asc()) | ||
| .all() | ||
| ] | ||
|
|
||
|
|
||
| # Per-user cache: avoid a DB query per email per user, but cap memory via maxsize. | ||
| @cached(cache=_USER_PATTERNS_CACHE) | ||
| def _get_enabled_user_patterns(user_id: int) -> list[str]: | ||
chrisblech marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return [ | ||
| r.pattern | ||
| for r in Session.query(ForbiddenEnvelopeSender) | ||
| .filter( | ||
| ForbiddenEnvelopeSender.enabled.is_(True), | ||
| ForbiddenEnvelopeSender.user_id == user_id, | ||
| ) | ||
| .order_by(ForbiddenEnvelopeSender.id.asc()) | ||
| .all() | ||
| ] | ||
|
|
||
|
|
||
| def is_sender_blocked_for_user(user_id: int | None, candidates: list[str]) -> bool: | ||
| """Return True if any candidate sender string matches: | ||
|
|
||
| - the global sender blacklist (user_id is NULL), OR | ||
| - the given user's sender blacklist (user_id matches) | ||
|
|
||
| Typical candidates: | ||
| - SMTP envelope MAIL FROM | ||
| - parsed header From address | ||
| """ | ||
|
|
||
| patterns: list[str] = [] | ||
| patterns.extend(_get_enabled_global_patterns()) | ||
| if user_id is not None: | ||
| patterns.extend(_get_enabled_user_patterns(int(user_id))) | ||
|
|
||
| if not patterns: | ||
| return False | ||
|
|
||
| for candidate in candidates: | ||
| if not candidate: | ||
| continue | ||
| # Ignore bounce/null reverse-path | ||
| if candidate == "<>": | ||
| continue | ||
|
|
||
| for pattern in patterns: | ||
| try: | ||
| # Full-string match to avoid false positives (partial hits). | ||
| if regex_match(pattern, candidate): | ||
| return True | ||
| except Exception: | ||
| # Never crash the SMTP handler because of a bad regex. | ||
| # (Global or user entry — both are user-provided.) | ||
| LOG.exception( | ||
| "Sender blacklist regex failed: user_id=%s pattern=%s candidate=%s", | ||
| user_id, | ||
| pattern, | ||
| candidate, | ||
| ) | ||
|
|
||
| return False | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.