From bdf75951f1132ca10e9771c34c159057df9bc520 Mon Sep 17 00:00:00 2001 From: Son NK Date: Wed, 22 Jan 2020 10:22:59 +0100 Subject: [PATCH] support ALIAS_DOMAINS - use verify_prefix_suffix() in /api/alias/custom/new - --- app/api/views/alias_options.py | 10 ++++++--- app/api/views/new_custom_alias.py | 34 +++++------------------------ app/auth/views/register.py | 6 ++--- app/dashboard/views/custom_alias.py | 31 ++++++++++++++++---------- app/dashboard/views/setting.py | 5 +++-- email_handler.py | 17 ++++++++------- tests/api/test_alias_options.py | 3 ++- tests/api/test_new_custom_alias.py | 19 +++++++++------- tests/env.test | 1 + 9 files changed, 62 insertions(+), 64 deletions(-) diff --git a/app/api/views/alias_options.py b/app/api/views/alias_options.py index d3d3dcf4..c1015028 100644 --- a/app/api/views/alias_options.py +++ b/app/api/views/alias_options.py @@ -3,7 +3,7 @@ from flask_cors import cross_origin from sqlalchemy import desc from app.api.base import api_bp, verify_api_key -from app.config import EMAIL_DOMAIN +from app.config import ALIAS_DOMAINS from app.extensions import db from app.log import LOG from app.models import AliasUsedOn, GenEmail, User @@ -67,9 +67,11 @@ def options(): else: ret["custom"]["suggestion"] = "" + ret["custom"]["suffixes"] = [] # maybe better to make sure the suffix is never used before # but this is ok as there's a check when creating a new custom alias - ret["custom"]["suffixes"] = [f".{random_word()}@{EMAIL_DOMAIN}"] + for domain in ALIAS_DOMAINS: + ret["custom"]["suffixes"].append(f".{random_word()}@{domain}") for custom_domain in user.verified_custom_domains(): ret["custom"]["suffixes"].append("@" + custom_domain.domain) @@ -144,7 +146,9 @@ def options_v2(): # maybe better to make sure the suffix is never used before # but this is ok as there's a check when creating a new custom alias - ret["suffixes"] = [f".{random_word()}@{EMAIL_DOMAIN}"] + # todo: take into account DISABLE_ALIAS_SUFFIX + for domain in ALIAS_DOMAINS: + ret["suffixes"].append(f".{random_word()}@{domain}") for custom_domain in user.verified_custom_domains(): ret["suffixes"].append("@" + custom_domain.domain) diff --git a/app/api/views/new_custom_alias.py b/app/api/views/new_custom_alias.py index 767020f5..e920ddf1 100644 --- a/app/api/views/new_custom_alias.py +++ b/app/api/views/new_custom_alias.py @@ -3,7 +3,8 @@ from flask import jsonify, request from flask_cors import cross_origin from app.api.base import api_bp, verify_api_key -from app.config import EMAIL_DOMAIN, MAX_NB_EMAIL_FREE_PLAN +from app.config import MAX_NB_EMAIL_FREE_PLAN +from app.dashboard.views.custom_alias import verify_prefix_suffix from app.extensions import db from app.log import LOG from app.models import GenEmail, AliasUsedOn @@ -43,35 +44,12 @@ def new_custom_alias(): if not data: return jsonify(error="request body cannot be empty"), 400 - alias_prefix = data.get("alias_prefix", "") - alias_suffix = data.get("alias_suffix", "") - - # make sure alias_prefix is not empty - alias_prefix = alias_prefix.strip() + alias_prefix = data.get("alias_prefix", "").strip() + alias_suffix = data.get("alias_suffix", "").strip() alias_prefix = convert_to_id(alias_prefix) - if not alias_prefix: # should be checked on frontend - LOG.d("user %s submits an empty alias with the prefix %s", user, alias_prefix) - return jsonify(error="alias prefix cannot be empty"), 400 - # make sure alias_suffix is either .random_letters@simplelogin.co or @my-domain.com - alias_suffix = alias_suffix.strip() - if alias_suffix.startswith("@"): - custom_domain = alias_suffix[1:] - if custom_domain not in user_custom_domains: - LOG.d("user %s submits a wrong custom domain %s ", user, custom_domain) - return jsonify(error="error"), 400 - else: - if not alias_suffix.startswith("."): - LOG.d("user %s submits a wrong alias suffix %s", user, alias_suffix) - return jsonify(error="error"), 400 - if not alias_suffix.endswith(EMAIL_DOMAIN): - LOG.d("user %s submits a wrong alias suffix %s", user, alias_suffix) - return jsonify(error="error"), 400 - - random_letters = alias_suffix[1 : alias_suffix.find("@")] - if len(random_letters) < 5: - LOG.d("user %s submits a wrong alias suffix %s", user, alias_suffix) - return jsonify(error="error"), 400 + if not verify_prefix_suffix(user, alias_prefix, alias_suffix, user_custom_domains): + return jsonify(error="wrong alias prefix or suffix"), 400 full_alias = alias_prefix + alias_suffix if GenEmail.get_by(email=full_alias): diff --git a/app/auth/views/register.py b/app/auth/views/register.py index 8e706942..29f216f9 100644 --- a/app/auth/views/register.py +++ b/app/auth/views/register.py @@ -5,7 +5,8 @@ from wtforms import StringField, validators from app import email_utils from app.auth.base import auth_bp -from app.config import URL, EMAIL_DOMAIN +from app.config import URL +from app.email_utils import email_belongs_to_alias_domains from app.extensions import db from app.log import LOG from app.models import User, ActivationCode @@ -31,8 +32,7 @@ def register(): if form.validate_on_submit(): email = form.email.data - - if email.endswith(EMAIL_DOMAIN): + if email_belongs_to_alias_domains(email): flash( "You cannot use alias as your personal inbox. Nice try though 😉", "error", diff --git a/app/dashboard/views/custom_alias.py b/app/dashboard/views/custom_alias.py index 1965e2f9..ebae7e8a 100644 --- a/app/dashboard/views/custom_alias.py +++ b/app/dashboard/views/custom_alias.py @@ -1,13 +1,16 @@ from flask import render_template, redirect, url_for, flash, request, session from flask_login import login_required, current_user -from flask_wtf import FlaskForm -from wtforms import StringField, validators, SelectField -from app.config import EMAIL_DOMAIN, HIGHLIGHT_GEN_EMAIL_ID, DISABLE_ALIAS_SUFFIX +from app.config import ( + HIGHLIGHT_GEN_EMAIL_ID, + DISABLE_ALIAS_SUFFIX, + ALIAS_DOMAINS, +) from app.dashboard.base import dashboard_bp +from app.email_utils import email_belongs_to_alias_domains from app.extensions import db from app.log import LOG -from app.models import GenEmail, DeletedAlias, CustomDomain +from app.models import GenEmail from app.utils import convert_to_id, random_word, word_exist @@ -29,9 +32,10 @@ def custom_alias(): suffixes.append("@" + alias_domain) # then default domain - suffixes.append( - ("" if DISABLE_ALIAS_SUFFIX else ".") + random_word() + "@" + EMAIL_DOMAIN - ) + for domain in ALIAS_DOMAINS: + suffixes.append( + ("" if DISABLE_ALIAS_SUFFIX else "." + random_word()) + "@" + domain + ) if request.method == "POST": alias_prefix = request.form.get("prefix") @@ -73,9 +77,12 @@ def verify_prefix_suffix(user, alias_prefix, alias_suffix, user_custom_domains) alias_suffix = alias_suffix.strip() if alias_suffix.startswith("@"): alias_domain = alias_suffix[1:] - # alias_domain can be either custom_domain or if DISABLE_ALIAS_SUFFIX, EMAIL_DOMAIN + # alias_domain can be either custom_domain or if DISABLE_ALIAS_SUFFIX, one of the default ALIAS_DOMAINS if DISABLE_ALIAS_SUFFIX: - if alias_domain not in user_custom_domains and alias_domain != EMAIL_DOMAIN: + if ( + alias_domain not in user_custom_domains + and alias_domain not in ALIAS_DOMAINS + ): LOG.error("wrong alias suffix %s, user %s", alias_suffix, user) return False else: @@ -86,9 +93,11 @@ def verify_prefix_suffix(user, alias_prefix, alias_suffix, user_custom_domains) if not alias_suffix.startswith("."): LOG.error("User %s submits a wrong alias suffix %s", user, alias_suffix) return False - if not alias_suffix.endswith(EMAIL_DOMAIN): + + full_alias = alias_prefix + alias_suffix + if not email_belongs_to_alias_domains(full_alias): LOG.error( - "Alias suffix should end with default alias domain %s", + "Alias suffix should end with one of the alias domains %s", user, alias_suffix, ) diff --git a/app/dashboard/views/setting.py b/app/dashboard/views/setting.py index c9c59e58..f70d2fb8 100644 --- a/app/dashboard/views/setting.py +++ b/app/dashboard/views/setting.py @@ -9,8 +9,9 @@ from flask_wtf.file import FileField from wtforms import StringField, validators from app import s3, email_utils -from app.config import URL, EMAIL_DOMAIN +from app.config import URL from app.dashboard.base import dashboard_bp +from app.email_utils import email_belongs_to_alias_domains from app.extensions import db from app.log import LOG from app.models import ( @@ -92,7 +93,7 @@ def setting(): or DeletedAlias.get_by(email=new_email) ): flash(f"Email {new_email} already used", "error") - elif new_email.endswith(EMAIL_DOMAIN): + elif email_belongs_to_alias_domains(new_email): flash( "You cannot use alias as your personal inbox. Nice try though 😉", "error", diff --git a/email_handler.py b/email_handler.py index c281ce67..91d1718c 100644 --- a/email_handler.py +++ b/email_handler.py @@ -38,7 +38,7 @@ from smtplib import SMTP from aiosmtpd.controller import Controller -from app.config import EMAIL_DOMAIN, POSTFIX_SERVER, URL +from app.config import EMAIL_DOMAIN, POSTFIX_SERVER, URL, ALIAS_DOMAINS from app.email_utils import ( get_email_name, get_email_part, @@ -49,6 +49,7 @@ from app.email_utils import ( delete_header, send_cannot_create_directory_alias, send_cannot_create_domain_alias, + email_belongs_to_alias_domains, ) from app.extensions import db from app.log import LOG @@ -120,7 +121,7 @@ class MailHandler: on_the_fly = False # check if alias belongs to a directory, ie having directory/anything@EMAIL_DOMAIN format - if alias.endswith(EMAIL_DOMAIN): + if email_belongs_to_alias_domains(alias): if "/" in alias or "+" in alias or "#" in alias: if "/" in alias: sep = "/" @@ -284,10 +285,10 @@ class MailHandler: forward_email = ForwardEmail.get_by(reply_email=reply_email) alias: str = forward_email.gen_email.email - - # alias must end with EMAIL_DOMAIN or custom-domain alias_domain = alias[alias.find("@") + 1 :] - if alias_domain != EMAIL_DOMAIN: + + # alias must end with one of the ALIAS_DOMAINS or custom-domain + if not email_belongs_to_alias_domains(alias): if not CustomDomain.get_by(domain=alias_domain): return "550 alias unknown by SimpleLogin" @@ -338,9 +339,9 @@ class MailHandler: envelope.rcpt_options, ) - if alias_domain == EMAIL_DOMAIN: - add_dkim_signature(msg, EMAIL_DOMAIN) - # add DKIM-Signature for non-custom-domain alias + if alias_domain in ALIAS_DOMAINS: + add_dkim_signature(msg, alias_domain) + # add DKIM-Signature for custom-domain alias else: custom_domain: CustomDomain = CustomDomain.get_by(domain=alias_domain) if custom_domain.dkim_verified: diff --git a/tests/api/test_alias_options.py b/tests/api/test_alias_options.py index b7ac4e72..47b34ab7 100644 --- a/tests/api/test_alias_options.py +++ b/tests/api/test_alias_options.py @@ -27,7 +27,8 @@ def test_different_scenarios(flask_client): assert r.status_code == 200 assert r.json["can_create_custom"] assert len(r.json["existing"]) == 1 - assert r.json["custom"]["suffixes"] + assert len(r.json["custom"]["suffixes"]) == 3 + assert r.json["custom"]["suggestion"] == "" # no hostname => no suggestion # <<< with hostname >>> diff --git a/tests/api/test_new_custom_alias.py b/tests/api/test_new_custom_alias.py index 4dabd645..e5107956 100644 --- a/tests/api/test_new_custom_alias.py +++ b/tests/api/test_new_custom_alias.py @@ -1,8 +1,9 @@ from flask import url_for -from app.config import EMAIL_DOMAIN +from app.config import EMAIL_DOMAIN, MAX_NB_EMAIL_FREE_PLAN from app.extensions import db from app.models import User, ApiKey, GenEmail +from app.utils import random_word def test_success(flask_client): @@ -15,14 +16,16 @@ def test_success(flask_client): api_key = ApiKey.create(user.id, "for test") db.session.commit() + word = random_word() + r = flask_client.post( url_for("api.new_custom_alias", hostname="www.test.com"), headers={"Authentication": api_key.code}, - json={"alias_prefix": "prefix", "alias_suffix": f".abcdef@{EMAIL_DOMAIN}"}, + json={"alias_prefix": "prefix", "alias_suffix": f".{word}@{EMAIL_DOMAIN}"}, ) assert r.status_code == 201 - assert r.json["alias"] == f"prefix.abcdef@{EMAIL_DOMAIN}" + assert r.json["alias"] == f"prefix.{word}@{EMAIL_DOMAIN}" def test_out_of_quota(flask_client): @@ -35,15 +38,15 @@ def test_out_of_quota(flask_client): api_key = ApiKey.create(user.id, "for test") db.session.commit() - # create 3 custom alias to run out of quota - GenEmail.create_new(user.id, prefix="test") - GenEmail.create_new(user.id, prefix="test") - GenEmail.create_new(user.id, prefix="test") + # create MAX_NB_EMAIL_FREE_PLAN custom alias to run out of quota + for _ in range(MAX_NB_EMAIL_FREE_PLAN): + GenEmail.create_new(user.id, prefix="test") + word = random_word() r = flask_client.post( url_for("api.new_custom_alias", hostname="www.test.com"), headers={"Authentication": api_key.code}, - json={"alias_prefix": "prefix", "alias_suffix": f".abcdef@{EMAIL_DOMAIN}"}, + json={"alias_prefix": "prefix", "alias_suffix": f".{word}@{EMAIL_DOMAIN}"}, ) assert r.status_code == 400 diff --git a/tests/env.test b/tests/env.test index 470aeb13..7835aae4 100644 --- a/tests/env.test +++ b/tests/env.test @@ -5,6 +5,7 @@ URL=http://localhost # Only print email content, not sending it NOT_SEND_EMAIL=true EMAIL_DOMAIN=sl.local +OTHER_ALIAS_DOMAINS=["d1.test", "d2.test"] SUPPORT_EMAIL=support@sl.local ADMIN_EMAIL=to_fill # Max number emails user can generate for free plan