started adding the security info

This commit is contained in:
Alysia Broddrick 2023-07-26 16:18:33 -07:00
parent b7587758a3
commit d3bc00fdce
No known key found for this signature in database
GPG key ID: 03917052CD0F06B7
5 changed files with 337 additions and 86 deletions

View file

@ -125,6 +125,8 @@ class DomainAdmin(ListHeaderAdmin):
def response_change(self, request, obj): def response_change(self, request, obj):
ACTION_BUTTON = "_place_client_hold" ACTION_BUTTON = "_place_client_hold"
GET_SECURITY_EMAIL="_get_security_contact"
SET_SECURITY_EMAIL="_set_security_contact"
if ACTION_BUTTON in request.POST: if ACTION_BUTTON in request.POST:
try: try:
obj.place_client_hold() obj.place_client_hold()
@ -141,6 +143,43 @@ class DomainAdmin(ListHeaderAdmin):
) )
return HttpResponseRedirect(".") return HttpResponseRedirect(".")
if GET_SECURITY_EMAIL in request.POST:
try:
security_email=obj.get_security_email()
except Exception as err:
self.message_user(request, err, messages.ERROR)
else:
self.message_user(request,
(
"The security email is %"
". Thanks!"
)
% security_email,
)
return HttpResponseRedirect(".")
return super().response_change(request, obj)
def response_change(self, request, obj):
ACTION_BUTTON = "_get_security_email"
if ACTION_BUTTON in request.POST:
try:
obj.security
except Exception as err:
self.message_user(request, err, messages.ERROR)
else:
self.message_user(
request,
(
"%s is in client hold. This domain is no longer accessible on"
" the public internet."
)
% obj.name,
)
return HttpResponseRedirect(".")
return super().response_change(request, obj) return super().response_change(request, obj)

View file

@ -19,7 +19,6 @@ from .utility.domain_helper import DomainHelper
from .utility.time_stamped_model import TimeStampedModel from .utility.time_stamped_model import TimeStampedModel
from .public_contact import PublicContact from .public_contact import PublicContact
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -273,19 +272,92 @@ class Domain(TimeStampedModel, DomainHelper):
# use admin as type parameter for this contact # use admin as type parameter for this contact
raise NotImplementedError() raise NotImplementedError()
def get_default_security_contact(self):
logger.info("getting default sec contact")
contact = PublicContact.get_default_security()
contact.domain = self
return contact
def _update_domain_with_contact(self, contact:PublicContact,rem=False):
logger.info("received type %s " % contact.contact_type)
domainContact=epp.DomainContact(contact=contact.registry_id,type=contact.contact_type)
updateDomain=commands.UpdateDomain(name=self.name, add=[domainContact] )
if rem:
updateDomain=commands.UpdateDomain(name=self.name, rem=[domainContact] )
logger.info("Send updated")
try:
registry.send(updateDomain, cleaned=True)
except RegistryError as e:
logger.error("Error removing old secuity contact code was %s error was %s" % (e.code, e))
@Cache @Cache
def security_contact(self) -> PublicContact: def security_contact(self) -> PublicContact:
"""Get or set the security contact for this domain.""" """Get or set the security contact for this domain."""
# TODO: replace this with a real implementation
contact = PublicContact.get_default_security() #get the contacts: call _get_property(contacts=True)
contact.domain = self #if contacts exist and security contact is in the contact list
contact.email = "mayor@igorville.gov" #return that contact
return contact #else call the setter
# send the public default contact
try:
contacts=self._get_property("contacts")
except KeyError as err:
logger.info("Found a key error in security_contact get")
## send public contact to the thingy
##TODO - change to get or create in db?
default= self.get_default_security_contact()
# self._cache["contacts"]=[]
# self._cache["contacts"].append({"type":"security", "contact":default})
self.security_contact=default
return default
except Exception as e:
logger.error("found an error ")
logger.error(e)
else:
logger.info("Showing contacts")
for contact in contacts:
if isinstance(contact, dict) and "type" in contact.keys() and \
"contact" in contact.keys() and contact["type"]=="security":
return contact["contact"]
##TODO -get the security contact, requires changing the implemenation below and the parser from epplib
#request=InfoContact(securityID)
#contactInfo=...send(request)
#convert info to a PublicContact
#return the info in Public conta
#TODO - below line never executes with current logic
return self.get_default_security_contact()
@security_contact.setter # type: ignore @security_contact.setter # type: ignore
def security_contact(self, contact: PublicContact): def security_contact(self, contact: PublicContact):
# TODO: replace this with a real implementation """makes the contact in the registry,
pass for security the public contact should have the org or registrant information
from domain information (not domain application)
and should have the security email from DomainApplication"""
print("making contact in registry")
self._make_contact_in_registry(contact=contact)
#create update domain command with security contact
current_security_contact=self.security_contact
if self.security_contact.email is not None:
#if there is already a security contact
domainContact=epp.DomainContact(contact=current_security_contact.registry_id,type=current_security_contact.contact_type)
updateDomain=commands.UpdateDomain(name=self.name, rem=[domainContact] )
try:
registry.send(updateDomain, cleaned=True)
except RegistryError as e:
logger.error("Error removing old secuity contact code was %s error was %s" % (e.code, e))
addDomainContact=epp.DomainContact(contact=contact.registry_id,type=contact.contact_type)
updateDomainAdd=commands.UpdateDomain(name=self.name, rem=[addDomainContact] )
try:
registry.send(updateDomainAdd, cleaned=True)
except RegistryError as e:
logger.error("Error removing old security contact code was %s error was %s" % (e.code, e))
@Cache @Cache
def technical_contact(self) -> PublicContact: def technical_contact(self) -> PublicContact:
@ -315,6 +387,11 @@ class Domain(TimeStampedModel, DomainHelper):
"""This domain should not be active.""" """This domain should not be active."""
raise NotImplementedError("This is not implemented yet.") raise NotImplementedError("This is not implemented yet.")
def get_security_email(self):
logger.info("get_security_email-> getting the contact ")
secContact=self.security_contact
return secContact.email
def remove_client_hold(self): def remove_client_hold(self):
"""This domain is okay to be active.""" """This domain is okay to be active."""
raise NotImplementedError() raise NotImplementedError()
@ -380,6 +457,8 @@ class Domain(TimeStampedModel, DomainHelper):
already_tried_to_create = False already_tried_to_create = False
while True: while True:
try: try:
logger.info("_get_or_create_domain()-> getting info on the domain, should hit an error")
req = commands.InfoDomain(name=self.name) req = commands.InfoDomain(name=self.name)
return registry.send(req, cleaned=True).res_data[0] return registry.send(req, cleaned=True).res_data[0]
except RegistryError as e: except RegistryError as e:
@ -387,10 +466,22 @@ class Domain(TimeStampedModel, DomainHelper):
raise e raise e
if e.code == ErrorCode.OBJECT_DOES_NOT_EXIST: if e.code == ErrorCode.OBJECT_DOES_NOT_EXIST:
# avoid infinite loop # avoid infinite loop
already_tried_to_create = True already_tried_to_create = True
self._make_domain_in_registry
else:
logger.error(e)
logger.error(e.code)
raise e
def _make_domain_in_registry(self):
registrant = self._get_or_create_contact( registrant = self._get_or_create_contact(
PublicContact.get_default_registrant() PublicContact.get_default_registrant()
) )
#TODO-notes no chg item for registrant in the epplib should
already_tried_to_create = True
security_contact = self._get_or_create_contact(self.get_default_security_contact())
req = commands.CreateDomain( req = commands.CreateDomain(
name=self.name, name=self.name,
registrant=registrant.id, registrant=registrant.id,
@ -398,21 +489,17 @@ class Domain(TimeStampedModel, DomainHelper):
pw="2fooBAR123fooBaz" pw="2fooBAR123fooBaz"
), # not a password ), # not a password
) )
registry.send(req, cleaned=True) logger.info("_get_or_create_domain()-> about to send domain request")
response=registry.send(req, cleaned=True)
logger.info("_get_or_create_domain()-> registry received create for "+self.name)
logger.info(response)
# no error, so go ahead and update state # no error, so go ahead and update state
self.state = Domain.State.CREATED self.state = Domain.State.CREATED
self.save() self.save()
else: self._update_domain_with_contact(security_contact)
raise e def _make_contact_in_registry(self, contact: PublicContact):
"""Create the contact in the registry, ignore duplicate contact errors"""
def _get_or_create_contact(self, contact: PublicContact):
"""Try to fetch info about a contact. Create it if it does not exist."""
while True:
try:
req = commands.InfoContact(id=contact.registry_id)
return registry.send(req, cleaned=True).res_data[0]
except RegistryError as e:
if e.code == ErrorCode.OBJECT_DOES_NOT_EXIST:
create = commands.CreateContact( create = commands.CreateContact(
id=contact.registry_id, id=contact.registry_id,
postal_info=epp.PostalInfo( # type: ignore postal_info=epp.PostalInfo( # type: ignore
@ -447,7 +534,25 @@ class Domain(TimeStampedModel, DomainHelper):
fields={DF.FAX, DF.VOICE, DF.ADDR}, fields={DF.FAX, DF.VOICE, DF.ADDR},
types={DF.ADDR: "loc"}, types={DF.ADDR: "loc"},
) )
try:
registry.send(create) registry.send(create)
return contact
except RegistryError as err:
#don't throw an error if it is just saying this is a duplicate contact
if err.code!=ErrorCode.OBJECT_EXISTS:
raise err
else:
logger.warning("Registrar tried to create duplicate contact for id %s",contact.registry_id)
def _get_or_create_contact(self, contact: PublicContact):
"""Try to fetch info about a contact. Create it if it does not exist."""
while True:
try:
req = commands.InfoContact(id=contact.registry_id)
return registry.send(req, cleaned=True).res_data[0]
except RegistryError as e:
if e.code == ErrorCode.OBJECT_DOES_NOT_EXIST:
return self._make_contact_in_registry(contact=contact)
else: else:
raise e raise e
@ -461,6 +566,7 @@ class Domain(TimeStampedModel, DomainHelper):
"""Contact registry for info about a domain.""" """Contact registry for info about a domain."""
try: try:
# get info from registry # get info from registry
logger.info("_fetch_cache()-> fetching from cache, should create domain")
data = self._get_or_create_domain() data = self._get_or_create_domain()
# extract properties from response # extract properties from response
# (Ellipsis is used to mean "null") # (Ellipsis is used to mean "null")
@ -479,6 +585,7 @@ class Domain(TimeStampedModel, DomainHelper):
# remove null properties (to distinguish between "a value of None" and null) # remove null properties (to distinguish between "a value of None" and null)
cleaned = {k: v for k, v in cache.items() if v is not ...} cleaned = {k: v for k, v in cache.items() if v is not ...}
logger.info("_fetch_cache()-> cleaned is "+str(cleaned))
# get contact info, if there are any # get contact info, if there are any
if ( if (
@ -497,6 +604,8 @@ class Domain(TimeStampedModel, DomainHelper):
# extract properties from response # extract properties from response
# (Ellipsis is used to mean "null") # (Ellipsis is used to mean "null")
logger.info("_fetch_cache()->contacts are ")
logger.info(data)
contact = { contact = {
"id": id, "id": id,
"auth_info": getattr(data, "auth_info", ...), "auth_info": getattr(data, "auth_info", ...),
@ -514,6 +623,7 @@ class Domain(TimeStampedModel, DomainHelper):
cleaned["contacts"].append( cleaned["contacts"].append(
{k: v for k, v in contact.items() if v is not ...} {k: v for k, v in contact.items() if v is not ...}
) )
logger.info("_fetch_cache()-> after getting contacts cleaned is "+str(cleaned))
# get nameserver info, if there are any # get nameserver info, if there are any
if ( if (
@ -522,6 +632,7 @@ class Domain(TimeStampedModel, DomainHelper):
and isinstance(cleaned["_hosts"], list) and isinstance(cleaned["_hosts"], list)
and len(cleaned["_hosts"]) and len(cleaned["_hosts"])
): ):
##TODO- add elif in cache set it to be the old cache value, no point in removing
cleaned["hosts"] = [] cleaned["hosts"] = []
for name in cleaned["_hosts"]: for name in cleaned["_hosts"]:
# we do not use _get_or_create_* because we expect the object we # we do not use _get_or_create_* because we expect the object we

View file

@ -3,6 +3,8 @@
{% block field_sets %} {% block field_sets %}
<div class="submit-row"> <div class="submit-row">
<input type="submit" value="Place hold" name="_place_client_hold"> <input type="submit" value="Place hold" name="_place_client_hold">
<input type="submit" value="Place hold" name="_get_security_email">
<input type="submit" value="Place hold" name="_set_security_contact">
</div> </div>
{{ block.super }} {{ block.super }}
{% endblock %} {% endblock %}

View file

@ -10,14 +10,14 @@ import datetime
from registrar.models import Domain # add in DomainApplication, User, from registrar.models import Domain # add in DomainApplication, User,
from unittest import skip from unittest import skip
from epplibwrapper import commands from epplibwrapper import commands,common
from registrar.models.domain_application import DomainApplication from registrar.models.domain_application import DomainApplication
from registrar.models.domain_information import DomainInformation from registrar.models.domain_information import DomainInformation
from registrar.models.draft_domain import DraftDomain from registrar.models.draft_domain import DraftDomain
from registrar.models.public_contact import PublicContact
from registrar.models.user import User from registrar.models.user import User
class MockEppLib(TestCase):
class TestDomainCache(TestCase):
class fakedEppObject(object): class fakedEppObject(object):
"""""" """"""
@ -33,6 +33,12 @@ class TestDomainCache(TestCase):
contacts=["123"], contacts=["123"],
hosts=["fake.host.com"], hosts=["fake.host.com"],
) )
infoDomainNoContact= fakedEppObject(
"security",
cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35),
contacts=[],
hosts=["fake.host.com"],
)
mockDataInfoContact = fakedEppObject( mockDataInfoContact = fakedEppObject(
"anotherPw", cr_date=datetime.datetime(2023, 7, 25, 19, 45, 35) "anotherPw", cr_date=datetime.datetime(2023, 7, 25, 19, 45, 35)
) )
@ -43,19 +49,33 @@ class TestDomainCache(TestCase):
def mockSend(self, _request, cleaned): def mockSend(self, _request, cleaned):
"""""" """"""
if isinstance(_request, commands.InfoDomain): if isinstance(_request, commands.InfoDomain):
if getattr(_request,"name",None)=="security.gov":
return MagicMock(res_data=[self.infoDomainNoContact])
return MagicMock(res_data=[self.mockDataInfoDomain]) return MagicMock(res_data=[self.mockDataInfoDomain])
elif isinstance(_request, commands.InfoContact): elif isinstance(_request, commands.InfoContact):
return MagicMock(res_data=[self.mockDataInfoContact]) return MagicMock(res_data=[self.mockDataInfoContact])
return MagicMock(res_data=[self.mockDataInfoHosts]) return MagicMock(res_data=[self.mockDataInfoHosts])
def setUp(self): def setUp(self):
"""mock epp send function as this will fail locally""" """mock epp send function as this will fail locally"""
self.patcher = patch("registrar.models.domain.registry.send") self.mockSendPatch = patch("registrar.models.domain.registry.send")
self.mockedSendFunction = self.patcher.start() self.mockedSendFunction = self.mockSendPatch.start()
self.mockedSendFunction.side_effect = self.mockSend self.mockedSendFunction.side_effect = self.mockSend
def tearDown(self): def tearDown(self):
self.patcher.stop() self.mockSendPatch.stop()
class TestDomainCache(MockEppLib):
# def setUp(self):
# #call setup from the mock epplib
# super().setUp()
# def tearDown(self):
# #call setup from the mock epplib
# super().tearDown()
def test_cache_sets_resets(self): def test_cache_sets_resets(self):
"""Cache should be set on getter and reset on setter calls""" """Cache should be set on getter and reset on setter calls"""
@ -120,18 +140,17 @@ class TestDomainCache(TestCase):
# get and check hosts is set correctly # get and check hosts is set correctly
domain._get_property("hosts") domain._get_property("hosts")
self.assertEqual(domain._cache["hosts"], [expectedHostsDict]) self.assertEqual(domain._cache["hosts"], [expectedHostsDict])
##IS THERE AN ERROR HERE???,
class TestDomainCreation(TestCase): class TestDomainCreation(TestCase):
"""Rule: An approved domain application must result in a domain""" """Rule: An approved domain application must result in a domain"""
def setUp(self): # def setUp(self):
""" # """
Background: # Background:
Given that a valid domain application exists # Given that a valid domain application exists
""" # """
@skip("not implemented yet")
def test_approved_application_creates_domain_locally(self): def test_approved_application_creates_domain_locally(self):
""" """
Scenario: Analyst approves a domain application Scenario: Analyst approves a domain application
@ -139,6 +158,8 @@ class TestDomainCreation(TestCase):
Then a Domain exists in the database with the same `name` Then a Domain exists in the database with the same `name`
But a domain object does not exist in the registry But a domain object does not exist in the registry
""" """
patcher = patch("registrar.models.domain.Domain._get_or_create_domain")
mocked_domain_creation=patcher.start()
draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov") draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
user, _ = User.objects.get_or_create() user, _ = User.objects.get_or_create()
application = DomainApplication.objects.create( application = DomainApplication.objects.create(
@ -146,12 +167,12 @@ class TestDomainCreation(TestCase):
) )
# skip using the submit method # skip using the submit method
application.status = DomainApplication.SUBMITTED application.status = DomainApplication.SUBMITTED
#trnasistion to approve state #transition to approve state
application.approve() application.approve()
# should hav information present for this domain
# should be an information present for this domain
domain = Domain.objects.get(name="igorville.gov") domain = Domain.objects.get(name="igorville.gov")
self.assertTrue(domain) self.assertTrue(domain)
mocked_domain_creation.assert_not_called()
@skip("not implemented yet") @skip("not implemented yet")
@ -191,8 +212,11 @@ class TestDomainCreation(TestCase):
domain.save() domain.save()
self.assertIn("ok", domain.status) self.assertIn("ok", domain.status)
def tearDown(self) -> None:
Domain.objects.delete()
# User.objects.delete()
class TestRegistrantContacts(TestCase): class TestRegistrantContacts(MockEppLib):
"""Rule: Registrants may modify their WHOIS data""" """Rule: Registrants may modify their WHOIS data"""
def setUp(self): def setUp(self):
@ -201,9 +225,33 @@ class TestRegistrantContacts(TestCase):
Given the registrant is logged in Given the registrant is logged in
And the registrant is the admin on a domain And the registrant is the admin on a domain
""" """
pass super().setUp()
#mock create contact email extension
self.contactMailingAddressPatch = patch("registrar.models.domain.commands.command_extensions.CreateContactMailingAddressExtension")
self.mockCreateContactExtension=self.contactMailingAddressPatch.start()
@skip("not implemented yet") #mock create contact
self.createContactPatch = patch("registrar.models.domain.commands.CreateContact")
self.mockCreateContact=self.createContactPatch.start()
#mock the sending
self.domain,_ = Domain.objects.get_or_create(name="security.gov")
# draft_domain, _ = DraftDomain.objects.get_or_create(name="igorville.gov")
# user, _ = User.objects.get_or_create()
# self.application = DomainApplication.objects.create(
# creator=user, requested_domain=draft_domain
# )
# self.application.status = DomainApplication.SUBMITTED
#transition to approve state
def tearDown(self):
super().tearDown()
# self.contactMailingAddressPatch.stop()
# self.createContactPatch.stop()
# @skip("source code not implemented")
def test_no_security_email(self): def test_no_security_email(self):
""" """
Scenario: Registrant has not added a security contact email Scenario: Registrant has not added a security contact email
@ -212,7 +260,29 @@ class TestRegistrantContacts(TestCase):
Then the domain has a valid security contact with CISA defaults Then the domain has a valid security contact with CISA defaults
And disclose flags are set to keep the email address hidden And disclose flags are set to keep the email address hidden
""" """
raise print(self.domain)
#get security contact
expectedSecContact=PublicContact.get_default_security()
expectedSecContact.domain=self.domain
receivedSecContact=self.domain.security_contact
DF = common.DiscloseField
di = common.Disclose(flag=False, fields={DF.FAX, DF.VOICE, DF.ADDR}, types={DF.ADDR: "loc"})
#check docs here looks like we may have more than one address field but
addr = common.ContactAddr(street=[expectedSecContact.street1,expectedSecContact.street2,expectedSecContact.street3] , city=expectedSecContact.city, pc=expectedSecContact.pc, cc=expectedSecContact.cc, sp=expectedSecContact.sp)
pi = common.PostalInfo(name=expectedSecContact.name, addr=addr, org=expectedSecContact.org, type="loc")
ai = common.ContactAuthInfo(pw='feedabee')
expectedCreateCommand=commands.CreateContact(id=expectedSecContact.registry_id, postal_info=pi, email=expectedSecContact.email, voice=expectedSecContact.voice, fax=expectedSecContact.fax, auth_info=ai, disclose=di, vat=None, ident=None, notify_email=None)
expectedUpdateDomain =commands.UpdateDomain(name=self.domain.name, add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")])
#check that send has triggered the create command
self.mockedSendFunction.assert_any_call(expectedCreateCommand,True)
self.mockedSendFunction.assert_any_call(expectedUpdateDomain, True)
#check that the security contact sent is the same as the one recieved
self.assertEqual(receivedSecContact,expectedSecContact)
@skip("not implemented yet") @skip("not implemented yet")
def test_user_adds_security_email(self): def test_user_adds_security_email(self):
@ -224,7 +294,30 @@ class TestRegistrantContacts(TestCase):
And Domain sends `commands.UpdateDomain` to the registry with the newly And Domain sends `commands.UpdateDomain` to the registry with the newly
created contact of type 'security' created contact of type 'security'
""" """
raise #make a security contact that is a PublicContact
expectedSecContact=PublicContact.get_default_security()
expectedSecContact.domain=self.domain
expectedSecContact.email="newEmail@fake.com"
expectedSecContact.registry_id="456"
expectedSecContact.name="Fakey McPhakerson"
self.domain.security_contact=expectedSecContact
#check create contact sent with email
DF = common.DiscloseField
di = common.Disclose(flag=False, fields={DF.FAX, DF.VOICE, DF.ADDR}, types={DF.ADDR: "loc"})
addr = common.ContactAddr(street=[expectedSecContact.street1,expectedSecContact.street2,expectedSecContact.street3] , city=expectedSecContact.city, pc=expectedSecContact.pc, cc=expectedSecContact.cc, sp=expectedSecContact.sp)
pi = common.PostalInfo(name=expectedSecContact.name, addr=addr, org=expectedSecContact.org, type="loc")
ai = common.ContactAuthInfo(pw='feedabee')
expectedCreateCommand=commands.CreateContact(id=expectedSecContact.registry_id, postal_info=pi, email=expectedSecContact.email, voice=expectedSecContact.voice, fax=expectedSecContact.fax, auth_info=ai, disclose=di, vat=None, ident=None, notify_email=None)
expectedUpdateDomain =commands.UpdateDomain(name=self.domain.name, add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")])
#check that send has triggered the create command for the contact
self.mockedSendFunction.assert_any_call(expectedCreateCommand, True)
##check domain contact was updated
self.mockedSendFunction.assert_any_call(expectedUpdateDomain, True)
@skip("not implemented yet") @skip("not implemented yet")
def test_security_email_is_idempotent(self): def test_security_email_is_idempotent(self):
@ -237,6 +330,10 @@ class TestRegistrantContacts(TestCase):
# implementation note: this requires seeing what happens when these are actually # implementation note: this requires seeing what happens when these are actually
# sent like this, and then implementing appropriate mocks for any errors the # sent like this, and then implementing appropriate mocks for any errors the
# registry normally sends in this case # registry normally sends in this case
#will send epplibwrapper.errors.RegistryError with code 2302 for a duplicate contact
#set the smae fake contact to the email
#show no errors
raise raise
@skip("not implemented yet") @skip("not implemented yet")
@ -428,7 +525,7 @@ class TestRegistrantDNSSEC(TestCase):
def test_user_adds_dns_data(self): def test_user_adds_dns_data(self):
""" """
Scenario: Registrant adds DNS data Scenario: Registrant adds DNS data
...
""" """
raise raise
@ -436,7 +533,7 @@ class TestRegistrantDNSSEC(TestCase):
def test_dnssec_is_idempotent(self): def test_dnssec_is_idempotent(self):
""" """
Scenario: Registrant adds DNS data twice, due to a UI glitch Scenario: Registrant adds DNS data twice, due to a UI glitch
...
""" """
# implementation note: this requires seeing what happens when these are actually # implementation note: this requires seeing what happens when these are actually
# sent like this, and then implementing appropriate mocks for any errors the # sent like this, and then implementing appropriate mocks for any errors the

View file

@ -269,6 +269,8 @@ class DomainSecurityEmailView(DomainPermissionView, FormMixin):
contact.email = new_email contact.email = new_email
contact.save() contact.save()
##update security email here
#call the setter
messages.success( messages.success(
self.request, "The security email for this domain have been updated." self.request, "The security email for this domain have been updated."
) )