mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-06-01 10:13:55 +02:00
Preliminary changes
This commit is contained in:
parent
9c1e3fa013
commit
c6baf9c98b
5 changed files with 197 additions and 36 deletions
|
@ -6,11 +6,12 @@ from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||||
from django.contrib.contenttypes.models import ContentType
|
from django.contrib.contenttypes.models import ContentType
|
||||||
from django.http.response import HttpResponseRedirect
|
from django.http.response import HttpResponseRedirect
|
||||||
from django.urls import reverse
|
from django.urls import reverse
|
||||||
|
from epplibwrapper.errors import ErrorCode, RegistryError
|
||||||
from registrar.models.utility.admin_sort_fields import AdminSortFields
|
from registrar.models.utility.admin_sort_fields import AdminSortFields
|
||||||
from . import models
|
from . import models
|
||||||
from auditlog.models import LogEntry # type: ignore
|
from auditlog.models import LogEntry # type: ignore
|
||||||
from auditlog.admin import LogEntryAdmin # type: ignore
|
from auditlog.admin import LogEntryAdmin # type: ignore
|
||||||
|
from django_fsm import TransitionNotAllowed # type: ignore
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
@ -717,10 +718,46 @@ class DomainAdmin(ListHeaderAdmin):
|
||||||
|
|
||||||
def do_delete_domain(self, request, obj):
|
def do_delete_domain(self, request, obj):
|
||||||
try:
|
try:
|
||||||
obj.deleted()
|
obj.deletedInEpp()
|
||||||
obj.save()
|
obj.save()
|
||||||
except Exception as err:
|
except RegistryError as err:
|
||||||
|
if err.is_connection_error():
|
||||||
|
self.message_user(
|
||||||
|
request,
|
||||||
|
"Error connecting to the registry",
|
||||||
|
messages.ERROR,
|
||||||
|
)
|
||||||
|
elif err.code == ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION:
|
||||||
|
self.message_user(
|
||||||
|
request,
|
||||||
|
"Error deleting this Domain: "
|
||||||
|
f"Cannot delete Domain when in status {obj.status}",
|
||||||
|
messages.ERROR,
|
||||||
|
)
|
||||||
|
elif err.code == ErrorCode.OBJECT_ASSOCIATION_PROHIBITS_OPERATION:
|
||||||
|
self.message_user(
|
||||||
|
request,
|
||||||
|
"Error deleting this Domain: "
|
||||||
|
f" This subdomain is being used as a hostname on another domain",
|
||||||
|
messages.ERROR,
|
||||||
|
)
|
||||||
|
elif err.code:
|
||||||
|
self.message_user(
|
||||||
|
request,
|
||||||
|
f"Error deleting this Domain: {err}",
|
||||||
|
messages.ERROR,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
# all other type error messages, display the error
|
||||||
|
self.message_user(request, err, messages.ERROR)
|
||||||
|
except ValueError as err:
|
||||||
self.message_user(request, err, messages.ERROR)
|
self.message_user(request, err, messages.ERROR)
|
||||||
|
except TransitionNotAllowed
|
||||||
|
self.message_user(
|
||||||
|
request,
|
||||||
|
f"Error deleting this Domain: {err}",
|
||||||
|
messages.ERROR,
|
||||||
|
)
|
||||||
else:
|
else:
|
||||||
self.message_user(
|
self.message_user(
|
||||||
request,
|
request,
|
||||||
|
|
|
@ -609,11 +609,6 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
"""
|
"""
|
||||||
return self.state == self.State.READY
|
return self.state == self.State.READY
|
||||||
|
|
||||||
def delete_request(self):
|
|
||||||
"""Delete from host. Possibly a duplicate of _delete_host?"""
|
|
||||||
# TODO fix in ticket #901
|
|
||||||
pass
|
|
||||||
|
|
||||||
def transfer(self):
|
def transfer(self):
|
||||||
"""Going somewhere. Not implemented."""
|
"""Going somewhere. Not implemented."""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
@ -658,7 +653,8 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
"""This domain should be deleted from the registry
|
"""This domain should be deleted from the registry
|
||||||
may raises RegistryError, should be caught or handled correctly by caller"""
|
may raises RegistryError, should be caught or handled correctly by caller"""
|
||||||
request = commands.DeleteDomain(name=self.name)
|
request = commands.DeleteDomain(name=self.name)
|
||||||
registry.send(request)
|
response = registry.send(request, cleaned=True)
|
||||||
|
return response
|
||||||
|
|
||||||
def __str__(self) -> str:
|
def __str__(self) -> str:
|
||||||
return self.name
|
return self.name
|
||||||
|
@ -773,6 +769,8 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
|
|
||||||
self.addAllDefaults()
|
self.addAllDefaults()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def addAllDefaults(self):
|
def addAllDefaults(self):
|
||||||
security_contact = self.get_default_security_contact()
|
security_contact = self.get_default_security_contact()
|
||||||
security_contact.save()
|
security_contact.save()
|
||||||
|
@ -805,15 +803,34 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
# TODO -on the client hold ticket any additional error handling here
|
# TODO -on the client hold ticket any additional error handling here
|
||||||
|
|
||||||
@transition(field="state", source=State.ON_HOLD, target=State.DELETED)
|
@transition(field="state", source=State.ON_HOLD, target=State.DELETED)
|
||||||
def deleted(self):
|
def deletedInEpp(self):
|
||||||
"""domain is deleted in epp but is saved in our database"""
|
"""domain is deleted in epp but is saved in our database.
|
||||||
# TODO Domains may not be deleted if:
|
Returns the request_code"""
|
||||||
# a child host is being used by
|
valid_delete_states = [
|
||||||
# another .gov domains. The host must be first removed
|
self.State.ON_HOLD,
|
||||||
# and/or renamed before the parent domain may be deleted.
|
self.State.DNS_NEEDED
|
||||||
logger.info("pendingCreate()-> inside pending create")
|
]
|
||||||
self._delete_domain()
|
# Check that the domain contacts a valid status
|
||||||
# TODO - delete ticket any additional error handling here
|
if (self.state not in valid_delete_states):
|
||||||
|
raise ValueError(
|
||||||
|
f"Invalid domain state of {self.state}. Cannot delete."
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
logger.info("deletedInEpp()-> inside _delete_domain")
|
||||||
|
self._delete_domain()
|
||||||
|
except RegistryError as err:
|
||||||
|
logger.error(
|
||||||
|
f"Could not delete domain. Registry returned error: {err}"
|
||||||
|
)
|
||||||
|
raise err
|
||||||
|
except Exception as err:
|
||||||
|
logger.error(
|
||||||
|
f"Could not delete domain. An unspecified error occured: {err}"
|
||||||
|
)
|
||||||
|
raise err
|
||||||
|
else:
|
||||||
|
self._invalidate_cache()
|
||||||
|
|
||||||
@transition(
|
@transition(
|
||||||
field="state",
|
field="state",
|
||||||
|
|
|
@ -598,7 +598,11 @@ class DomainApplication(TimeStampedModel):
|
||||||
"emails/domain_request_withdrawn.txt",
|
"emails/domain_request_withdrawn.txt",
|
||||||
"emails/domain_request_withdrawn_subject.txt",
|
"emails/domain_request_withdrawn_subject.txt",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# TODO
|
||||||
|
#def delete(self, *args, **kwargs):
|
||||||
|
#super().delete(*args, **kwargs)
|
||||||
|
|
||||||
@transition(
|
@transition(
|
||||||
field="status",
|
field="status",
|
||||||
source=[IN_REVIEW, APPROVED],
|
source=[IN_REVIEW, APPROVED],
|
||||||
|
@ -612,7 +616,7 @@ class DomainApplication(TimeStampedModel):
|
||||||
(will cascade), and send an email notification."""
|
(will cascade), and send an email notification."""
|
||||||
|
|
||||||
if self.status == self.APPROVED:
|
if self.status == self.APPROVED:
|
||||||
self.approved_domain.delete_request()
|
self.approved_domain.deletedInEpp()
|
||||||
self.approved_domain.delete()
|
self.approved_domain.delete()
|
||||||
self.approved_domain = None
|
self.approved_domain = None
|
||||||
|
|
||||||
|
|
|
@ -604,6 +604,13 @@ class MockEppLib(TestCase):
|
||||||
# use this for when a contact is being updated
|
# use this for when a contact is being updated
|
||||||
# sets the second send() to fail
|
# sets the second send() to fail
|
||||||
raise RegistryError(code=ErrorCode.OBJECT_EXISTS)
|
raise RegistryError(code=ErrorCode.OBJECT_EXISTS)
|
||||||
|
elif (
|
||||||
|
isinstance(_request, commands.DeleteDomain)
|
||||||
|
and getattr(_request, "name", None) == "fail.gov"
|
||||||
|
):
|
||||||
|
raise RegistryError(
|
||||||
|
code=ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION
|
||||||
|
)
|
||||||
return MagicMock(res_data=[self.mockDataInfoHosts])
|
return MagicMock(res_data=[self.mockDataInfoHosts])
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
|
|
|
@ -940,39 +940,95 @@ class TestAnalystLock(TestCase):
|
||||||
raise
|
raise
|
||||||
|
|
||||||
|
|
||||||
class TestAnalystDelete(TestCase):
|
class TestAnalystDelete(MockEppLib):
|
||||||
"""Rule: Analysts may delete a domain"""
|
"""Rule: Analysts may delete a domain"""
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
"""
|
"""
|
||||||
Background:
|
Background:
|
||||||
Given the analyst is logged in
|
Given the analyst is logged in
|
||||||
And a domain exists in the registry
|
And a domain exists in the registry
|
||||||
"""
|
"""
|
||||||
pass
|
super().setUp()
|
||||||
|
self.domain, _ = Domain.objects.get_or_create(
|
||||||
|
name="fake.gov", state=Domain.State.READY
|
||||||
|
)
|
||||||
|
self.domain_on_hold, _ = Domain.objects.get_or_create(
|
||||||
|
name="fake-on-hold.gov", state=Domain.State.ON_HOLD
|
||||||
|
)
|
||||||
|
|
||||||
|
def tearDown(self):
|
||||||
|
Domain.objects.all().delete()
|
||||||
|
super().tearDown()
|
||||||
|
|
||||||
@skip("not implemented yet")
|
|
||||||
def test_analyst_deletes_domain(self):
|
def test_analyst_deletes_domain(self):
|
||||||
"""
|
"""
|
||||||
Scenario: Analyst permanently deletes a domain
|
Scenario: Analyst permanently deletes a domain
|
||||||
When `domain.delete()` is called
|
When `domain.deletedInEpp()` is called
|
||||||
Then `commands.DeleteDomain` is sent to the registry
|
Then `commands.DeleteDomain` is sent to the registry
|
||||||
And `state` is set to `DELETED`
|
And `state` is set to `DELETED`
|
||||||
"""
|
"""
|
||||||
raise
|
# Put the domain in client hold
|
||||||
|
self.domain.place_client_hold()
|
||||||
|
# Delete it...
|
||||||
|
self.domain.deletedInEpp()
|
||||||
|
self.mockedSendFunction.assert_has_calls(
|
||||||
|
[
|
||||||
|
call(
|
||||||
|
commands.DeleteDomain(name="fake.gov"),
|
||||||
|
cleaned=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# Domain itself should not be deleted
|
||||||
|
self.assertNotEqual(self.domain, None)
|
||||||
|
# Domain should have the right state
|
||||||
|
self.assertEqual(self.domain.state, Domain.State.DELETED)
|
||||||
|
|
||||||
@skip("not implemented yet")
|
|
||||||
def test_analyst_deletes_domain_idempotent(self):
|
def test_analyst_deletes_domain_idempotent(self):
|
||||||
"""
|
"""
|
||||||
Scenario: Analyst tries to delete an already deleted domain
|
Scenario: Analyst tries to delete an already deleted domain
|
||||||
Given `state` is already `DELETED`
|
Given `state` is already `DELETED`
|
||||||
When `domain.delete()` is called
|
When `domain.deletedInEpp()` is called
|
||||||
Then `commands.DeleteDomain` is sent to the registry
|
Then `commands.DeleteDomain` is sent to the registry
|
||||||
And Domain returns normally (without error)
|
And Domain returns normally (without error)
|
||||||
"""
|
"""
|
||||||
raise
|
# Put the domain in client hold
|
||||||
|
self.domain.place_client_hold()
|
||||||
|
# Delete it...
|
||||||
|
self.domain.deletedInEpp()
|
||||||
|
self.mockedSendFunction.assert_has_calls(
|
||||||
|
[
|
||||||
|
call(
|
||||||
|
commands.DeleteDomain(name="fake.gov"),
|
||||||
|
cleaned=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# Domain itself should not be deleted
|
||||||
|
self.assertNotEqual(self.domain, None)
|
||||||
|
# Domain should have the right state
|
||||||
|
self.assertEqual(self.domain.state, Domain.State.DELETED)
|
||||||
|
|
||||||
|
# Delete it again - monitoring for errors
|
||||||
|
try:
|
||||||
|
self.domain.deletedInEpp()
|
||||||
|
except Exception as err:
|
||||||
|
self.fail("deletedInEpp() threw an error")
|
||||||
|
raise err
|
||||||
|
|
||||||
|
self.mockedSendFunction.assert_has_calls(
|
||||||
|
[
|
||||||
|
call(
|
||||||
|
commands.DeleteDomain(name="fake.gov"),
|
||||||
|
cleaned=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# Domain itself should not be deleted
|
||||||
|
self.assertNotEqual(self.domain, None)
|
||||||
|
# Domain should have the right state
|
||||||
|
self.assertEqual(self.domain.state, Domain.State.DELETED)
|
||||||
|
|
||||||
@skip("not implemented yet")
|
|
||||||
def test_deletion_is_unsuccessful(self):
|
def test_deletion_is_unsuccessful(self):
|
||||||
"""
|
"""
|
||||||
Scenario: Domain deletion is unsuccessful
|
Scenario: Domain deletion is unsuccessful
|
||||||
|
@ -980,4 +1036,44 @@ class TestAnalystDelete(TestCase):
|
||||||
Then a user-friendly error message is returned for displaying on the web
|
Then a user-friendly error message is returned for displaying on the web
|
||||||
And `state` is not set to `DELETED`
|
And `state` is not set to `DELETED`
|
||||||
"""
|
"""
|
||||||
raise
|
domain, _ = Domain.objects.get_or_create(
|
||||||
|
name="fail.gov", state=Domain.State.ON_HOLD
|
||||||
|
)
|
||||||
|
# Put the domain in client hold
|
||||||
|
domain.place_client_hold()
|
||||||
|
# Delete it...
|
||||||
|
|
||||||
|
with self.assertRaises(RegistryError) as err:
|
||||||
|
domain.deletedInEpp()
|
||||||
|
self.assertTrue(
|
||||||
|
err.is_client_error()
|
||||||
|
and err.code == ErrorCode.OBJECT_STATUS_PROHIBITS_OPERATION
|
||||||
|
)
|
||||||
|
# TODO - check UI for error
|
||||||
|
# Domain itself should not be deleted
|
||||||
|
self.assertNotEqual(domain, None)
|
||||||
|
# State should not have changed
|
||||||
|
self.assertEqual(domain.state, Domain.State.ON_HOLD)
|
||||||
|
|
||||||
|
@skip("not implemented yet")
|
||||||
|
def test_deletion_ready_fsm_failure(self):
|
||||||
|
"""
|
||||||
|
Scenario: Domain deletion is unsuccessful due to FSM rules
|
||||||
|
Given state is 'ready'
|
||||||
|
When `domain.deletedInEpp()` is called
|
||||||
|
Then a user-friendly error message is returned for displaying on the web
|
||||||
|
And `state` is not set to `DELETED`
|
||||||
|
"""
|
||||||
|
self.domain.deletedInEpp()
|
||||||
|
self.mockedSendFunction.assert_has_calls(
|
||||||
|
[
|
||||||
|
call(
|
||||||
|
commands.DeleteDomain(name="fake.gov", auth_info=None),
|
||||||
|
cleaned=True,
|
||||||
|
)
|
||||||
|
]
|
||||||
|
)
|
||||||
|
# Domain should not be deleted
|
||||||
|
self.assertNotEqual(self.domain, None)
|
||||||
|
# Domain should have the right state
|
||||||
|
self.assertEqual(self.domain.state, "DELETED")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue