diff --git a/docs/developer/README.md b/docs/developer/README.md index 358df649c..8e9dbcd40 100644 --- a/docs/developer/README.md +++ b/docs/developer/README.md @@ -97,6 +97,7 @@ While on production (the sandbox referred to as `stable`), an existing analyst o "username": "", "first_name": "", "last_name": "", + "email": "", }, ... ] @@ -121,6 +122,7 @@ Analysts are a variant of the admin role with limited permissions. The process f "username": "", "first_name": "", "last_name": "", + "email": "", }, ... ] @@ -131,6 +133,20 @@ Analysts are a variant of the admin role with limited permissions. The process f Do note that if you wish to have both an analyst and admin account, append `-Analyst` to your first and last name, or use a completely different first/last name to avoid confusion. Example: `Bob-Analyst` +## Adding an email address to the email whitelist (sandboxes only) +On all non-production environments, we use an email whitelist table (called `AllowEmail`). This whitelist is not case sensitive, and it provides an inclusion for +1 emails (like example.person+1@igorville.gov). The content after the `+` can be any _digit_. The whitelist checks for the "base" email (example.person) so even if you only have the +1 email defined, an email will still be sent assuming that it follows those conventions. + +To add yourself to this, you can go about it in three ways. + +Permanent (all sandboxes): +1. In src/registrar/fixtures_users.py, add the "email" field to your user in either the ADMIN or STAFF table. +2. In src/registrar/fixtures_users.py, add the desired email address to the `ADDITIONAL_ALLOWED_EMAILS` list. This route is suggested for product. + +Sandbox specific (wiped when the db is reset): +3. Create a new record on the `AllowEmail` table with your email address. This can be done through django admin. + +More detailed instructions regarding #3 can be found [here](https://docs.google.com/document/d/1ebIz4PcUuoiT7LlVy83EAyHAk_nWPEc99neMp4QjzDs). + ## Adding to CODEOWNERS (optional) The CODEOWNERS file sets the tagged individuals as default reviewers on any Pull Request that changes files that they are marked as owners of. diff --git a/src/registrar/fixtures_users.py b/src/registrar/fixtures_users.py index 8476c72eb..939a7b3ac 100644 --- a/src/registrar/fixtures_users.py +++ b/src/registrar/fixtures_users.py @@ -245,7 +245,7 @@ class UserFixture: # Additional emails to add to the AllowedEmail whitelist. # The format should be as follows: ["email@igorville.gov", "email2@igorville.gov"] - ADDITIONAL_ALLOWED_EMAILS = ["zander.adkinson@ecstech.com"] + ADDITIONAL_ALLOWED_EMAILS = [] def load_users(cls, users, group_name, are_superusers=False): logger.info(f"Going to load {len(users)} users in group {group_name}") diff --git a/src/registrar/tests/test_emails.py b/src/registrar/tests/test_emails.py index a98c16604..e699d9b75 100644 --- a/src/registrar/tests/test_emails.py +++ b/src/registrar/tests/test_emails.py @@ -2,7 +2,7 @@ from unittest.mock import MagicMock -from django.test import TestCase +from django.test import TestCase, override_settings from waffle.testutils import override_flag from registrar.utility import email from registrar.utility.email import send_templated_email @@ -259,3 +259,59 @@ class TestEmails(TestCase): self.assertIn("Content-Transfer-Encoding: base64", call_args["RawMessage"]["Data"]) self.assertIn("Content-Disposition: attachment;", call_args["RawMessage"]["Data"]) self.assertNotIn("Attachment file content", call_args["RawMessage"]["Data"]) + + +class TestAllowedEmail(TestCase): + """Tests our allowed email whitelist""" + + def setUp(self): + self.mock_client_class = MagicMock() + self.mock_client = self.mock_client_class.return_value + self.email = "mayor@igorville.gov" + self.email_2 = "cake@igorville.gov" + self.plus_email = "mayor+1@igorville.gov" + self.invalid_plus_email = "1+mayor@igorville.gov" + + def tearDown(self): + super().tearDown() + AllowedEmail.objects.all().delete() + + @boto3_mocking.patching + @override_settings(IS_PRODUCTION=True) + @less_console_noise_decorator + def test_email_whitelist_disabled_in_production(self): + """Tests if the whitelist is disabled in production""" + + # Ensure that the given email isn't in the whitelist + is_in_whitelist = AllowedEmail.objects.filter(email=self.email).exists() + self.assertFalse(is_in_whitelist) + + # The submit should work as normal + domain_request = completed_domain_request(has_anything_else=False) + with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): + domain_request.submit() + _, kwargs = self.mock_client.send_email.call_args + body = kwargs["Content"]["Simple"]["Body"]["Text"]["Data"] + self.assertNotIn("Anything else", body) + # spacing should be right between adjacent elements + self.assertRegex(body, r"5557\n\n----") + + @boto3_mocking.patching + @override_settings(IS_PRODUCTION=False) + @less_console_noise_decorator + def test_email_whitelist(self): + """Tests the email whitelist is enabled elsewhere""" + with boto3_mocking.clients.handler_for("sesv2", self.mock_client_class): + expected_message = "Could not send email. " + "The email 'doesnotexist@igorville.com' does not exist within the whitelist." + with self.assertRaisesRegex(email.EmailSendingError, expected_message): + send_templated_email( + "test content", + "test subject", + "doesnotexist@igorville.com", + context={"domain_request": self}, + bcc_address=None, + ) + + # Assert that an email wasn't sent + self.assertFalse(self.mock_client.send_email.called)