black new version
This commit is contained in:
parent
bb6e2a35ca
commit
fdedc24358
|
@ -23,8 +23,7 @@ from app.models import (
|
|||
|
||||
|
||||
def try_auto_create(address: str) -> Optional[Alias]:
|
||||
"""Try to auto-create the alias using directory or catch-all domain
|
||||
"""
|
||||
"""Try to auto-create the alias using directory or catch-all domain"""
|
||||
alias = try_auto_create_catch_all_domain(address)
|
||||
if not alias:
|
||||
alias = try_auto_create_directory(address)
|
||||
|
@ -77,7 +76,8 @@ def try_auto_create_directory(address: str) -> Optional[Alias]:
|
|||
db.session.flush()
|
||||
for i in range(1, len(mailboxes)):
|
||||
AliasMailbox.create(
|
||||
alias_id=alias.id, mailbox_id=mailboxes[i].id,
|
||||
alias_id=alias.id,
|
||||
mailbox_id=mailboxes[i].id,
|
||||
)
|
||||
|
||||
db.session.commit()
|
||||
|
@ -127,7 +127,8 @@ def try_auto_create_catch_all_domain(address: str) -> Optional[Alias]:
|
|||
db.session.flush()
|
||||
for i in range(1, len(mailboxes)):
|
||||
AliasMailbox.create(
|
||||
alias_id=alias.id, mailbox_id=mailboxes[i].id,
|
||||
alias_id=alias.id,
|
||||
mailbox_id=mailboxes[i].id,
|
||||
)
|
||||
db.session.commit()
|
||||
return alias
|
||||
|
|
|
@ -195,7 +195,8 @@ def get_alias_infos_with_pagination_v3(
|
|||
func.sum(case([(EmailLog.is_reply, 1)], else_=0)).label("nb_reply"),
|
||||
func.sum(
|
||||
case(
|
||||
[(and_(EmailLog.is_reply == False, EmailLog.blocked), 1)], else_=0,
|
||||
[(and_(EmailLog.is_reply == False, EmailLog.blocked), 1)],
|
||||
else_=0,
|
||||
)
|
||||
).label("nb_blocked"),
|
||||
func.sum(
|
||||
|
@ -203,7 +204,8 @@ def get_alias_infos_with_pagination_v3(
|
|||
[
|
||||
(
|
||||
and_(
|
||||
EmailLog.is_reply == False, EmailLog.blocked == False,
|
||||
EmailLog.is_reply == False,
|
||||
EmailLog.blocked == False,
|
||||
),
|
||||
1,
|
||||
)
|
||||
|
|
|
@ -310,7 +310,8 @@ def verify_receipt(receipt_data, user, password) -> Optional[AppleSubscription]:
|
|||
# try sandbox_url
|
||||
LOG.warning("Use the sandbox url instead")
|
||||
r = requests.post(
|
||||
_SANDBOX_URL, json={"receipt-data": receipt_data, "password": password},
|
||||
_SANDBOX_URL,
|
||||
json={"receipt-data": receipt_data, "password": password},
|
||||
)
|
||||
|
||||
data = r.json()
|
||||
|
@ -466,7 +467,8 @@ def verify_receipt(receipt_data, user, password) -> Optional[AppleSubscription]:
|
|||
|
||||
if data["status"] != 0:
|
||||
LOG.warning(
|
||||
"verifyReceipt status !=0, probably invalid receipt. User %s", user,
|
||||
"verifyReceipt status !=0, probably invalid receipt. User %s",
|
||||
user,
|
||||
)
|
||||
return None
|
||||
|
||||
|
|
|
@ -288,7 +288,8 @@ def new_custom_alias_v3():
|
|||
|
||||
for i in range(1, len(mailboxes)):
|
||||
AliasMailbox.create(
|
||||
alias_id=alias.id, mailbox_id=mailboxes[i].id,
|
||||
alias_id=alias.id,
|
||||
mailbox_id=mailboxes[i].id,
|
||||
)
|
||||
|
||||
db.session.commit()
|
||||
|
|
|
@ -54,7 +54,9 @@ def register():
|
|||
# 'hostname': '127.0.0.1'}
|
||||
if not hcaptcha_res["success"]:
|
||||
LOG.warning(
|
||||
"User put wrong captcha %s %s", form.email.data, hcaptcha_res,
|
||||
"User put wrong captcha %s %s",
|
||||
form.email.data,
|
||||
hcaptcha_res,
|
||||
)
|
||||
flash("Wrong Captcha", "error")
|
||||
return render_template(
|
||||
|
|
|
@ -70,7 +70,8 @@ def get_contact_infos(alias: Alias, page=0, contact_id=None) -> [ContactInfo]:
|
|||
[
|
||||
(
|
||||
and_(
|
||||
EmailLog.is_reply == False, EmailLog.blocked == False,
|
||||
EmailLog.is_reply == False,
|
||||
EmailLog.blocked == False,
|
||||
),
|
||||
1,
|
||||
)
|
||||
|
@ -80,15 +81,28 @@ def get_contact_infos(alias: Alias, page=0, contact_id=None) -> [ContactInfo]:
|
|||
).label("nb_forward"),
|
||||
func.max(EmailLog.created_at).label("max_email_log_created_at"),
|
||||
)
|
||||
.join(EmailLog, EmailLog.contact_id == Contact.id, isouter=True,)
|
||||
.join(
|
||||
EmailLog,
|
||||
EmailLog.contact_id == Contact.id,
|
||||
isouter=True,
|
||||
)
|
||||
.filter(Contact.alias_id == alias.id)
|
||||
.group_by(Contact.id)
|
||||
.subquery()
|
||||
)
|
||||
|
||||
q = (
|
||||
db.session.query(Contact, EmailLog, sub.c.nb_reply, sub.c.nb_forward,)
|
||||
.join(EmailLog, EmailLog.contact_id == Contact.id, isouter=True,)
|
||||
db.session.query(
|
||||
Contact,
|
||||
EmailLog,
|
||||
sub.c.nb_reply,
|
||||
sub.c.nb_forward,
|
||||
)
|
||||
.join(
|
||||
EmailLog,
|
||||
EmailLog.contact_id == Contact.id,
|
||||
isouter=True,
|
||||
)
|
||||
.filter(Contact.alias_id == alias.id)
|
||||
.filter(Contact.id == sub.c.id)
|
||||
.filter(
|
||||
|
@ -167,7 +181,10 @@ def alias_contact_manager(alias_id):
|
|||
except Exception:
|
||||
flash(f"{contact_addr} is invalid", "error")
|
||||
return redirect(
|
||||
url_for("dashboard.alias_contact_manager", alias_id=alias_id,)
|
||||
url_for(
|
||||
"dashboard.alias_contact_manager",
|
||||
alias_id=alias_id,
|
||||
)
|
||||
)
|
||||
contact_email = contact_email.lower()
|
||||
|
||||
|
|
|
@ -143,7 +143,8 @@ def custom_alias():
|
|||
|
||||
for i in range(1, len(mailboxes)):
|
||||
AliasMailbox.create(
|
||||
alias_id=alias.id, mailbox_id=mailboxes[i].id,
|
||||
alias_id=alias.id,
|
||||
mailbox_id=mailboxes[i].id,
|
||||
)
|
||||
|
||||
db.session.commit()
|
||||
|
|
|
@ -111,7 +111,8 @@ def domain_detail_dns(custom_domain_id):
|
|||
custom_domain.dmarc_verified = False
|
||||
db.session.commit()
|
||||
flash(
|
||||
f"DMARC: The TXT record is not correctly set", "warning",
|
||||
f"DMARC: The TXT record is not correctly set",
|
||||
"warning",
|
||||
)
|
||||
dmarc_ok = False
|
||||
dmarc_errors = txt_records
|
||||
|
@ -207,7 +208,8 @@ def domain_detail_trash(custom_domain_id):
|
|||
DomainDeletedAlias.delete(deleted_alias.id)
|
||||
db.session.commit()
|
||||
flash(
|
||||
f"{deleted_alias.email} can now be re-created", "success",
|
||||
f"{deleted_alias.email} can now be re-created",
|
||||
"success",
|
||||
)
|
||||
|
||||
return redirect(
|
||||
|
|
|
@ -102,7 +102,10 @@ def index():
|
|||
flash("Unknown error, sorry for the inconvenience", "error")
|
||||
return redirect(
|
||||
url_for(
|
||||
"dashboard.index", query=query, sort=sort, filter=alias_filter,
|
||||
"dashboard.index",
|
||||
query=query,
|
||||
sort=sort,
|
||||
filter=alias_filter,
|
||||
)
|
||||
)
|
||||
|
||||
|
|
|
@ -13,9 +13,13 @@ def refused_email_route():
|
|||
if highlight_id:
|
||||
highlight_id = int(highlight_id)
|
||||
|
||||
email_logs: [EmailLog] = EmailLog.query.filter(
|
||||
EmailLog.user_id == current_user.id, EmailLog.refused_email_id != None
|
||||
).order_by(EmailLog.id.desc()).all()
|
||||
email_logs: [EmailLog] = (
|
||||
EmailLog.query.filter(
|
||||
EmailLog.user_id == current_user.id, EmailLog.refused_email_id != None
|
||||
)
|
||||
.order_by(EmailLog.id.desc())
|
||||
.all()
|
||||
)
|
||||
|
||||
# make sure the highlighted email_log is the first email_log
|
||||
highlight_index = None
|
||||
|
|
|
@ -403,8 +403,7 @@ def get_mx_domain_list(domain) -> [str]:
|
|||
|
||||
|
||||
def personal_email_already_used(email: str) -> bool:
|
||||
"""test if an email can be used as user email
|
||||
"""
|
||||
"""test if an email can be used as user email"""
|
||||
if User.get_by(email=email):
|
||||
return True
|
||||
|
||||
|
@ -490,11 +489,11 @@ def get_addrs_from_header(msg: Message, header) -> [str]:
|
|||
|
||||
def get_spam_info(msg: Message, max_score=None) -> (bool, str):
|
||||
"""parse SpamAssassin header to detect whether a message is classified as spam.
|
||||
Return (is spam, spam status detail)
|
||||
The header format is
|
||||
```X-Spam-Status: No, score=-0.1 required=5.0 tests=DKIM_SIGNED,DKIM_VALID,
|
||||
DKIM_VALID_AU,RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_H2,SPF_PASS,
|
||||
URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.2```
|
||||
Return (is spam, spam status detail)
|
||||
The header format is
|
||||
```X-Spam-Status: No, score=-0.1 required=5.0 tests=DKIM_SIGNED,DKIM_VALID,
|
||||
DKIM_VALID_AU,RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_H2,SPF_PASS,
|
||||
URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.2```
|
||||
"""
|
||||
spamassassin_status = msg["X-Spam-Status"]
|
||||
if not spamassassin_status:
|
||||
|
@ -505,11 +504,11 @@ def get_spam_info(msg: Message, max_score=None) -> (bool, str):
|
|||
|
||||
def get_spam_from_header(spam_status_header, max_score=None) -> (bool, str):
|
||||
"""get spam info from X-Spam-Status header
|
||||
Return (is spam, spam status detail).
|
||||
The spam_status_header has the following format
|
||||
```No, score=-0.1 required=5.0 tests=DKIM_SIGNED,DKIM_VALID,
|
||||
DKIM_VALID_AU,RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_H2,SPF_PASS,
|
||||
URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.2```
|
||||
Return (is spam, spam status detail).
|
||||
The spam_status_header has the following format
|
||||
```No, score=-0.1 required=5.0 tests=DKIM_SIGNED,DKIM_VALID,
|
||||
DKIM_VALID_AU,RCVD_IN_DNSWL_BLOCKED,RCVD_IN_MSPIKE_H2,SPF_PASS,
|
||||
URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.2```
|
||||
"""
|
||||
# yes or no
|
||||
spamassassin_answer = spam_status_header[: spam_status_header.find(",")]
|
||||
|
|
|
@ -17,14 +17,19 @@ def greylisting_needed_for_alias(alias: Alias) -> bool:
|
|||
nb_activity = (
|
||||
db.session.query(EmailLog)
|
||||
.join(Contact, EmailLog.contact_id == Contact.id)
|
||||
.filter(Contact.alias_id == alias.id, EmailLog.created_at > min_time,)
|
||||
.filter(
|
||||
Contact.alias_id == alias.id,
|
||||
EmailLog.created_at > min_time,
|
||||
)
|
||||
.group_by(EmailLog.id)
|
||||
.count()
|
||||
)
|
||||
|
||||
if nb_activity > MAX_ACTIVITY_DURING_MINUTE_PER_ALIAS:
|
||||
LOG.d(
|
||||
"Too much forward on alias %s. Nb Activity %s", alias, nb_activity,
|
||||
"Too much forward on alias %s. Nb Activity %s",
|
||||
alias,
|
||||
nb_activity,
|
||||
)
|
||||
return True
|
||||
|
||||
|
@ -39,7 +44,10 @@ def greylisting_needed_for_mailbox(alias: Alias) -> bool:
|
|||
db.session.query(EmailLog)
|
||||
.join(Contact, EmailLog.contact_id == Contact.id)
|
||||
.join(Alias, Contact.alias_id == Alias.id)
|
||||
.filter(Alias.mailbox_id == alias.mailbox_id, EmailLog.created_at > min_time,)
|
||||
.filter(
|
||||
Alias.mailbox_id == alias.mailbox_id,
|
||||
EmailLog.created_at > min_time,
|
||||
)
|
||||
.group_by(EmailLog.id)
|
||||
.count()
|
||||
)
|
||||
|
|
|
@ -608,7 +608,9 @@ class MfaBrowser(db.Model, ModelMixin):
|
|||
found = True
|
||||
|
||||
return MfaBrowser.create(
|
||||
user_id=user.id, token=token, expires=arrow.now().shift(days=30),
|
||||
user_id=user.id,
|
||||
token=token,
|
||||
expires=arrow.now().shift(days=30),
|
||||
)
|
||||
|
||||
@classmethod
|
||||
|
|
|
@ -34,7 +34,9 @@ def upload_from_bytesio(key: str, bs: BytesIO, content_type="string"):
|
|||
|
||||
else:
|
||||
_session.resource("s3").Bucket(BUCKET).put_object(
|
||||
Key=key, Body=bs, ContentType=content_type,
|
||||
Key=key,
|
||||
Body=bs,
|
||||
ContentType=content_type,
|
||||
)
|
||||
|
||||
|
||||
|
|
7
cron.py
7
cron.py
|
@ -151,8 +151,7 @@ class Stats:
|
|||
|
||||
|
||||
def stats_before(moment: Arrow) -> Stats:
|
||||
"""return the stats before a specific moment, ignoring all stats come from users in IGNORED_EMAILS
|
||||
"""
|
||||
"""return the stats before a specific moment, ignoring all stats come from users in IGNORED_EMAILS"""
|
||||
# nb user
|
||||
q = User.query
|
||||
for ie in IGNORED_EMAILS:
|
||||
|
@ -175,7 +174,9 @@ def stats_before(moment: Arrow) -> Stats:
|
|||
q = (
|
||||
db.session.query(EmailLog)
|
||||
.join(User, EmailLog.user_id == User.id)
|
||||
.filter(EmailLog.created_at < moment,)
|
||||
.filter(
|
||||
EmailLog.created_at < moment,
|
||||
)
|
||||
)
|
||||
for ie in IGNORED_EMAILS:
|
||||
q = q.filter(~User.email.contains(ie))
|
||||
|
|
|
@ -162,13 +162,18 @@ def get_or_create_contact(
|
|||
if contact:
|
||||
if contact.name != contact_name:
|
||||
LOG.d(
|
||||
"Update contact %s name %s to %s", contact, contact.name, contact_name,
|
||||
"Update contact %s name %s to %s",
|
||||
contact,
|
||||
contact.name,
|
||||
contact_name,
|
||||
)
|
||||
contact.name = contact_name
|
||||
db.session.commit()
|
||||
else:
|
||||
LOG.debug(
|
||||
"create contact for alias %s and contact %s", alias, contact_from_header,
|
||||
"create contact for alias %s and contact %s",
|
||||
alias,
|
||||
contact_from_header,
|
||||
)
|
||||
|
||||
reply_email = generate_reply_email()
|
||||
|
@ -355,7 +360,8 @@ def prepare_pgp_message(orig_msg: Message, pgp_fingerprint: str):
|
|||
|
||||
# Delete unnecessary headers in orig_msg except to save space
|
||||
delete_all_headers_except(
|
||||
orig_msg, _MIME_HEADERS,
|
||||
orig_msg,
|
||||
_MIME_HEADERS,
|
||||
)
|
||||
|
||||
first = MIMEApplication(
|
||||
|
@ -644,7 +650,10 @@ async def handle_reply(envelope, smtp: SMTP, msg: Message, rcpt_to: str) -> (boo
|
|||
# the "reply email"
|
||||
if mail_from == "<>":
|
||||
LOG.warning(
|
||||
"Bounce when sending to alias %s from %s, user %s", alias, contact, user,
|
||||
"Bounce when sending to alias %s from %s, user %s",
|
||||
alias,
|
||||
contact,
|
||||
user,
|
||||
)
|
||||
|
||||
handle_bounce(contact, alias, msg, user)
|
||||
|
@ -1399,7 +1408,9 @@ class MailHandler:
|
|||
return ret
|
||||
except Exception:
|
||||
LOG.exception(
|
||||
"email handling fail %s -> %s", envelope.mail_from, envelope.rcpt_tos,
|
||||
"email handling fail %s -> %s",
|
||||
envelope.mail_from,
|
||||
envelope.rcpt_tos,
|
||||
)
|
||||
return "421 SL Retry later"
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ def demo():
|
|||
|
||||
@app.route("/callback", methods=["GET"])
|
||||
def callback():
|
||||
""" Step 3: Retrieving an access token.
|
||||
"""Step 3: Retrieving an access token.
|
||||
The user has been redirected back from the provider to your registered
|
||||
callback URL. With this redirection comes an authorization code included
|
||||
in the redirect URL. We will use that to obtain an access token.
|
||||
|
@ -75,8 +75,7 @@ def callback():
|
|||
|
||||
@app.route("/profile", methods=["GET"])
|
||||
def profile():
|
||||
"""Fetching a protected resource using an OAuth 2 token.
|
||||
"""
|
||||
"""Fetching a protected resource using an OAuth 2 token."""
|
||||
simplelogin = OAuth2Session(client_id, token=session["oauth_token"])
|
||||
return jsonify(simplelogin.get(userinfo_url).json())
|
||||
|
||||
|
|
|
@ -82,7 +82,11 @@ from app.oauth.base import oauth_bp
|
|||
if SENTRY_DSN:
|
||||
LOG.d("enable sentry")
|
||||
sentry_sdk.init(
|
||||
dsn=SENTRY_DSN, integrations=[FlaskIntegration(), SqlalchemyIntegration(),],
|
||||
dsn=SENTRY_DSN,
|
||||
integrations=[
|
||||
FlaskIntegration(),
|
||||
SqlalchemyIntegration(),
|
||||
],
|
||||
)
|
||||
|
||||
# the app is served behin nginx which uses http and not https
|
||||
|
|
|
@ -243,14 +243,16 @@ def test_auth_login_forgot_password(flask_client):
|
|||
db.session.commit()
|
||||
|
||||
r = flask_client.post(
|
||||
url_for("api.forgot_password"), json={"email": "abcd@gmail.com"},
|
||||
url_for("api.forgot_password"),
|
||||
json={"email": "abcd@gmail.com"},
|
||||
)
|
||||
|
||||
assert r.status_code == 200
|
||||
|
||||
# No such email, still return 200
|
||||
r = flask_client.post(
|
||||
url_for("api.forgot_password"), json={"email": "not-exist@b.c"},
|
||||
url_for("api.forgot_password"),
|
||||
json={"email": "not-exist@b.c"},
|
||||
)
|
||||
|
||||
assert r.status_code == 200
|
||||
|
|
|
@ -177,7 +177,8 @@ def test_get_mailboxes(flask_client):
|
|||
db.session.commit()
|
||||
|
||||
r = flask_client.get(
|
||||
url_for("api.get_mailboxes"), headers={"Authentication": api_key.code},
|
||||
url_for("api.get_mailboxes"),
|
||||
headers={"Authentication": api_key.code},
|
||||
)
|
||||
assert r.status_code == 200
|
||||
# m2@example.com is not returned as it's not verified
|
||||
|
|
|
@ -120,7 +120,11 @@ def test_success_v2(flask_client):
|
|||
r = flask_client.post(
|
||||
url_for("api.new_custom_alias_v2", hostname="www.test.com"),
|
||||
headers={"Authentication": api_key.code},
|
||||
json={"alias_prefix": "prefix", "signed_suffix": suffix, "note": "test note",},
|
||||
json={
|
||||
"alias_prefix": "prefix",
|
||||
"signed_suffix": suffix,
|
||||
"note": "test note",
|
||||
},
|
||||
)
|
||||
|
||||
assert r.status_code == 201
|
||||
|
@ -163,7 +167,11 @@ def test_cannot_create_alias_in_trash(flask_client):
|
|||
r = flask_client.post(
|
||||
url_for("api.new_custom_alias_v2", hostname="www.test.com"),
|
||||
headers={"Authentication": api_key.code},
|
||||
json={"alias_prefix": "prefix", "signed_suffix": suffix, "note": "test note",},
|
||||
json={
|
||||
"alias_prefix": "prefix",
|
||||
"signed_suffix": suffix,
|
||||
"note": "test note",
|
||||
},
|
||||
)
|
||||
|
||||
# assert alias creation is successful
|
||||
|
@ -179,14 +187,21 @@ def test_cannot_create_alias_in_trash(flask_client):
|
|||
r = flask_client.post(
|
||||
url_for("api.new_custom_alias_v2", hostname="www.test.com"),
|
||||
headers={"Authentication": api_key.code},
|
||||
json={"alias_prefix": "prefix", "signed_suffix": suffix, "note": "test note",},
|
||||
json={
|
||||
"alias_prefix": "prefix",
|
||||
"signed_suffix": suffix,
|
||||
"note": "test note",
|
||||
},
|
||||
)
|
||||
assert r.status_code == 409
|
||||
|
||||
|
||||
def test_success_v3(flask_client):
|
||||
user = User.create(
|
||||
email="a@b.c", password="password", name="Test User", activated=True,
|
||||
email="a@b.c",
|
||||
password="password",
|
||||
name="Test User",
|
||||
activated=True,
|
||||
)
|
||||
db.session.commit()
|
||||
|
||||
|
|
|
@ -69,6 +69,9 @@ def test_logout(flask_client):
|
|||
)
|
||||
|
||||
# logout
|
||||
r = flask_client.get(url_for("auth.logout"), follow_redirects=True,)
|
||||
r = flask_client.get(
|
||||
url_for("auth.logout"),
|
||||
follow_redirects=True,
|
||||
)
|
||||
|
||||
assert r.status_code == 200
|
||||
|
|
|
@ -82,7 +82,10 @@ def test_add_or_replace_header():
|
|||
|
||||
def test_parseaddr_unicode():
|
||||
# only email
|
||||
assert parseaddr_unicode("abcd@gmail.com") == ("", "abcd@gmail.com",)
|
||||
assert parseaddr_unicode("abcd@gmail.com") == (
|
||||
"",
|
||||
"abcd@gmail.com",
|
||||
)
|
||||
|
||||
# ascii address
|
||||
assert parseaddr_unicode("First Last <abcd@gmail.com>") == (
|
||||
|
|
Loading…
Reference in New Issue