diff --git a/app/alias_utils.py b/app/alias_utils.py index e737cc3c..cc5f21d1 100644 --- a/app/alias_utils.py +++ b/app/alias_utils.py @@ -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 diff --git a/app/api/serializer.py b/app/api/serializer.py index b1b00651..a46d3bfb 100644 --- a/app/api/serializer.py +++ b/app/api/serializer.py @@ -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, ) diff --git a/app/api/views/apple.py b/app/api/views/apple.py index 9cee685f..be483347 100644 --- a/app/api/views/apple.py +++ b/app/api/views/apple.py @@ -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 diff --git a/app/api/views/new_custom_alias.py b/app/api/views/new_custom_alias.py index 78c5b138..4d043aed 100644 --- a/app/api/views/new_custom_alias.py +++ b/app/api/views/new_custom_alias.py @@ -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() diff --git a/app/auth/views/register.py b/app/auth/views/register.py index b1f65c4b..e25ab3ec 100644 --- a/app/auth/views/register.py +++ b/app/auth/views/register.py @@ -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( diff --git a/app/dashboard/views/alias_contact_manager.py b/app/dashboard/views/alias_contact_manager.py index 363eb975..6ec672b6 100644 --- a/app/dashboard/views/alias_contact_manager.py +++ b/app/dashboard/views/alias_contact_manager.py @@ -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() diff --git a/app/dashboard/views/custom_alias.py b/app/dashboard/views/custom_alias.py index f89a9405..e207de52 100644 --- a/app/dashboard/views/custom_alias.py +++ b/app/dashboard/views/custom_alias.py @@ -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() diff --git a/app/dashboard/views/domain_detail.py b/app/dashboard/views/domain_detail.py index 5e00f18d..ba3eb536 100644 --- a/app/dashboard/views/domain_detail.py +++ b/app/dashboard/views/domain_detail.py @@ -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( diff --git a/app/dashboard/views/index.py b/app/dashboard/views/index.py index ccc1ea61..dfce0bc4 100644 --- a/app/dashboard/views/index.py +++ b/app/dashboard/views/index.py @@ -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, ) ) diff --git a/app/dashboard/views/refused_email.py b/app/dashboard/views/refused_email.py index 9646e103..6e947d3a 100644 --- a/app/dashboard/views/refused_email.py +++ b/app/dashboard/views/refused_email.py @@ -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 diff --git a/app/email_utils.py b/app/email_utils.py index daa32771..64a44d56 100644 --- a/app/email_utils.py +++ b/app/email_utils.py @@ -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(",")] diff --git a/app/greylisting.py b/app/greylisting.py index 87c36464..c990cb94 100644 --- a/app/greylisting.py +++ b/app/greylisting.py @@ -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() ) diff --git a/app/models.py b/app/models.py index 7cabf2a7..c2ad0e45 100644 --- a/app/models.py +++ b/app/models.py @@ -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 diff --git a/app/s3.py b/app/s3.py index e4443ed8..6c4ae2d8 100644 --- a/app/s3.py +++ b/app/s3.py @@ -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, ) diff --git a/cron.py b/cron.py index c1759d7a..5765a330 100644 --- a/cron.py +++ b/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)) diff --git a/email_handler.py b/email_handler.py index a00c783a..7f49d39c 100644 --- a/email_handler.py +++ b/email_handler.py @@ -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" diff --git a/oauth_tester.py b/oauth_tester.py index 1d18975c..130ceea9 100644 --- a/oauth_tester.py +++ b/oauth_tester.py @@ -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()) diff --git a/server.py b/server.py index 2942e24c..0f0a32d9 100644 --- a/server.py +++ b/server.py @@ -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 diff --git a/tests/api/test_auth_login.py b/tests/api/test_auth_login.py index 5724b0f9..2be38536 100644 --- a/tests/api/test_auth_login.py +++ b/tests/api/test_auth_login.py @@ -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 diff --git a/tests/api/test_mailbox.py b/tests/api/test_mailbox.py index a2fc753c..2902896b 100644 --- a/tests/api/test_mailbox.py +++ b/tests/api/test_mailbox.py @@ -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 diff --git a/tests/api/test_new_custom_alias.py b/tests/api/test_new_custom_alias.py index e2468a59..92e3cd2b 100644 --- a/tests/api/test_new_custom_alias.py +++ b/tests/api/test_new_custom_alias.py @@ -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() diff --git a/tests/api/test_user_info.py b/tests/api/test_user_info.py index ce2c9f25..3a61623b 100644 --- a/tests/api/test_user_info.py +++ b/tests/api/test_user_info.py @@ -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 diff --git a/tests/test_email_utils.py b/tests/test_email_utils.py index 653e228a..5ced93aa 100644 --- a/tests/test_email_utils.py +++ b/tests/test_email_utils.py @@ -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 ") == (