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):
ACTION_BUTTON = "_place_client_hold"
GET_SECURITY_EMAIL="_get_security_contact"
SET_SECURITY_EMAIL="_set_security_contact"
if ACTION_BUTTON in request.POST:
try:
obj.place_client_hold()
@ -141,6 +143,43 @@ class DomainAdmin(ListHeaderAdmin):
)
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)

View file

@ -19,7 +19,6 @@ from .utility.domain_helper import DomainHelper
from .utility.time_stamped_model import TimeStampedModel
from .public_contact import PublicContact
logger = logging.getLogger(__name__)
@ -273,19 +272,92 @@ class Domain(TimeStampedModel, DomainHelper):
# use admin as type parameter for this contact
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
def security_contact(self) -> PublicContact:
"""Get or set the security contact for this domain."""
# TODO: replace this with a real implementation
contact = PublicContact.get_default_security()
contact.domain = self
contact.email = "mayor@igorville.gov"
return contact
#get the contacts: call _get_property(contacts=True)
#if contacts exist and security contact is in the contact list
#return that 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
def security_contact(self, contact: PublicContact):
# TODO: replace this with a real implementation
pass
"""makes the contact in the registry,
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
def technical_contact(self) -> PublicContact:
@ -315,6 +387,11 @@ class Domain(TimeStampedModel, DomainHelper):
"""This domain should not be active."""
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):
"""This domain is okay to be active."""
raise NotImplementedError()
@ -380,6 +457,8 @@ class Domain(TimeStampedModel, DomainHelper):
already_tried_to_create = False
while True:
try:
logger.info("_get_or_create_domain()-> getting info on the domain, should hit an error")
req = commands.InfoDomain(name=self.name)
return registry.send(req, cleaned=True).res_data[0]
except RegistryError as e:
@ -387,23 +466,83 @@ class Domain(TimeStampedModel, DomainHelper):
raise e
if e.code == ErrorCode.OBJECT_DOES_NOT_EXIST:
# avoid infinite loop
already_tried_to_create = True
registrant = self._get_or_create_contact(
PublicContact.get_default_registrant()
)
req = commands.CreateDomain(
name=self.name,
registrant=registrant.id,
auth_info=epp.DomainAuthInfo(
pw="2fooBAR123fooBaz"
), # not a password
)
registry.send(req, cleaned=True)
# no error, so go ahead and update state
self.state = Domain.State.CREATED
self.save()
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(
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(
name=self.name,
registrant=registrant.id,
auth_info=epp.DomainAuthInfo(
pw="2fooBAR123fooBaz"
), # not a password
)
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
self.state = Domain.State.CREATED
self.save()
self._update_domain_with_contact(security_contact)
def _make_contact_in_registry(self, contact: PublicContact):
"""Create the contact in the registry, ignore duplicate contact errors"""
create = commands.CreateContact(
id=contact.registry_id,
postal_info=epp.PostalInfo( # type: ignore
name=contact.name,
addr=epp.ContactAddr(
street=[
getattr(contact, street)
for street in ["street1", "street2", "street3"]
if hasattr(contact, street)
],
city=contact.city,
pc=contact.pc,
cc=contact.cc,
sp=contact.sp,
),
org=contact.org,
type="loc",
),
email=contact.email,
voice=contact.voice,
fax=contact.fax,
auth_info=epp.ContactAuthInfo(pw="2fooBAR123fooBaz"),
)
# security contacts should only show email addresses, for now
if (
contact.contact_type
== PublicContact.ContactTypeChoices.SECURITY
):
DF = epp.DiscloseField
create.disclose = epp.Disclose(
flag=False,
fields={DF.FAX, DF.VOICE, DF.ADDR},
types={DF.ADDR: "loc"},
)
try:
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."""
@ -413,41 +552,7 @@ class Domain(TimeStampedModel, DomainHelper):
return registry.send(req, cleaned=True).res_data[0]
except RegistryError as e:
if e.code == ErrorCode.OBJECT_DOES_NOT_EXIST:
create = commands.CreateContact(
id=contact.registry_id,
postal_info=epp.PostalInfo( # type: ignore
name=contact.name,
addr=epp.ContactAddr(
street=[
getattr(contact, street)
for street in ["street1", "street2", "street3"]
if hasattr(contact, street)
],
city=contact.city,
pc=contact.pc,
cc=contact.cc,
sp=contact.sp,
),
org=contact.org,
type="loc",
),
email=contact.email,
voice=contact.voice,
fax=contact.fax,
auth_info=epp.ContactAuthInfo(pw="2fooBAR123fooBaz"),
)
# security contacts should only show email addresses, for now
if (
contact.contact_type
== PublicContact.ContactTypeChoices.SECURITY
):
DF = epp.DiscloseField
create.disclose = epp.Disclose(
flag=False,
fields={DF.FAX, DF.VOICE, DF.ADDR},
types={DF.ADDR: "loc"},
)
registry.send(create)
return self._make_contact_in_registry(contact=contact)
else:
raise e
@ -461,6 +566,7 @@ class Domain(TimeStampedModel, DomainHelper):
"""Contact registry for info about a domain."""
try:
# get info from registry
logger.info("_fetch_cache()-> fetching from cache, should create domain")
data = self._get_or_create_domain()
# extract properties from response
# (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)
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
if (
@ -497,6 +604,8 @@ class Domain(TimeStampedModel, DomainHelper):
# extract properties from response
# (Ellipsis is used to mean "null")
logger.info("_fetch_cache()->contacts are ")
logger.info(data)
contact = {
"id": id,
"auth_info": getattr(data, "auth_info", ...),
@ -514,6 +623,7 @@ class Domain(TimeStampedModel, DomainHelper):
cleaned["contacts"].append(
{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
if (
@ -522,6 +632,7 @@ class Domain(TimeStampedModel, DomainHelper):
and isinstance(cleaned["_hosts"], list)
and len(cleaned["_hosts"])
):
##TODO- add elif in cache set it to be the old cache value, no point in removing
cleaned["hosts"] = []
for name in cleaned["_hosts"]:
# we do not use _get_or_create_* because we expect the object we

View file

@ -3,6 +3,8 @@
{% block field_sets %}
<div class="submit-row">
<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>
{{ block.super }}
{% endblock %}

View file

@ -10,14 +10,14 @@ import datetime
from registrar.models import Domain # add in DomainApplication, User,
from unittest import skip
from epplibwrapper import commands
from epplibwrapper import commands,common
from registrar.models.domain_application import DomainApplication
from registrar.models.domain_information import DomainInformation
from registrar.models.draft_domain import DraftDomain
from registrar.models.public_contact import PublicContact
from registrar.models.user import User
class TestDomainCache(TestCase):
class MockEppLib(TestCase):
class fakedEppObject(object):
""""""
@ -33,6 +33,12 @@ class TestDomainCache(TestCase):
contacts=["123"],
hosts=["fake.host.com"],
)
infoDomainNoContact= fakedEppObject(
"security",
cr_date=datetime.datetime(2023, 5, 25, 19, 45, 35),
contacts=[],
hosts=["fake.host.com"],
)
mockDataInfoContact = fakedEppObject(
"anotherPw", cr_date=datetime.datetime(2023, 7, 25, 19, 45, 35)
)
@ -43,19 +49,33 @@ class TestDomainCache(TestCase):
def mockSend(self, _request, cleaned):
""""""
if isinstance(_request, commands.InfoDomain):
if getattr(_request,"name",None)=="security.gov":
return MagicMock(res_data=[self.infoDomainNoContact])
return MagicMock(res_data=[self.mockDataInfoDomain])
elif isinstance(_request, commands.InfoContact):
return MagicMock(res_data=[self.mockDataInfoContact])
return MagicMock(res_data=[self.mockDataInfoHosts])
def setUp(self):
"""mock epp send function as this will fail locally"""
self.patcher = patch("registrar.models.domain.registry.send")
self.mockedSendFunction = self.patcher.start()
self.mockSendPatch = patch("registrar.models.domain.registry.send")
self.mockedSendFunction = self.mockSendPatch.start()
self.mockedSendFunction.side_effect = self.mockSend
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):
"""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
domain._get_property("hosts")
self.assertEqual(domain._cache["hosts"], [expectedHostsDict])
##IS THERE AN ERROR HERE???,
class TestDomainCreation(TestCase):
"""Rule: An approved domain application must result in a domain"""
def setUp(self):
"""
Background:
Given that a valid domain application exists
"""
# def setUp(self):
# """
# Background:
# Given that a valid domain application exists
# """
@skip("not implemented yet")
def test_approved_application_creates_domain_locally(self):
"""
Scenario: Analyst approves a domain application
@ -139,6 +158,8 @@ class TestDomainCreation(TestCase):
Then a Domain exists in the database with the same `name`
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")
user, _ = User.objects.get_or_create()
application = DomainApplication.objects.create(
@ -146,12 +167,12 @@ class TestDomainCreation(TestCase):
)
# skip using the submit method
application.status = DomainApplication.SUBMITTED
#trnasistion to approve state
#transition to approve state
application.approve()
# should be an information present for this domain
# should hav information present for this domain
domain = Domain.objects.get(name="igorville.gov")
self.assertTrue(domain)
mocked_domain_creation.assert_not_called()
@skip("not implemented yet")
@ -191,8 +212,11 @@ class TestDomainCreation(TestCase):
domain.save()
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"""
def setUp(self):
@ -201,9 +225,33 @@ class TestRegistrantContacts(TestCase):
Given the registrant is logged in
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):
"""
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
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")
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
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")
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
# sent like this, and then implementing appropriate mocks for any errors the
# 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
@skip("not implemented yet")
@ -428,7 +525,7 @@ class TestRegistrantDNSSEC(TestCase):
def test_user_adds_dns_data(self):
"""
Scenario: Registrant adds DNS data
...
"""
raise
@ -436,7 +533,7 @@ class TestRegistrantDNSSEC(TestCase):
def test_dnssec_is_idempotent(self):
"""
Scenario: Registrant adds DNS data twice, due to a UI glitch
...
"""
# implementation note: this requires seeing what happens when these are actually
# 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.save()
##update security email here
#call the setter
messages.success(
self.request, "The security email for this domain have been updated."
)