make sure a deleted subdomain can't be recreated
This commit is contained in:
parent
5a81c08e32
commit
a8c86785d1
|
@ -3,6 +3,7 @@ from flask_login import login_required, current_user
|
||||||
|
|
||||||
from app.config import MAX_NB_SUBDOMAIN
|
from app.config import MAX_NB_SUBDOMAIN
|
||||||
from app.dashboard.base import dashboard_bp
|
from app.dashboard.base import dashboard_bp
|
||||||
|
from app.errors import SubdomainInTrashError
|
||||||
from app.log import LOG
|
from app.log import LOG
|
||||||
from app.models import CustomDomain, Mailbox, SLDomain
|
from app.models import CustomDomain, Mailbox, SLDomain
|
||||||
|
|
||||||
|
@ -51,6 +52,7 @@ def subdomain_route():
|
||||||
).first():
|
).first():
|
||||||
flash(f"{full_domain} already used in a SimpleLogin mailbox", "error")
|
flash(f"{full_domain} already used in a SimpleLogin mailbox", "error")
|
||||||
else:
|
else:
|
||||||
|
try:
|
||||||
new_custom_domain = CustomDomain.create(
|
new_custom_domain = CustomDomain.create(
|
||||||
is_sl_subdomain=True,
|
is_sl_subdomain=True,
|
||||||
catch_all=True, # by default catch-all is enabled
|
catch_all=True, # by default catch-all is enabled
|
||||||
|
@ -63,7 +65,12 @@ def subdomain_route():
|
||||||
ownership_verified=True,
|
ownership_verified=True,
|
||||||
commit=True,
|
commit=True,
|
||||||
)
|
)
|
||||||
|
except SubdomainInTrashError:
|
||||||
|
flash(
|
||||||
|
f"{full_domain} has been used before and cannot be reused",
|
||||||
|
"error",
|
||||||
|
)
|
||||||
|
else:
|
||||||
flash(
|
flash(
|
||||||
f"New subdomain {new_custom_domain.domain} is created",
|
f"New subdomain {new_custom_domain.domain} is created",
|
||||||
"success",
|
"success",
|
||||||
|
|
|
@ -8,3 +8,9 @@ class DirectoryInTrashError(Exception):
|
||||||
"""raised when a directory is deleted before """
|
"""raised when a directory is deleted before """
|
||||||
|
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class SubdomainInTrashError(Exception):
|
||||||
|
"""raised when a subdomain is deleted before """
|
||||||
|
|
||||||
|
pass
|
||||||
|
|
|
@ -32,7 +32,7 @@ from app.config import (
|
||||||
ALIAS_RANDOM_SUFFIX_LENGTH,
|
ALIAS_RANDOM_SUFFIX_LENGTH,
|
||||||
)
|
)
|
||||||
from app.db import Session
|
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.log import LOG
|
||||||
from app.oauth_models import Scope
|
from app.oauth_models import Scope
|
||||||
from app.pw_models import PasswordOracle
|
from app.pw_models import PasswordOracle
|
||||||
|
@ -1979,8 +1979,12 @@ class CustomDomain(Base, ModelMixin):
|
||||||
return f"sl-verification={self.ownership_txt_token}"
|
return f"sl-verification={self.ownership_txt_token}"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def create(cls, **kw):
|
def create(cls, **kwargs):
|
||||||
domain: CustomDomain = super(CustomDomain, cls).create(**kw)
|
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
|
# generate a domain ownership txt token
|
||||||
if not domain.ownership_txt_token:
|
if not domain.ownership_txt_token:
|
||||||
|
@ -1989,6 +1993,14 @@ class CustomDomain(Base, ModelMixin):
|
||||||
|
|
||||||
return domain
|
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
|
@property
|
||||||
def auto_create_rules(self):
|
def auto_create_rules(self):
|
||||||
return sorted(self._auto_create_rules, key=lambda rule: rule.order)
|
return sorted(self._auto_create_rules, key=lambda rule: rule.order)
|
||||||
|
|
83
tests/dashboard/test_subdomain.py
Normal file
83
tests/dashboard/test_subdomain.py
Normal 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()
|
||||||
|
)
|
Loading…
Reference in a new issue