make sure a deleted subdomain can't be recreated

This commit is contained in:
Son 2021-11-17 17:21:13 +01:00
parent 5a81c08e32
commit a8c86785d1
4 changed files with 134 additions and 26 deletions

View File

@ -3,6 +3,7 @@ from flask_login import login_required, current_user
from app.config import MAX_NB_SUBDOMAIN
from app.dashboard.base import dashboard_bp
from app.errors import SubdomainInTrashError
from app.log import LOG
from app.models import CustomDomain, Mailbox, SLDomain
@ -51,30 +52,36 @@ def subdomain_route():
).first():
flash(f"{full_domain} already used in a SimpleLogin mailbox", "error")
else:
new_custom_domain = CustomDomain.create(
is_sl_subdomain=True,
catch_all=True, # by default catch-all is enabled
domain=full_domain,
user_id=current_user.id,
verified=True,
dkim_verified=False, # wildcard DNS does not work for DKIM
spf_verified=True,
dmarc_verified=False, # wildcard DNS does not work for DMARC
ownership_verified=True,
commit=True,
)
flash(
f"New subdomain {new_custom_domain.domain} is created",
"success",
)
return redirect(
url_for(
"dashboard.domain_detail",
custom_domain_id=new_custom_domain.id,
try:
new_custom_domain = CustomDomain.create(
is_sl_subdomain=True,
catch_all=True, # by default catch-all is enabled
domain=full_domain,
user_id=current_user.id,
verified=True,
dkim_verified=False, # wildcard DNS does not work for DKIM
spf_verified=True,
dmarc_verified=False, # wildcard DNS does not work for DMARC
ownership_verified=True,
commit=True,
)
except SubdomainInTrashError:
flash(
f"{full_domain} has been used before and cannot be reused",
"error",
)
else:
flash(
f"New subdomain {new_custom_domain.domain} is created",
"success",
)
return redirect(
url_for(
"dashboard.domain_detail",
custom_domain_id=new_custom_domain.id,
)
)
)
return render_template(
"dashboard/subdomain.html",

View File

@ -8,3 +8,9 @@ class DirectoryInTrashError(Exception):
"""raised when a directory is deleted before """
pass
class SubdomainInTrashError(Exception):
"""raised when a subdomain is deleted before """
pass

View File

@ -32,7 +32,7 @@ from app.config import (
ALIAS_RANDOM_SUFFIX_LENGTH,
)
from app.db import Session
from app.errors import AliasInTrashError, DirectoryInTrashError
from app.errors import AliasInTrashError, DirectoryInTrashError, SubdomainInTrashError
from app.log import LOG
from app.oauth_models import Scope
from app.pw_models import PasswordOracle
@ -1979,8 +1979,12 @@ class CustomDomain(Base, ModelMixin):
return f"sl-verification={self.ownership_txt_token}"
@classmethod
def create(cls, **kw):
domain: CustomDomain = super(CustomDomain, cls).create(**kw)
def create(cls, **kwargs):
domain = kwargs.get("domain")
if DeletedSubdomain.get_by(domain=domain):
raise SubdomainInTrashError
domain: CustomDomain = super(CustomDomain, cls).create(**kwargs)
# generate a domain ownership txt token
if not domain.ownership_txt_token:
@ -1989,6 +1993,14 @@ class CustomDomain(Base, ModelMixin):
return domain
@classmethod
def delete(cls, obj_id):
obj: CustomDomain = cls.get(obj_id)
if obj.is_sl_subdomain:
DeletedSubdomain.create(domain=obj.domain)
return super(CustomDomain, cls).delete(obj_id)
@property
def auto_create_rules(self):
return sorted(self._auto_create_rules, key=lambda rule: rule.order)

View File

@ -0,0 +1,83 @@
from flask import url_for
from app.db import Session
from app.models import SLDomain, CustomDomain, Job
from tests.utils import login
def setup_sl_domain() -> SLDomain:
"""Take the first SLDomain and set its can_use_subdomain=True"""
sl_domain: SLDomain = SLDomain.first()
sl_domain.can_use_subdomain = True
Session.commit()
return sl_domain
def test_create_subdomain(flask_client):
login(flask_client)
sl_domain = setup_sl_domain()
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"New subdomain test.{sl_domain.domain} is created" in r.data.decode()
assert CustomDomain.get_by(domain=f"test.{sl_domain.domain}") is not None
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 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()
)