Repository: cisagov/manage.get.gov

Issue:
Pull all security emails for existing domains and update their disclose
### Issue description

Security emails are:
 1. not showing as disclosed on whois 
 2. some are missing from our database because the users have not gone to that page and their domains weren't created by us 
 3. all security domains for transition domains were defaulted to a disclose of false and now need to be switched to true. 
 4. newly created domains were also defaulted to having their disclose off and we need to make sure they get their disclose turned on as well.

To fix this a script that goes through and just calls _update_epp_contact on the domain objects for all domains is all that is needed. This should be able to run on stable

### Acceptance criteria

- [x] script that loops through all domains  and calls calls the .security_contact getter to trigger us saving their contact information
- [x] this script also then calls _update_epp_contact() for those domains that we can retrieve a security contact. It should just pass in the original security contact as a parameter and no change other than the disclose being flipped to true when appropriate should occur
- [ ] unit test to ensure only disclose gets updated 
- [x] other tests where deemed appropriate 

### Additional context

_No response_

### Links to other issues

blocked by #1497 


Discussion:

Resolving patch:
diff --git a/src/registrar/management/commands/disclose_security_emails.py b/src/registrar/management/commands/disclose_security_emails.py
new file mode 100644
--- /dev/null
+++ b/src/registrar/management/commands/disclose_security_emails.py
@@ -0,0 +1,69 @@
+""""
+Converts all ready and DNS needed domains with a non-default public contact
+to disclose their public contact. Created for Issue#1535 to resolve
+ disclose issue of domains with missing security emails.
+"""
+
+import logging
+import copy
+
+from django.core.management import BaseCommand
+from registrar.models import Domain
+
+logger = logging.getLogger(__name__)
+
+
+class Command(BaseCommand):
+    help = "Disclose all nondefault domain security emails."
+
+    def __init__(self):
+        """Sets global variables for code tidiness"""
+        super().__init__()
+        # domains with errors, which are not successfully updated to disclose
+        self.domains_with_errors: list[str] = []
+        # domains that are successfully disclosed
+        self.disclosed_domain_contacts_count = 0
+        # domains that skip disclose due to having contact registrar@dotgov.gov
+        self.skipped_domain_contacts_count = 0
+
+    def handle(self, **options):
+        """
+        Converts all ready and DNS needed domains with a non-default public contact
+        to disclose their public contact.
+        """
+        logger.info("Updating security emails to public")
+
+        # Initializes domains that need to be disclosed
+
+        statuses = [Domain.State.READY, Domain.State.DNS_NEEDED]
+        domains = Domain.objects.filter(state__in=statuses)
+
+        logger.info(f"Found {len(domains)} domains with status Ready or DNS Needed.")
+
+        # Update EPP contact for domains with a security contact
+        for domain in domains:
+            try:
+                contact = domain.security_contact  # noqa on these items as we only want to call security_contact
+                logger.info(f"Domain {domain.name} security contact: {domain.security_contact.email}")
+                if domain.security_contact.email != "registrar@dotgov.gov":
+                    domain._update_epp_contact(contact=domain.security_contact)
+                    self.disclosed_domain_contacts_count += 1
+                else:
+                    logger.info(
+                        f"Skipping disclose for {domain.name} security contact {domain.security_contact.email}."
+                    )
+                    self.skipped_domain_contacts_count += 1
+            except Exception as err:
+                # error condition if domain not in database
+                self.domains_with_errors.append(copy.deepcopy(domain.name))
+                logger.error(f"error retrieving domain {domain.name} contact {domain.security_contact}: {err}")
+
+        # Inform user how many contacts were disclosed, skipped, and errored
+        logger.info(f"Updated {self.disclosed_domain_contacts_count} contacts to disclosed.")
+        logger.info(
+            f"Skipped disclosing {self.skipped_domain_contacts_count} contacts with security email "
+            f"registrar@dotgov.gov."
+        )
+        logger.info(
+            f"Error disclosing the following {len(self.domains_with_errors)} contacts: {self.domains_with_errors}"
+        )
diff --git a/src/registrar/models/domain.py b/src/registrar/models/domain.py
--- a/src/registrar/models/domain.py
+++ b/src/registrar/models/domain.py
@@ -1396,11 +1396,13 @@ def dns_needed(self):
     def _disclose_fields(self, contact: PublicContact):
         """creates a disclose object that can be added to a contact Create using
         .disclose= <this function> on the command before sending.
-        if item is security email then make sure email is visable"""
+        if item is security email then make sure email is visible"""
         is_security = contact.contact_type == contact.ContactTypeChoices.SECURITY
         DF = epp.DiscloseField
         fields = {DF.EMAIL}
         disclose = is_security and contact.email != PublicContact.get_default_security().email
+        # Delete after testing on other devices
+        logger.info("Updated domain contact %s to disclose: %s", contact.email, disclose)
         # Will only disclose DF.EMAIL if its not the default
         return epp.Disclose(
             flag=disclose,


Test patch:
diff --git a/src/registrar/tests/test_management_scripts.py b/src/registrar/tests/test_management_scripts.py
--- a/src/registrar/tests/test_management_scripts.py
+++ b/src/registrar/tests/test_management_scripts.py
@@ -11,9 +11,11 @@
     DomainInformation,
     UserDomainRole,
 )
+from registrar.models.public_contact import PublicContact
 
 from django.core.management import call_command
-from unittest.mock import patch
+from unittest.mock import patch, call
+from epplibwrapper import commands, common
 
 from .common import MockEppLib
 
@@ -441,3 +443,57 @@ def test_extends_expiration_date_idempotent(self):
 
         # Explicitly test the expiration date - should be the same
         self.assertEqual(desired_domain.expiration_date, datetime.date(2024, 11, 15))
+
+
+class TestDiscloseEmails(MockEppLib):
+    def setUp(self):
+        super().setUp()
+
+    def tearDown(self):
+        super().tearDown()
+        PublicContact.objects.all().delete()
+        Domain.objects.all().delete()
+
+    def run_disclose_security_emails(self):
+        """
+        This method executes the disclose_security_emails command.
+
+        The 'call_command' function from Django's management framework is then used to
+        execute the disclose_security_emails command.
+        """
+        with patch(
+            "registrar.management.commands.utility.terminal_helper.TerminalHelper.query_yes_no_exit",  # noqa
+            return_value=True,
+        ):
+            call_command("disclose_security_emails")
+
+    def test_disclose_security_emails(self):
+        """
+        Tests that command disclose_security_emails runs successfully with
+        appropriate EPP calll to UpdateContact.
+        """
+        domain, _ = Domain.objects.get_or_create(name="testdisclose.gov", state=Domain.State.READY)
+        expectedSecContact = PublicContact.get_default_security()
+        expectedSecContact.domain = domain
+        expectedSecContact.email = "123@mail.gov"
+        # set domain security email to 123@mail.gov instead of default email
+        domain.security_contact = expectedSecContact
+        self.run_disclose_security_emails()
+
+        # running disclose_security_emails sends EPP call UpdateContact with disclose
+        self.mockedSendFunction.assert_has_calls(
+            [
+                call(
+                    commands.UpdateContact(
+                        id=domain.security_contact.registry_id,
+                        postal_info=domain._make_epp_contact_postal_info(contact=domain.security_contact),
+                        email=domain.security_contact.email,
+                        voice=domain.security_contact.voice,
+                        fax=domain.security_contact.fax,
+                        auth_info=common.ContactAuthInfo(pw="2fooBAR123fooBaz"),
+                        disclose=domain._disclose_fields(contact=domain.security_contact),
+                    ),
+                    cleaned=True,
+                )
+            ]
+        )