def test_cannot_create_alias_in_trash(flask_client): user = login(flask_client) # create a custom domain CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True, commit=True) signed_suffix = signer.sign("@ab.cd").decode() r = flask_client.post( "/api/v3/alias/custom/new", json={ "alias_prefix": "prefix", "signed_suffix": signed_suffix, "mailbox_ids": [user.default_mailbox_id], }, ) assert r.status_code == 201 assert r.json["alias"] == f"*****@*****.**" # delete alias: it's going to be moved to ab.cd trash alias = Alias.get_by(email="*****@*****.**") assert alias.custom_domain_id delete_alias(alias, user) # try to create the same alias, will fail as the alias is in trash r = flask_client.post( "/api/v3/alias/custom/new", json={ "alias_prefix": "prefix", "signed_suffix": signed_suffix, "mailbox_ids": [user.default_mailbox_id], }, ) assert r.status_code == 409
def delete_domain(custom_domain_id: CustomDomain): from server import create_light_app with create_light_app().app_context(): custom_domain = CustomDomain.get(custom_domain_id) if not custom_domain: return domain_name = custom_domain.domain user = custom_domain.user CustomDomain.delete(custom_domain.id) db.session.commit() LOG.d("Domain %s deleted", domain_name) send_email( user.email, f"Your domain {domain_name} has been deleted", f"""Domain {domain_name} along with its aliases are deleted successfully. Regards, SimpleLogin team. """, )
def test_create_subdomain_out_of_quota(flask_client): user = login(flask_client) sl_domain = setup_sl_domain() for i in range(MAX_NB_SUBDOMAIN): CustomDomain.create( domain=f"test{i}.{sl_domain.domain}", user_id=user.id, is_sl_subdomain=True, commit=True, ) assert CustomDomain.count() == MAX_NB_SUBDOMAIN flask_client.post( url_for("dashboard.subdomain_route"), data={ "form-name": "create", "subdomain": "test", "domain": sl_domain.domain }, follow_redirects=True, ) # no new subdomain is created assert CustomDomain.count() == MAX_NB_SUBDOMAIN
def test_get_custom_domains(flask_client): user = login(flask_client) CustomDomain.create(user_id=user.id, domain="test1.org", verified=True, commit=True) CustomDomain.create( user_id=user.id, domain="test2.org", verified=False, commit=True ) r = flask_client.get( "/api/custom_domains", ) assert r.status_code == 200 assert len(r.json["custom_domains"]) == 2 for domain in r.json["custom_domains"]: assert domain["domain_name"] assert domain["id"] assert domain["nb_alias"] == 0 assert "is_verified" in domain assert "catch_all" in domain assert "name" in domain assert "random_prefix_generation" in domain assert domain["creation_date"] assert domain["creation_timestamp"] assert domain["mailboxes"] for mailbox in domain["mailboxes"]: assert "id" in mailbox assert "email" in mailbox
def test_too_many_requests(flask_client): user = login(flask_client) # create a custom domain CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True, commit=True) # can't create more than 5 aliases in 1 minute for i in range(7): signed_suffix = signer.sign("@ab.cd").decode() r = flask_client.post( "/api/v3/alias/custom/new", json={ "alias_prefix": f"prefix{i}", "signed_suffix": signed_suffix, "mailbox_ids": [user.default_mailbox_id], }, ) # to make flask-limiter work with unit test # https://github.com/alisaifee/flask-limiter/issues/147#issuecomment-642683820 g._rate_limiting_complete = False else: # last request assert r.status_code == 429 assert r.json == {"error": "Rate limit exceeded"}
def test_import_no_mailboxes(flask_client): # Create user user = login(flask_client) # Check start state assert len(Alias.filter_by(user_id=user.id).all()) == 1 # Onboarding alias # Create domain CustomDomain.create( user_id=user.id, domain="my-domain.com", ownership_verified=True ) Session.commit() alias_data = [ "alias,note", "[email protected],Used on eBay", '[email protected],"Used on Facebook, Instagram."', ] file = File.create(path="/test", commit=True) batch_import = BatchImport.create(user_id=user.id, file_id=file.id) import_from_csv(batch_import, user, alias_data) assert len(Alias.filter_by(user_id=user.id).all()) == 3 # +2
def test_too_many_requests(flask_client): user = login(flask_client) # create a custom domain CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True, commit=True) # can't create more than 5 aliases in 1 minute for i in range(7): signed_suffix = signer.sign("@ab.cd").decode() r = flask_client.post( url_for("dashboard.custom_alias"), data={ "prefix": f"prefix{i}", "suffix": signed_suffix, "mailboxes": [user.default_mailbox_id], }, follow_redirects=True, ) # to make flask-limiter work with unit test # https://github.com/alisaifee/flask-limiter/issues/147#issuecomment-642683820 g._rate_limiting_complete = False else: # last request assert r.status_code == 429 assert "Whoa, slow down there, pardner!" in str(r.data)
def test_get_setting_domains_v2(flask_client): user = login(flask_client) CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True, commit=True) r = flask_client.get("/api/v2/setting/domains") assert r.status_code == 200 assert r.json == [ { "domain": "d1.test", "is_custom": False }, { "domain": "d2.test", "is_custom": False }, { "domain": "sl.local", "is_custom": False }, { "domain": "ab.cd", "is_custom": True }, ]
def test_get_custom_domains(flask_client): user = login(flask_client) CustomDomain.create(user_id=user.id, domain="test1.org", verified=True, commit=True) CustomDomain.create(user_id=user.id, domain="test2.org", verified=False, commit=True) r = flask_client.get("/api/custom_domains", ) assert r.status_code == 200 assert r.json == { "custom_domains": [ { "domain": "test1.org", "id": 1, "nb_alias": 0, "verified": True }, { "domain": "test2.org", "id": 2, "nb_alias": 0, "verified": False }, ] }
def domain_detail(custom_domain_id): custom_domain = CustomDomain.get(custom_domain_id) if not custom_domain or custom_domain.user_id != current_user.id: flash("You cannot see this page", "warning") return redirect(url_for("dashboard.index")) if request.method == "POST": if request.form.get("form-name") == "switch-catch-all": custom_domain.catch_all = not custom_domain.catch_all db.session.commit() if custom_domain.catch_all: flash( f"The catch-all has been enabled for {custom_domain.domain}", "success", ) else: flash( f"The catch-all has been disabled for {custom_domain.domain}", "warning", ) return redirect( url_for("dashboard.domain_detail", custom_domain_id=custom_domain.id)) elif request.form.get("form-name") == "delete": name = custom_domain.domain CustomDomain.delete(custom_domain_id) db.session.commit() flash(f"Domain {name} has been deleted", "success") return redirect(url_for("dashboard.custom_domain")) nb_alias = GenEmail.filter_by(custom_domain_id=custom_domain.id).count() return render_template("dashboard/domain_detail/info.html", **locals())
def test_create_subdomain_in_trash(flask_client): user = login(flask_client) sl_domain = setup_sl_domain() subdomain = CustomDomain.create( domain=f"test.{sl_domain.domain}", user_id=user.id, is_sl_subdomain=True, commit=True, ) # delete the subdomain CustomDomain.delete(subdomain.id) assert CustomDomain.get_by(domain=f"test.{sl_domain.domain}") is None r = flask_client.post( url_for("dashboard.subdomain_route"), data={ "form-name": "create", "subdomain": "test", "domain": sl_domain.domain }, follow_redirects=True, ) assert r.status_code == 200 assert ( f"test.{sl_domain.domain} has been used before and cannot be reused" in r.data.decode())
def test_can_be_used_as_personal_email(flask_client): # default alias domain assert not email_can_be_used_as_mailbox("*****@*****.**") assert not email_can_be_used_as_mailbox("*****@*****.**") # custom domain user = User.create( email="[email protected]", password="******", name="Test User", activated=True, commit=True, ) CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True, commit=True) assert not email_can_be_used_as_mailbox("*****@*****.**") # disposable domain assert not email_can_be_used_as_mailbox("*****@*****.**") assert not email_can_be_used_as_mailbox("*****@*****.**") # subdomain will not work assert not email_can_be_used_as_mailbox("*****@*****.**") # valid domains should not be affected assert email_can_be_used_as_mailbox("*****@*****.**") assert email_can_be_used_as_mailbox("*****@*****.**")
def test_get_setting_domains_v2(flask_client): user = login(flask_client) CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True, commit=True) r = flask_client.get("/api/v2/setting/domains") assert r.status_code == 200
def test_import(flask_client): # Create user user = login(flask_client) # Check start state assert len(Alias.filter_by(user_id=user.id).all()) == 1 # Onboarding alias # Create domains CustomDomain.create( user_id=user.id, domain="my-domain.com", ownership_verified=True ) CustomDomain.create( user_id=user.id, domain="my-destination-domain.com", ownership_verified=True ) Session.commit() # Create mailboxes mailbox1 = Mailbox.create( user_id=user.id, email="*****@*****.**", verified=True ) mailbox2 = Mailbox.create( user_id=user.id, email="*****@*****.**", verified=True ) Session.commit() alias_data = [ "alias,note,mailboxes", "[email protected],Used on eBay,[email protected]", '[email protected],"Used on Facebook, Instagram.",[email protected] [email protected]', ] file = File.create(path="/test", commit=True) batch_import = BatchImport.create(user_id=user.id, file_id=file.id) import_from_csv(batch_import, user, alias_data) aliases = Alias.filter_by(user_id=user.id).order_by(Alias.id).all() assert len(aliases) == 3 # +2 # aliases[0] is the onboarding alias, skip it # eBay alias assert aliases[1].email == "*****@*****.**" assert len(aliases[1].mailboxes) == 1 # First one should be primary assert aliases[1].mailbox_id == mailbox1.id # Others are sorted assert aliases[1].mailboxes[0] == mailbox1 # Facebook alias assert aliases[2].email == "*****@*****.**" assert len(aliases[2].mailboxes) == 2 # First one should be primary assert aliases[2].mailbox_id == mailbox1.id # Others are sorted assert aliases[2].mailboxes[0] == mailbox2 assert aliases[2].mailboxes[1] == mailbox1
def compute_metric2() -> Metric2: now = arrow.now() _24h_ago = now.shift(days=-1) nb_referred_user_paid = 0 for user in User.filter(User.referral_id.isnot(None)): if user.is_paid(): nb_referred_user_paid += 1 return Metric2.create( date=now, # user stats nb_user=User.count(), nb_activated_user=User.filter_by(activated=True).count(), # subscription stats nb_premium=Subscription.filter( Subscription.cancelled.is_(False)).count(), nb_cancelled_premium=Subscription.filter( Subscription.cancelled.is_(True)).count(), # todo: filter by expires_date > now nb_apple_premium=AppleSubscription.count(), nb_manual_premium=ManualSubscription.filter( ManualSubscription.end_at > now, ManualSubscription.is_giveaway.is_(False), ).count(), nb_coinbase_premium=CoinbaseSubscription.filter( CoinbaseSubscription.end_at > now).count(), # referral stats nb_referred_user=User.filter(User.referral_id.isnot(None)).count(), nb_referred_user_paid=nb_referred_user_paid, nb_alias=Alias.count(), # email log stats nb_forward_last_24h=EmailLog.filter( EmailLog.created_at > _24h_ago).filter_by(bounced=False, is_spam=False, is_reply=False, blocked=False).count(), nb_bounced_last_24h=EmailLog.filter( EmailLog.created_at > _24h_ago).filter_by(bounced=True).count(), nb_total_bounced_last_24h=Bounce.filter( Bounce.created_at > _24h_ago).count(), nb_reply_last_24h=EmailLog.filter( EmailLog.created_at > _24h_ago).filter_by(is_reply=True).count(), nb_block_last_24h=EmailLog.filter( EmailLog.created_at > _24h_ago).filter_by(blocked=True).count(), # other stats nb_verified_custom_domain=CustomDomain.filter_by( verified=True).count(), nb_subdomain=CustomDomain.filter_by(is_sl_subdomain=True).count(), nb_directory=Directory.count(), nb_deleted_directory=DeletedDirectory.count(), nb_deleted_subdomain=DeletedSubdomain.count(), nb_app=Client.count(), commit=True, )
def test_verify_prefix_suffix(flask_client): user = login(flask_client) db.session.commit() CustomDomain.create(user_id=user.id, domain="test.com", verified=True) assert verify_prefix_suffix(user, "prefix", "@test.com") assert not verify_prefix_suffix(user, "prefix", "@abcd.com") word = random_word() suffix = f".{word}@{EMAIL_DOMAIN}" assert verify_prefix_suffix(user, "prefix", suffix)
def test_available_suffixes(flask_client): user = login(flask_client) CustomDomain.create(user_id=user.id, domain="test.com", verified=True) assert len(get_available_suffixes(user)) > 0 # first suffix is custom domain first_suffix = get_available_suffixes(user)[0] assert first_suffix.is_custom assert first_suffix.suffix == "@test.com" assert first_suffix.signed_suffix.startswith("@test.com")
def custom_domain(): custom_domains = CustomDomain.query.filter_by( user_id=current_user.id).all() new_custom_domain_form = NewCustomDomainForm() errors = {} if request.method == "POST": if request.form.get("form-name") == "create": if not current_user.is_premium(): flash("Only premium plan can add custom domain", "warning") return redirect(url_for("dashboard.custom_domain")) if new_custom_domain_form.validate(): new_domain = new_custom_domain_form.domain.data.lower().strip() if new_domain.startswith("http://"): new_domain = new_domain[len("http://"):] if new_domain.startswith("https://"): new_domain = new_domain[len("https://"):] if CustomDomain.get_by(domain=new_domain): flash(f"{new_domain} already added", "warning") elif get_email_domain_part(current_user.email) == new_domain: flash( "You cannot add a domain that you are currently using for your personal email. " "Please change your personal email to your real email", "error", ) else: new_custom_domain = CustomDomain.create( domain=new_domain, user_id=current_user.id) db.session.commit() flash(f"New domain {new_custom_domain.domain} is created", "success") return redirect( url_for( "dashboard.domain_detail_dns", custom_domain_id=new_custom_domain.id, )) return render_template( "dashboard/custom_domain.html", custom_domains=custom_domains, new_custom_domain_form=new_custom_domain_form, EMAIL_SERVERS_WITH_PRIORITY=EMAIL_SERVERS_WITH_PRIORITY, errors=errors, )
def test_available_suffixes(flask_client): user = login(flask_client) db.session.commit() CustomDomain.create(user_id=user.id, domain="test.com", verified=True) assert len(available_suffixes(user)) > 0 # first suffix is custom domain first_suffix = available_suffixes(user)[0] assert first_suffix[0] assert first_suffix[1] == "@test.com" assert first_suffix[2].startswith("@test.com")
def test_can_be_used_as_personal_email(flask_client): # default alias domain assert not can_be_used_as_personal_email("*****@*****.**") assert not can_be_used_as_personal_email("*****@*****.**") assert can_be_used_as_personal_email("*****@*****.**") # custom domain user = User.create( email="[email protected]", password="******", name="Test User", activated=True ) db.session.commit() CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True) db.session.commit() assert not can_be_used_as_personal_email("*****@*****.**")
def test_cannot_create_alias_in_trash(flask_client): user = User.create(email="[email protected]", password="******", name="Test User", activated=True) db.session.commit() # create api_key api_key = ApiKey.create(user.id, "for test") db.session.commit() # create a custom domain CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True) db.session.commit() # create new alias with note suffix = "@ab.cd" suffix = signer.sign(suffix).decode() 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", }, ) # assert alias creation is successful assert r.status_code == 201 assert r.json["alias"] == "*****@*****.**" # delete alias: it's going to be moved to ab.cd trash alias = Alias.get_by(email="*****@*****.**") assert alias.custom_domain_id delete_alias(alias, user) # try to create the same alias, will fail as the alias is in trash 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", }, ) assert r.status_code == 409
def test_get_setting_domains(flask_client): user = login(flask_client) CustomDomain.create(user_id=user.id, domain="ab.cd", verified=True, commit=True) r = flask_client.get("/api/setting/domains") assert r.status_code == 200 assert r.json == [ [True, "d1.test"], [True, "d2.test"], [True, "sl.local"], [False, "ab.cd"], ]
def export_data(): """ Get user data Output: Alias, custom domain and app info """ user = g.user data = { "email": user.email, "name": user.name, "aliases": [], "apps": [], "custom_domains": [], } for alias in Alias.filter_by(user_id=user.id).all(): # type: Alias data["aliases"].append(dict(email=alias.email, enabled=alias.enabled)) for custom_domain in CustomDomain.filter_by(user_id=user.id).all(): data["custom_domains"].append(custom_domain.domain) for app in Client.filter_by(user_id=user.id): # type: Client data["apps"].append(dict(name=app.name, home_url=app.home_url)) return jsonify(data)
def test_available_suffixes_default_domain(flask_client): user = login(flask_client) sl_domain = SLDomain.query.first() CustomDomain.create(user_id=user.id, domain="test.com", verified=True, commit=True) user.default_alias_public_domain_id = sl_domain.id # first suffix is SL Domain first_suffix = get_available_suffixes(user)[0] assert first_suffix.suffix.endswith(f"@{sl_domain.domain}") user.default_alias_public_domain_id = None # first suffix is custom domain first_suffix = get_available_suffixes(user)[0] assert first_suffix.suffix == "@test.com"
def test_delete_subdomain(flask_client): user = login(flask_client) sl_domain = setup_sl_domain() subdomain = CustomDomain.create( domain=f"test.{sl_domain.domain}", user_id=user.id, is_sl_subdomain=True, commit=True, ) nb_job = Job.count() r = flask_client.post( url_for("dashboard.domain_detail", custom_domain_id=subdomain.id), data={"form-name": "delete"}, follow_redirects=True, ) assert r.status_code == 200 assert f"test.{sl_domain.domain} scheduled for deletion." in r.data.decode( ) # a domain deletion job is scheduled assert Job.count() == nb_job + 1
def custom_domain_to_dict(custom_domain: CustomDomain): return { "id": custom_domain.id, "domain_name": custom_domain.domain, "is_verified": custom_domain.verified, "nb_alias": custom_domain.nb_alias(), "creation_date": custom_domain.created_at.format(), "creation_timestamp": custom_domain.created_at.timestamp, "catch_all": custom_domain.catch_all, "name": custom_domain.name, "random_prefix_generation": custom_domain.random_prefix_generation, "mailboxes": [{ "id": mb.id, "email": mb.email } for mb in custom_domain.mailboxes], }
def import_from_csv(batch_import: BatchImport, user: User, lines): reader = csv.DictReader(lines) for row in reader: try: full_alias = sanitize_email(row["alias"]) note = row["note"] except KeyError: LOG.warning("Cannot parse row %s", row) continue alias_domain = get_email_domain_part(full_alias) custom_domain = CustomDomain.get_by(domain=alias_domain) if (not custom_domain or not custom_domain.verified or custom_domain.user_id != user.id): LOG.debug("domain %s can't be used %s", alias_domain, user) continue if (Alias.get_by(email=full_alias) or DeletedAlias.get_by(email=full_alias) or DomainDeletedAlias.get_by(email=full_alias)): LOG.d("alias already used %s", full_alias) continue mailboxes = [] if "mailboxes" in row: for mailbox_email in row["mailboxes"].split(): mailbox_email = sanitize_email(mailbox_email) mailbox = Mailbox.get_by(email=mailbox_email) if not mailbox or not mailbox.verified or mailbox.user_id != user.id: LOG.d("mailbox %s can't be used %s", mailbox, user) continue mailboxes.append(mailbox.id) if len(mailboxes) == 0: mailboxes = [user.default_mailbox_id] alias = Alias.create( user_id=user.id, email=full_alias, note=note, mailbox_id=mailboxes[0], custom_domain_id=custom_domain.id, batch_import_id=batch_import.id, ) db.session.commit() db.session.flush() LOG.d("Create %s", alias) for i in range(1, len(mailboxes)): alias_mailbox = AliasMailbox.create( alias_id=alias.id, mailbox_id=mailboxes[i], ) db.session.commit() LOG.d("Create %s", alias_mailbox)
def custom_domain_to_dict(custom_domain: CustomDomain): return { "id": custom_domain.id, "domain": custom_domain.domain, "verified": custom_domain.verified, "nb_alias": custom_domain.nb_alias(), }
def email_domain_can_be_used_as_mailbox(email: str) -> bool: """return True if an email can be used as a personal email. An email domain can be used if it is not - one of ALIAS_DOMAINS - one of custom domains - disposable domain """ domain = get_email_domain_part(email) if not domain: return False if domain in ALIAS_DOMAINS: return False from app.models import CustomDomain if CustomDomain.get_by(domain=domain, verified=True): return False if is_disposable_domain(domain): LOG.d("Domain %s is disposable", domain) return False # check if email MX domain is disposable mx_domains = get_mx_domain_list(domain) # if no MX record, email is not valid if not mx_domains: return False for mx_domain in mx_domains: if is_disposable_domain(mx_domain): LOG.d("MX Domain %s %s is disposable", mx_domain, domain) return False return True
def get_custom_domains(): user = g.user custom_domains = CustomDomain.filter_by(user_id=user.id, is_sl_subdomain=False).all() return jsonify( custom_domains=[custom_domain_to_dict(cd) for cd in custom_domains])