diff --git a/src/registrar/migrations/0102_domain_dsdata_last_change.py b/src/registrar/migrations/0102_domain_dsdata_last_change.py new file mode 100644 index 0000000000..6ea631b6a5 --- /dev/null +++ b/src/registrar/migrations/0102_domain_dsdata_last_change.py @@ -0,0 +1,18 @@ +# Generated by Django 4.2.10 on 2024-06-14 19:26 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("registrar", "0101_domaininformation_cisa_representative_first_name_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="domain", + name="dsdata_last_change", + field=models.TextField(blank=True, help_text="Record of the last change event for ds data", null=True), + ), + ] diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py index 26dcb89a78..767227499b 100644 --- a/src/registrar/models/domain.py +++ b/src/registrar/models/domain.py @@ -40,6 +40,8 @@ from .public_contact import PublicContact +from .user_domain_role import UserDomainRole + logger = logging.getLogger(__name__) @@ -672,11 +674,29 @@ def dnssecdata(self, _dnssecdata: Optional[extensions.DNSSECExtension]): remRequest = commands.UpdateDomain(name=self.name) remExtension = commands.UpdateDomainDNSSECExtension(**remParams) remRequest.add_extension(remExtension) + dsdata_change_log = "" + + # Get the user's email + user_domain_role = UserDomainRole.objects.filter(domain=self).first() + user_email = user_domain_role.user.email if user_domain_role else "unknown user" + try: - if "dsData" in _addDnssecdata and _addDnssecdata["dsData"] is not None: + added_record = "dsData" in _addDnssecdata and _addDnssecdata["dsData"] is not None + deleted_record = "dsData" in _remDnssecdata and _remDnssecdata["dsData"] is not None + + if added_record: registry.send(addRequest, cleaned=True) - if "dsData" in _remDnssecdata and _remDnssecdata["dsData"] is not None: + dsdata_change_log = f"{user_email} added a DS data record" + if deleted_record: registry.send(remRequest, cleaned=True) + if dsdata_change_log != "": # if they add and remove a record at same time + dsdata_change_log = f"{user_email} added and deleted a DS data record" + else: + dsdata_change_log = f"{user_email} deleted a DS data record" + if dsdata_change_log != "": + self.dsdata_last_change = dsdata_change_log + self.save() # audit log will now record this as a change + except RegistryError as e: logger.error("Error updating DNSSEC, code was %s error was %s" % (e.code, e)) raise e @@ -1057,6 +1077,12 @@ def __str__(self) -> str: verbose_name="first ready on", ) + dsdata_last_change = TextField( + null=True, + blank=True, + help_text="Record of the last change event for ds data", + ) + def isActive(self): return self.state == Domain.State.CREATED diff --git a/src/registrar/tests/test_models_domain.py b/src/registrar/tests/test_models_domain.py index abad6f57e7..bbd1e3f548 100644 --- a/src/registrar/tests/test_models_domain.py +++ b/src/registrar/tests/test_models_domain.py @@ -1901,12 +1901,8 @@ def test_user_adds_dnssec_data(self): 3 - setter adds the UpdateDNSSECExtension extension to the command 4 - setter causes the getter to call info domain on next get from cache 5 - getter properly parses dnssecdata from InfoDomain response and sets to cache - """ - # need to use a separate patcher and side_effect for this test, as - # response from InfoDomain must be different for different iterations - # of the same command def side_effect(_request, cleaned): if isinstance(_request, commands.InfoDomain): if mocked_send.call_count == 1: @@ -1924,17 +1920,30 @@ def side_effect(_request, cleaned): mocked_send = patcher.start() mocked_send.side_effect = side_effect domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov") + + # Check initial dsdata_last_change value (should be None) + initial_change = domain.dsdata_last_change + + # Adding dnssec data domain.dnssecdata = self.dnssecExtensionWithDsData - # get the DNS SEC extension added to the UpdateDomain command and + + # Check dsdata_last_change is updated after adding data + domain = Domain.objects.get(name="dnssec-dsdata.gov") + self.assertIsNotNone(domain.dsdata_last_change) + + self.assertNotEqual(domain.dsdata_last_change, initial_change) + + # Get the DNS SEC extension added to the UpdateDomain command and # verify that it is properly sent # args[0] is the _request sent to registry args, _ = mocked_send.call_args - # assert that the extension on the update matches + # Assert that the extension on the update matches self.assertEquals( args[0].extensions[0], self.createUpdateExtension(self.dnssecExtensionWithDsData), ) - # test that the dnssecdata getter is functioning properly + + # Test that the dnssecdata getter is functioning properly dnssecdata_get = domain.dnssecdata mocked_send.assert_has_calls( [ @@ -2129,13 +2138,9 @@ def test_user_removes_dnssec_data(self): 2 - first setter calls UpdateDomain command 3 - second setter calls InfoDomain command again 3 - setter then calls UpdateDomain command - 4 - setter adds the UpdateDNSSECExtension extension to the command with rem - + 4 - setter adds the UpdateDNSSExtension extension to the command with rem """ - # need to use a separate patcher and side_effect for this test, as - # response from InfoDomain must be different for different iterations - # of the same command def side_effect(_request, cleaned): if isinstance(_request, commands.InfoDomain): if mocked_send.call_count == 1: @@ -2153,10 +2158,25 @@ def side_effect(_request, cleaned): mocked_send = patcher.start() mocked_send.side_effect = side_effect domain, _ = Domain.objects.get_or_create(name="dnssec-dsdata.gov") - # dnssecdata_get_initial = domain.dnssecdata # call to force initial mock - # domain._invalidate_cache() + + # Initial setting of dnssec data domain.dnssecdata = self.dnssecExtensionWithDsData + + # Check dsdata_last_change is updated + domain = Domain.objects.get(name="dnssec-dsdata.gov") + self.assertIsNotNone(domain.dsdata_last_change) + + initial_change = domain.dsdata_last_change + + # Remove dnssec data domain.dnssecdata = self.dnssecExtensionRemovingDsData + + # Check that dsdata_last_change is updated again + domain = Domain.objects.get(name="dnssec-dsdata.gov") + self.assertIsNotNone(domain.dsdata_last_change) + + self.assertNotEqual(domain.dsdata_last_change, initial_change) + # get the DNS SEC extension added to the UpdateDomain command and # verify that it is properly sent # args[0] is the _request sent to registry