mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-07-27 04:58:42 +02:00
Merge branch 'main' into meoward/3549-dependabot-updates
This commit is contained in:
commit
e5c8c342fe
14 changed files with 447 additions and 77 deletions
|
@ -880,6 +880,7 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
which inturn call this function)
|
which inturn call this function)
|
||||||
Will throw error if contact type is not the same as expectType
|
Will throw error if contact type is not the same as expectType
|
||||||
Raises ValueError if expected type doesn't match the contact type"""
|
Raises ValueError if expected type doesn't match the contact type"""
|
||||||
|
|
||||||
if expectedType != contact.contact_type:
|
if expectedType != contact.contact_type:
|
||||||
raise ValueError("Cannot set a contact with a different contact type, expected type was %s" % expectedType)
|
raise ValueError("Cannot set a contact with a different contact type, expected type was %s" % expectedType)
|
||||||
|
|
||||||
|
@ -892,7 +893,6 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
duplicate_contacts = PublicContact.objects.exclude(registry_id=contact.registry_id).filter(
|
duplicate_contacts = PublicContact.objects.exclude(registry_id=contact.registry_id).filter(
|
||||||
domain=self, contact_type=contact.contact_type
|
domain=self, contact_type=contact.contact_type
|
||||||
)
|
)
|
||||||
|
|
||||||
# if no record exists with this contact type
|
# if no record exists with this contact type
|
||||||
# make contact in registry, duplicate and errors handled there
|
# make contact in registry, duplicate and errors handled there
|
||||||
errorCode = self._make_contact_in_registry(contact)
|
errorCode = self._make_contact_in_registry(contact)
|
||||||
|
@ -971,6 +971,24 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
logger.info("making technical contact")
|
logger.info("making technical contact")
|
||||||
self._set_singleton_contact(contact, expectedType=contact.ContactTypeChoices.TECHNICAL)
|
self._set_singleton_contact(contact, expectedType=contact.ContactTypeChoices.TECHNICAL)
|
||||||
|
|
||||||
|
def print_contact_info_epp(self, contact: PublicContact):
|
||||||
|
"""Prints registry data for this PublicContact for easier debugging"""
|
||||||
|
results = self._request_contact_info(contact, get_result_as_dict=True)
|
||||||
|
logger.info("---------------------")
|
||||||
|
logger.info(f"EPP info for {contact.contact_type}:")
|
||||||
|
logger.info("---------------------")
|
||||||
|
for key, value in results.items():
|
||||||
|
logger.info(f"{key}: {value}")
|
||||||
|
|
||||||
|
def print_all_domain_contact_info_epp(self):
|
||||||
|
"""Prints registry data for this domains security, registrant, technical, and administrative contacts."""
|
||||||
|
logger.info(f"Contact info for {self}:")
|
||||||
|
logger.info("=====================")
|
||||||
|
contacts = [self.security_contact, self.registrant_contact, self.technical_contact, self.administrative_contact]
|
||||||
|
for contact in contacts:
|
||||||
|
if contact:
|
||||||
|
self.print_contact_info_epp(contact)
|
||||||
|
|
||||||
def is_active(self) -> bool:
|
def is_active(self) -> bool:
|
||||||
"""Currently just returns if the state is created,
|
"""Currently just returns if the state is created,
|
||||||
because then it should be live, theoretically.
|
because then it should be live, theoretically.
|
||||||
|
@ -1351,10 +1369,14 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
)
|
)
|
||||||
return street_dict
|
return street_dict
|
||||||
|
|
||||||
def _request_contact_info(self, contact: PublicContact):
|
def _request_contact_info(self, contact: PublicContact, get_result_as_dict=False):
|
||||||
|
"""Grabs the resultant contact information in epp for this public contact
|
||||||
|
by using the InfoContact command.
|
||||||
|
Returns a commands.InfoContactResultData object, or a dict if get_result_as_dict is True."""
|
||||||
try:
|
try:
|
||||||
req = commands.InfoContact(id=contact.registry_id)
|
req = commands.InfoContact(id=contact.registry_id)
|
||||||
return registry.send(req, cleaned=True).res_data[0]
|
result = registry.send(req, cleaned=True).res_data[0]
|
||||||
|
return result if not get_result_as_dict else vars(result)
|
||||||
except RegistryError as error:
|
except RegistryError as error:
|
||||||
logger.error(
|
logger.error(
|
||||||
"Registry threw error for contact id %s contact type is %s, error code is\n %s full error is %s", # noqa
|
"Registry threw error for contact id %s contact type is %s, error code is\n %s full error is %s", # noqa
|
||||||
|
@ -1674,22 +1696,26 @@ class Domain(TimeStampedModel, DomainHelper):
|
||||||
return help_text
|
return help_text
|
||||||
|
|
||||||
def _disclose_fields(self, contact: PublicContact):
|
def _disclose_fields(self, contact: PublicContact):
|
||||||
"""creates a disclose object that can be added to a contact Create using
|
"""creates a disclose object that can be added to a contact Create or Update using
|
||||||
.disclose= <this function> on the command before sending.
|
.disclose= <this function> on the command before sending.
|
||||||
if item is security email then make sure email is visible"""
|
if item is security email then make sure email is visible"""
|
||||||
is_security = contact.contact_type == contact.ContactTypeChoices.SECURITY
|
# You can find each enum here:
|
||||||
|
# https://github.com/cisagov/epplib/blob/master/epplib/models/common.py#L32
|
||||||
DF = epp.DiscloseField
|
DF = epp.DiscloseField
|
||||||
fields = {DF.EMAIL}
|
all_disclose_fields = {field for field in DF}
|
||||||
|
disclose_args = {"fields": all_disclose_fields, "flag": False, "types": {DF.ADDR: "loc"}}
|
||||||
|
|
||||||
hidden_security_emails = [DefaultEmail.PUBLIC_CONTACT_DEFAULT.value, DefaultEmail.LEGACY_DEFAULT.value]
|
fields_to_remove = {DF.NOTIFY_EMAIL, DF.VAT, DF.IDENT}
|
||||||
disclose = is_security and contact.email not in hidden_security_emails
|
if contact.contact_type == contact.ContactTypeChoices.SECURITY:
|
||||||
# Delete after testing on other devices
|
if contact.email not in DefaultEmail.get_all_emails():
|
||||||
logger.info("Updated domain contact %s to disclose: %s", contact.email, disclose)
|
fields_to_remove.add(DF.EMAIL)
|
||||||
# Will only disclose DF.EMAIL if its not the default
|
elif contact.contact_type == contact.ContactTypeChoices.ADMINISTRATIVE:
|
||||||
return epp.Disclose(
|
fields_to_remove.update({DF.EMAIL, DF.VOICE, DF.ADDR})
|
||||||
flag=disclose,
|
|
||||||
fields=fields,
|
disclose_args["fields"].difference_update(fields_to_remove) # type: ignore
|
||||||
)
|
|
||||||
|
logger.debug("Updated domain contact %s to disclose: %s", contact.email, disclose_args.get("flag"))
|
||||||
|
return epp.Disclose(**disclose_args) # type: ignore
|
||||||
|
|
||||||
def _make_epp_contact_postal_info(self, contact: PublicContact): # type: ignore
|
def _make_epp_contact_postal_info(self, contact: PublicContact): # type: ignore
|
||||||
return epp.PostalInfo( # type: ignore
|
return epp.PostalInfo( # type: ignore
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from random import choices
|
from random import choices
|
||||||
from string import ascii_uppercase, ascii_lowercase, digits
|
from string import ascii_uppercase, ascii_lowercase, digits
|
||||||
|
@ -9,6 +10,9 @@ from registrar.utility.enums import DefaultEmail
|
||||||
from .utility.time_stamped_model import TimeStampedModel
|
from .utility.time_stamped_model import TimeStampedModel
|
||||||
|
|
||||||
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_id():
|
def get_id():
|
||||||
"""Generate a 16 character registry ID with a low probability of collision."""
|
"""Generate a 16 character registry ID with a low probability of collision."""
|
||||||
day = datetime.today().strftime("%A")[:2]
|
day = datetime.today().strftime("%A")[:2]
|
||||||
|
@ -92,15 +96,14 @@ class PublicContact(TimeStampedModel):
|
||||||
return cls(
|
return cls(
|
||||||
contact_type=PublicContact.ContactTypeChoices.REGISTRANT,
|
contact_type=PublicContact.ContactTypeChoices.REGISTRANT,
|
||||||
registry_id=get_id(),
|
registry_id=get_id(),
|
||||||
name="CSD/CB – Attn: Cameron Dixon",
|
name="CSD/CB – Attn: .gov TLD",
|
||||||
org="Cybersecurity and Infrastructure Security Agency",
|
org="Cybersecurity and Infrastructure Security Agency",
|
||||||
street1="CISA – NGR STOP 0645",
|
street1="1110 N. Glebe Rd",
|
||||||
street2="1110 N. Glebe Rd.",
|
|
||||||
city="Arlington",
|
city="Arlington",
|
||||||
sp="VA",
|
sp="VA",
|
||||||
pc="20598-0645",
|
pc="22201",
|
||||||
cc="US",
|
cc="US",
|
||||||
email=DefaultEmail.PUBLIC_CONTACT_DEFAULT.value,
|
email=DefaultEmail.PUBLIC_CONTACT_DEFAULT,
|
||||||
voice="+1.8882820870",
|
voice="+1.8882820870",
|
||||||
pw="thisisnotapassword",
|
pw="thisisnotapassword",
|
||||||
)
|
)
|
||||||
|
@ -110,14 +113,14 @@ class PublicContact(TimeStampedModel):
|
||||||
return cls(
|
return cls(
|
||||||
contact_type=PublicContact.ContactTypeChoices.ADMINISTRATIVE,
|
contact_type=PublicContact.ContactTypeChoices.ADMINISTRATIVE,
|
||||||
registry_id=get_id(),
|
registry_id=get_id(),
|
||||||
name="Program Manager",
|
name="CSD/CB – Attn: .gov TLD",
|
||||||
org="Cybersecurity and Infrastructure Security Agency",
|
org="Cybersecurity and Infrastructure Security Agency",
|
||||||
street1="4200 Wilson Blvd.",
|
street1="1110 N. Glebe Rd",
|
||||||
city="Arlington",
|
city="Arlington",
|
||||||
sp="VA",
|
sp="VA",
|
||||||
pc="22201",
|
pc="22201",
|
||||||
cc="US",
|
cc="US",
|
||||||
email=DefaultEmail.PUBLIC_CONTACT_DEFAULT.value,
|
email=DefaultEmail.PUBLIC_CONTACT_DEFAULT,
|
||||||
voice="+1.8882820870",
|
voice="+1.8882820870",
|
||||||
pw="thisisnotapassword",
|
pw="thisisnotapassword",
|
||||||
)
|
)
|
||||||
|
@ -127,14 +130,14 @@ class PublicContact(TimeStampedModel):
|
||||||
return cls(
|
return cls(
|
||||||
contact_type=PublicContact.ContactTypeChoices.TECHNICAL,
|
contact_type=PublicContact.ContactTypeChoices.TECHNICAL,
|
||||||
registry_id=get_id(),
|
registry_id=get_id(),
|
||||||
name="Registry Customer Service",
|
name="CSD/CB – Attn: .gov TLD",
|
||||||
org="Cybersecurity and Infrastructure Security Agency",
|
org="Cybersecurity and Infrastructure Security Agency",
|
||||||
street1="4200 Wilson Blvd.",
|
street1="1110 N. Glebe Rd",
|
||||||
city="Arlington",
|
city="Arlington",
|
||||||
sp="VA",
|
sp="VA",
|
||||||
pc="22201",
|
pc="22201",
|
||||||
cc="US",
|
cc="US",
|
||||||
email=DefaultEmail.PUBLIC_CONTACT_DEFAULT.value,
|
email=DefaultEmail.PUBLIC_CONTACT_DEFAULT,
|
||||||
voice="+1.8882820870",
|
voice="+1.8882820870",
|
||||||
pw="thisisnotapassword",
|
pw="thisisnotapassword",
|
||||||
)
|
)
|
||||||
|
@ -144,14 +147,14 @@ class PublicContact(TimeStampedModel):
|
||||||
return cls(
|
return cls(
|
||||||
contact_type=PublicContact.ContactTypeChoices.SECURITY,
|
contact_type=PublicContact.ContactTypeChoices.SECURITY,
|
||||||
registry_id=get_id(),
|
registry_id=get_id(),
|
||||||
name="Registry Customer Service",
|
name="CSD/CB – Attn: .gov TLD",
|
||||||
org="Cybersecurity and Infrastructure Security Agency",
|
org="Cybersecurity and Infrastructure Security Agency",
|
||||||
street1="4200 Wilson Blvd.",
|
street1="1110 N. Glebe Rd",
|
||||||
city="Arlington",
|
city="Arlington",
|
||||||
sp="VA",
|
sp="VA",
|
||||||
pc="22201",
|
pc="22201",
|
||||||
cc="US",
|
cc="US",
|
||||||
email=DefaultEmail.PUBLIC_CONTACT_DEFAULT.value,
|
email=DefaultEmail.PUBLIC_CONTACT_DEFAULT,
|
||||||
voice="+1.8882820870",
|
voice="+1.8882820870",
|
||||||
pw="thisisnotapassword",
|
pw="thisisnotapassword",
|
||||||
)
|
)
|
||||||
|
|
|
@ -40,7 +40,7 @@
|
||||||
<button
|
<button
|
||||||
type="submit"
|
type="submit"
|
||||||
class="usa-button"
|
class="usa-button"
|
||||||
>{% if form.security_email.value is None or form.security_email.value == "dotgov@cisa.dhs.gov" or form.security_email.value == "registrar@dotgov.gov"%}Add security email{% else %}Save{% endif %}</button>
|
>{% if form.security_email.value is None or form.security_email.value == "dotgov@cisa.dhs.gov" or form.security_email.value == "registrar@dotgov.gov" or form.security_email.value == "help@get.gov"%}Add security email{% else %}Save{% endif %}</button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
{% endblock %} {# domain_content #}
|
{% endblock %} {# domain_content #}
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
{% autoescape off %}{# In a text file, we don't want to have HTML entities escaped #}
|
||||||
|
Hi,{% if requested_user and requested_user.first_name %} {{ requested_user.first_name }}.{% endif %}
|
||||||
|
|
||||||
|
An update was made to your .gov organization.
|
||||||
|
|
||||||
|
ORGANIZATION: {{ portfolio }}
|
||||||
|
UPDATED BY: {{ editor.email }}
|
||||||
|
UPDATED ON: {{ date }}
|
||||||
|
INFORMATION UPDATED: {{ updated_info }}
|
||||||
|
|
||||||
|
You can view this update in the .gov registrar <https://manage.get.gov>.
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
WHY DID YOU RECEIVE THIS EMAIL?
|
||||||
|
You're listed as an admin for {{ portfolio }}, so you'll receive a
|
||||||
|
notification whenever changes are made to that .gov organization.
|
||||||
|
|
||||||
|
If you have questions or concerns, reach out to the person who made the change or reply
|
||||||
|
to this email.
|
||||||
|
|
||||||
|
THANK YOU
|
||||||
|
.Gov helps the public identify official, trusted information. Thank you for using a .gov
|
||||||
|
domain.
|
||||||
|
|
||||||
|
----------------------------------------------------------------
|
||||||
|
|
||||||
|
The .gov team
|
||||||
|
Contact us: <https://get.gov/contact/>
|
||||||
|
Learn about .gov <https://get.gov>
|
||||||
|
|
||||||
|
The .gov registry is a part of the Cybersecurity and Infrastructure Security Agency
|
||||||
|
(CISA) <https://cisa.gov/>
|
||||||
|
{% endautoescape %}
|
|
@ -0,0 +1 @@
|
||||||
|
An update was made to your .gov organization
|
|
@ -1444,10 +1444,8 @@ class MockEppLib(TestCase):
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|
||||||
mockDefaultTechnicalContact = InfoDomainWithContacts.dummyInfoContactResultData(
|
mockDefaultTechnicalContact = InfoDomainWithContacts.dummyInfoContactResultData("defaultTech", "help@get.gov")
|
||||||
"defaultTech", "dotgov@cisa.dhs.gov"
|
mockDefaultSecurityContact = InfoDomainWithContacts.dummyInfoContactResultData("defaultSec", "help@get.gov")
|
||||||
)
|
|
||||||
mockDefaultSecurityContact = InfoDomainWithContacts.dummyInfoContactResultData("defaultSec", "dotgov@cisa.dhs.gov")
|
|
||||||
mockSecurityContact = InfoDomainWithContacts.dummyInfoContactResultData("securityContact", "security@mail.gov")
|
mockSecurityContact = InfoDomainWithContacts.dummyInfoContactResultData("securityContact", "security@mail.gov")
|
||||||
mockTechnicalContact = InfoDomainWithContacts.dummyInfoContactResultData("technicalContact", "tech@mail.gov")
|
mockTechnicalContact = InfoDomainWithContacts.dummyInfoContactResultData("technicalContact", "tech@mail.gov")
|
||||||
mockAdministrativeContact = InfoDomainWithContacts.dummyInfoContactResultData("adminContact", "admin@mail.gov")
|
mockAdministrativeContact = InfoDomainWithContacts.dummyInfoContactResultData("adminContact", "admin@mail.gov")
|
||||||
|
@ -1962,14 +1960,23 @@ class MockEppLib(TestCase):
|
||||||
self.mockedSendFunction = self.mockSendPatch.start()
|
self.mockedSendFunction = self.mockSendPatch.start()
|
||||||
self.mockedSendFunction.side_effect = self.mockSend
|
self.mockedSendFunction.side_effect = self.mockSend
|
||||||
|
|
||||||
def _convertPublicContactToEpp(self, contact: PublicContact, disclose_email=False, createContact=True):
|
def _convertPublicContactToEpp(
|
||||||
|
self,
|
||||||
|
contact: PublicContact,
|
||||||
|
disclose=False,
|
||||||
|
createContact=True,
|
||||||
|
disclose_fields=None,
|
||||||
|
disclose_types=None,
|
||||||
|
):
|
||||||
DF = common.DiscloseField
|
DF = common.DiscloseField
|
||||||
fields = {DF.EMAIL}
|
if disclose_fields is None:
|
||||||
|
fields = {DF.NOTIFY_EMAIL, DF.VAT, DF.IDENT, DF.EMAIL}
|
||||||
|
disclose_fields = {field for field in DF} - fields
|
||||||
|
|
||||||
di = common.Disclose(
|
if disclose_types is None:
|
||||||
flag=disclose_email,
|
disclose_types = {DF.ADDR: "loc"}
|
||||||
fields=fields,
|
|
||||||
)
|
di = common.Disclose(flag=disclose, fields=disclose_fields, types=disclose_types)
|
||||||
|
|
||||||
# check docs here looks like we may have more than one address field but
|
# check docs here looks like we may have more than one address field but
|
||||||
addr = common.ContactAddr(
|
addr = common.ContactAddr(
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import unittest
|
import unittest
|
||||||
from unittest.mock import patch, MagicMock
|
from unittest.mock import patch, MagicMock, ANY
|
||||||
from datetime import date
|
from datetime import date
|
||||||
from registrar.models.domain import Domain
|
from registrar.models.domain import Domain
|
||||||
from registrar.models.portfolio import Portfolio
|
from registrar.models.portfolio import Portfolio
|
||||||
|
@ -21,6 +21,7 @@ from registrar.utility.email_invitations import (
|
||||||
send_portfolio_invitation_remove_email,
|
send_portfolio_invitation_remove_email,
|
||||||
send_portfolio_member_permission_remove_email,
|
send_portfolio_member_permission_remove_email,
|
||||||
send_portfolio_member_permission_update_email,
|
send_portfolio_member_permission_update_email,
|
||||||
|
send_portfolio_update_emails_to_portfolio_admins,
|
||||||
)
|
)
|
||||||
|
|
||||||
from api.tests.common import less_console_noise_decorator
|
from api.tests.common import less_console_noise_decorator
|
||||||
|
@ -1261,3 +1262,60 @@ class SendDomainManagerRemovalEmailsToManagersTests(unittest.TestCase):
|
||||||
|
|
||||||
self.assertTrue(result) # No emails sent, but also no failures
|
self.assertTrue(result) # No emails sent, but also no failures
|
||||||
mock_filter.assert_called_once_with(domain=self.domain)
|
mock_filter.assert_called_once_with(domain=self.domain)
|
||||||
|
|
||||||
|
|
||||||
|
class TestSendPortfolioOrganizationUpdateEmail(unittest.TestCase):
|
||||||
|
"""Unit tests for send_portfolio_update_emails_to_portfolio_admins function."""
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Set up test data."""
|
||||||
|
self.email = "removed.admin@example.com"
|
||||||
|
self.requestor_email = "requestor@example.com"
|
||||||
|
self.portfolio = MagicMock(spec=Portfolio, name="Portfolio")
|
||||||
|
self.portfolio.organization_name = "Test Organization"
|
||||||
|
|
||||||
|
# Mock portfolio admin users
|
||||||
|
self.admin_user1 = MagicMock(spec=User)
|
||||||
|
self.admin_user1.email = "admin1@example.com"
|
||||||
|
|
||||||
|
self.admin_user2 = MagicMock(spec=User)
|
||||||
|
self.admin_user2.email = "admin2@example.com"
|
||||||
|
|
||||||
|
self.portfolio_admin1 = MagicMock(spec=UserPortfolioPermission)
|
||||||
|
self.portfolio_admin1.user = self.admin_user1
|
||||||
|
self.portfolio_admin1.roles = [UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
|
||||||
|
|
||||||
|
self.portfolio_admin2 = MagicMock(spec=UserPortfolioPermission)
|
||||||
|
self.portfolio_admin2.user = self.admin_user2
|
||||||
|
self.portfolio_admin2.roles = [UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
|
||||||
|
|
||||||
|
@patch("registrar.utility.email_invitations.send_templated_email")
|
||||||
|
@patch("registrar.utility.email_invitations.UserPortfolioPermission.objects.filter")
|
||||||
|
def test_send_portfolio_update_emails_to_portfolio_admins(self, mock_filter, mock_send_templated_email):
|
||||||
|
"""Test send_portfolio_update_emails_to_portfolio_admins sends templated email."""
|
||||||
|
# Mock data
|
||||||
|
editor = self.admin_user1
|
||||||
|
updated_page = "Organization"
|
||||||
|
|
||||||
|
mock_filter.return_value = [self.portfolio_admin1, self.portfolio_admin2]
|
||||||
|
mock_send_templated_email.return_value = None # No exception means success
|
||||||
|
|
||||||
|
# Call function
|
||||||
|
result = send_portfolio_update_emails_to_portfolio_admins(editor, self.portfolio, updated_page)
|
||||||
|
|
||||||
|
mock_filter.assert_called_once_with(
|
||||||
|
portfolio=self.portfolio, roles__contains=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
|
||||||
|
)
|
||||||
|
mock_send_templated_email.assert_any_call(
|
||||||
|
"emails/portfolio_org_update_notification.txt",
|
||||||
|
"emails/portfolio_org_update_notification_subject.txt",
|
||||||
|
to_address=self.admin_user1.email,
|
||||||
|
context=ANY,
|
||||||
|
)
|
||||||
|
mock_send_templated_email.assert_any_call(
|
||||||
|
"emails/portfolio_org_update_notification.txt",
|
||||||
|
"emails/portfolio_org_update_notification_subject.txt",
|
||||||
|
to_address=self.admin_user2.email,
|
||||||
|
context=ANY,
|
||||||
|
)
|
||||||
|
self.assertTrue(result)
|
||||||
|
|
|
@ -722,6 +722,9 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
self.domain, _ = Domain.objects.get_or_create(name="security.gov")
|
self.domain, _ = Domain.objects.get_or_create(name="security.gov")
|
||||||
# Creates a domain with an associated contact
|
# Creates a domain with an associated contact
|
||||||
self.domain_contact, _ = Domain.objects.get_or_create(name="freeman.gov")
|
self.domain_contact, _ = Domain.objects.get_or_create(name="freeman.gov")
|
||||||
|
DF = common.DiscloseField
|
||||||
|
excluded_disclose_fields = {DF.NOTIFY_EMAIL, DF.VAT, DF.IDENT}
|
||||||
|
self.all_disclose_fields = {field for field in DF} - excluded_disclose_fields
|
||||||
|
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
super().tearDown()
|
super().tearDown()
|
||||||
|
@ -758,7 +761,9 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
contact_type=PublicContact.ContactTypeChoices.SECURITY,
|
contact_type=PublicContact.ContactTypeChoices.SECURITY,
|
||||||
).registry_id
|
).registry_id
|
||||||
expectedSecContact.registry_id = id
|
expectedSecContact.registry_id = id
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=False)
|
expectedCreateCommand = self._convertPublicContactToEpp(
|
||||||
|
expectedSecContact, disclose=False, disclose_fields=self.all_disclose_fields
|
||||||
|
)
|
||||||
expectedUpdateDomain = commands.UpdateDomain(
|
expectedUpdateDomain = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")],
|
add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")],
|
||||||
|
@ -788,7 +793,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
# self.domain.security_contact=expectedSecContact
|
# self.domain.security_contact=expectedSecContact
|
||||||
expectedSecContact.save()
|
expectedSecContact.save()
|
||||||
# no longer the default email it should be disclosed
|
# no longer the default email it should be disclosed
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=True)
|
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose=False)
|
||||||
expectedUpdateDomain = commands.UpdateDomain(
|
expectedUpdateDomain = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")],
|
add=[common.DomainContact(contact=expectedSecContact.registry_id, type="security")],
|
||||||
|
@ -813,7 +818,9 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
security_contact.registry_id = "fail"
|
security_contact.registry_id = "fail"
|
||||||
security_contact.save()
|
security_contact.save()
|
||||||
self.domain.security_contact = security_contact
|
self.domain.security_contact = security_contact
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=False)
|
expectedCreateCommand = self._convertPublicContactToEpp(
|
||||||
|
security_contact, disclose=False, disclose_fields=self.all_disclose_fields
|
||||||
|
)
|
||||||
expectedUpdateDomain = commands.UpdateDomain(
|
expectedUpdateDomain = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[common.DomainContact(contact=security_contact.registry_id, type="security")],
|
add=[common.DomainContact(contact=security_contact.registry_id, type="security")],
|
||||||
|
@ -846,7 +853,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
new_contact.registry_id = "fail"
|
new_contact.registry_id = "fail"
|
||||||
new_contact.email = ""
|
new_contact.email = ""
|
||||||
self.domain.security_contact = new_contact
|
self.domain.security_contact = new_contact
|
||||||
firstCreateContactCall = self._convertPublicContactToEpp(old_contact, disclose_email=True)
|
firstCreateContactCall = self._convertPublicContactToEpp(old_contact, disclose=False)
|
||||||
updateDomainAddCall = commands.UpdateDomain(
|
updateDomainAddCall = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[common.DomainContact(contact=old_contact.registry_id, type="security")],
|
add=[common.DomainContact(contact=old_contact.registry_id, type="security")],
|
||||||
|
@ -856,7 +863,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
PublicContact.get_default_security().email,
|
PublicContact.get_default_security().email,
|
||||||
)
|
)
|
||||||
# this one triggers the fail
|
# this one triggers the fail
|
||||||
secondCreateContact = self._convertPublicContactToEpp(new_contact, disclose_email=True)
|
secondCreateContact = self._convertPublicContactToEpp(new_contact, disclose=False)
|
||||||
updateDomainRemCall = commands.UpdateDomain(
|
updateDomainRemCall = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
rem=[common.DomainContact(contact=old_contact.registry_id, type="security")],
|
rem=[common.DomainContact(contact=old_contact.registry_id, type="security")],
|
||||||
|
@ -864,7 +871,9 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
defaultSecID = PublicContact.objects.filter(domain=self.domain).get().registry_id
|
defaultSecID = PublicContact.objects.filter(domain=self.domain).get().registry_id
|
||||||
default_security = PublicContact.get_default_security()
|
default_security = PublicContact.get_default_security()
|
||||||
default_security.registry_id = defaultSecID
|
default_security.registry_id = defaultSecID
|
||||||
createDefaultContact = self._convertPublicContactToEpp(default_security, disclose_email=False)
|
createDefaultContact = self._convertPublicContactToEpp(
|
||||||
|
default_security, disclose=False, disclose_fields=self.all_disclose_fields
|
||||||
|
)
|
||||||
updateDomainWDefault = commands.UpdateDomain(
|
updateDomainWDefault = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[common.DomainContact(contact=defaultSecID, type="security")],
|
add=[common.DomainContact(contact=defaultSecID, type="security")],
|
||||||
|
@ -892,15 +901,15 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
security_contact.email = "originalUserEmail@gmail.com"
|
security_contact.email = "originalUserEmail@gmail.com"
|
||||||
security_contact.registry_id = "fail"
|
security_contact.registry_id = "fail"
|
||||||
security_contact.save()
|
security_contact.save()
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=True)
|
expectedCreateCommand = self._convertPublicContactToEpp(security_contact, disclose=False)
|
||||||
expectedUpdateDomain = commands.UpdateDomain(
|
expectedUpdateDomain = commands.UpdateDomain(
|
||||||
name=self.domain.name,
|
name=self.domain.name,
|
||||||
add=[common.DomainContact(contact=security_contact.registry_id, type="security")],
|
add=[common.DomainContact(contact=security_contact.registry_id, type="security")],
|
||||||
)
|
)
|
||||||
security_contact.email = "changedEmail@email.com"
|
security_contact.email = "changedEmail@email.com"
|
||||||
security_contact.save()
|
security_contact.save()
|
||||||
expectedSecondCreateCommand = self._convertPublicContactToEpp(security_contact, disclose_email=True)
|
expectedSecondCreateCommand = self._convertPublicContactToEpp(security_contact, disclose=False)
|
||||||
updateContact = self._convertPublicContactToEpp(security_contact, disclose_email=True, createContact=False)
|
updateContact = self._convertPublicContactToEpp(security_contact, disclose=False, createContact=False)
|
||||||
expected_calls = [
|
expected_calls = [
|
||||||
call(expectedCreateCommand, cleaned=True),
|
call(expectedCreateCommand, cleaned=True),
|
||||||
call(expectedUpdateDomain, cleaned=True),
|
call(expectedUpdateDomain, cleaned=True),
|
||||||
|
@ -988,9 +997,23 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
for contact in contacts:
|
for contact in contacts:
|
||||||
expected_contact = contact[0]
|
expected_contact = contact[0]
|
||||||
actual_contact = contact[1]
|
actual_contact = contact[1]
|
||||||
is_security = expected_contact.contact_type == "security"
|
if expected_contact.contact_type == PublicContact.ContactTypeChoices.SECURITY:
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(expected_contact, disclose_email=is_security)
|
disclose_fields = self.all_disclose_fields - {"email"}
|
||||||
# Should only be disclosed if the type is security, as the email is valid
|
expectedCreateCommand = self._convertPublicContactToEpp(
|
||||||
|
expected_contact, disclose=False, disclose_fields=disclose_fields
|
||||||
|
)
|
||||||
|
elif expected_contact.contact_type == PublicContact.ContactTypeChoices.ADMINISTRATIVE:
|
||||||
|
disclose_fields = self.all_disclose_fields - {"email", "voice", "addr"}
|
||||||
|
expectedCreateCommand = self._convertPublicContactToEpp(
|
||||||
|
expected_contact,
|
||||||
|
disclose=False,
|
||||||
|
disclose_fields=disclose_fields,
|
||||||
|
disclose_types={"addr": "loc"},
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
expectedCreateCommand = self._convertPublicContactToEpp(
|
||||||
|
expected_contact, disclose=False, disclose_fields=self.all_disclose_fields
|
||||||
|
)
|
||||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||||
# The emails should match on both items
|
# The emails should match on both items
|
||||||
self.assertEqual(expected_contact.email, actual_contact.email)
|
self.assertEqual(expected_contact.email, actual_contact.email)
|
||||||
|
@ -999,23 +1022,24 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
with less_console_noise():
|
with less_console_noise():
|
||||||
domain, _ = Domain.objects.get_or_create(name="freeman.gov")
|
domain, _ = Domain.objects.get_or_create(name="freeman.gov")
|
||||||
dummy_contact = domain.get_default_security_contact()
|
dummy_contact = domain.get_default_security_contact()
|
||||||
test_disclose = self._convertPublicContactToEpp(dummy_contact, disclose_email=True).__dict__
|
test_disclose = self._convertPublicContactToEpp(dummy_contact, disclose=False).__dict__
|
||||||
test_not_disclose = self._convertPublicContactToEpp(dummy_contact, disclose_email=False).__dict__
|
test_not_disclose = self._convertPublicContactToEpp(dummy_contact, disclose=False).__dict__
|
||||||
# Separated for linter
|
# Separated for linter
|
||||||
disclose_email_field = {common.DiscloseField.EMAIL}
|
disclose_email_field = self.all_disclose_fields - {common.DiscloseField.EMAIL}
|
||||||
|
DF = common.DiscloseField
|
||||||
expected_disclose = {
|
expected_disclose = {
|
||||||
"auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"),
|
"auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"),
|
||||||
"disclose": common.Disclose(flag=True, fields=disclose_email_field, types=None),
|
"disclose": common.Disclose(flag=False, fields=disclose_email_field, types={DF.ADDR: "loc"}),
|
||||||
"email": "dotgov@cisa.dhs.gov",
|
"email": "help@get.gov",
|
||||||
"extensions": [],
|
"extensions": [],
|
||||||
"fax": None,
|
"fax": None,
|
||||||
"id": "ThIq2NcRIDN7PauO",
|
"id": "ThIq2NcRIDN7PauO",
|
||||||
"ident": None,
|
"ident": None,
|
||||||
"notify_email": None,
|
"notify_email": None,
|
||||||
"postal_info": common.PostalInfo(
|
"postal_info": common.PostalInfo(
|
||||||
name="Registry Customer Service",
|
name="CSD/CB – Attn: .gov TLD",
|
||||||
addr=common.ContactAddr(
|
addr=common.ContactAddr(
|
||||||
street=["4200 Wilson Blvd.", None, None],
|
street=["1110 N. Glebe Rd", None, None],
|
||||||
city="Arlington",
|
city="Arlington",
|
||||||
pc="22201",
|
pc="22201",
|
||||||
cc="US",
|
cc="US",
|
||||||
|
@ -1030,17 +1054,17 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
# Separated for linter
|
# Separated for linter
|
||||||
expected_not_disclose = {
|
expected_not_disclose = {
|
||||||
"auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"),
|
"auth_info": common.ContactAuthInfo(pw="2fooBAR123fooBaz"),
|
||||||
"disclose": common.Disclose(flag=False, fields=disclose_email_field, types=None),
|
"disclose": common.Disclose(flag=False, fields=disclose_email_field, types={DF.ADDR: "loc"}),
|
||||||
"email": "dotgov@cisa.dhs.gov",
|
"email": "help@get.gov",
|
||||||
"extensions": [],
|
"extensions": [],
|
||||||
"fax": None,
|
"fax": None,
|
||||||
"id": "ThrECENCHI76PGLh",
|
"id": "ThrECENCHI76PGLh",
|
||||||
"ident": None,
|
"ident": None,
|
||||||
"notify_email": None,
|
"notify_email": None,
|
||||||
"postal_info": common.PostalInfo(
|
"postal_info": common.PostalInfo(
|
||||||
name="Registry Customer Service",
|
name="CSD/CB – Attn: .gov TLD",
|
||||||
addr=common.ContactAddr(
|
addr=common.ContactAddr(
|
||||||
street=["4200 Wilson Blvd.", None, None],
|
street=["1110 N. Glebe Rd", None, None],
|
||||||
city="Arlington",
|
city="Arlington",
|
||||||
pc="22201",
|
pc="22201",
|
||||||
cc="US",
|
cc="US",
|
||||||
|
@ -1058,6 +1082,39 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
self.assertEqual(test_disclose, expected_disclose)
|
self.assertEqual(test_disclose, expected_disclose)
|
||||||
self.assertEqual(test_not_disclose, expected_not_disclose)
|
self.assertEqual(test_not_disclose, expected_not_disclose)
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_convert_public_contact_with_custom_fields(self):
|
||||||
|
"""Test converting a contact with custom disclosure fields."""
|
||||||
|
domain, _ = Domain.objects.get_or_create(name="freeman.gov")
|
||||||
|
dummy_contact = domain.get_default_administrative_contact()
|
||||||
|
DF = common.DiscloseField
|
||||||
|
|
||||||
|
# Create contact with multiple disclosure fields
|
||||||
|
result = self._convertPublicContactToEpp(
|
||||||
|
dummy_contact,
|
||||||
|
disclose=True,
|
||||||
|
disclose_fields={DF.EMAIL, DF.VOICE, DF.ADDR},
|
||||||
|
disclose_types={},
|
||||||
|
)
|
||||||
|
self.assertEqual(result.disclose.flag, True)
|
||||||
|
self.assertEqual(result.disclose.fields, {DF.EMAIL, DF.VOICE, DF.ADDR})
|
||||||
|
self.assertEqual(result.disclose.types, {})
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_convert_public_contact_with_empty_fields(self):
|
||||||
|
"""Test converting a contact with empty disclosure fields."""
|
||||||
|
domain, _ = Domain.objects.get_or_create(name="freeman.gov")
|
||||||
|
dummy_contact = domain.get_default_security_contact()
|
||||||
|
|
||||||
|
DF = common.DiscloseField
|
||||||
|
# Create contact with empty fields list
|
||||||
|
result = self._convertPublicContactToEpp(dummy_contact, disclose=True, disclose_fields={DF.EMAIL})
|
||||||
|
|
||||||
|
# Verify disclosure settings
|
||||||
|
self.assertEqual(result.disclose.flag, True)
|
||||||
|
self.assertEqual(result.disclose.fields, {DF.EMAIL})
|
||||||
|
self.assertEqual(result.disclose.types, {DF.ADDR: "loc"})
|
||||||
|
|
||||||
def test_not_disclosed_on_default_security_contact(self):
|
def test_not_disclosed_on_default_security_contact(self):
|
||||||
"""
|
"""
|
||||||
Scenario: Registrant creates a new domain with no security email
|
Scenario: Registrant creates a new domain with no security email
|
||||||
|
@ -1071,7 +1128,9 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
expectedSecContact.domain = domain
|
expectedSecContact.domain = domain
|
||||||
expectedSecContact.registry_id = "defaultSec"
|
expectedSecContact.registry_id = "defaultSec"
|
||||||
domain.security_contact = expectedSecContact
|
domain.security_contact = expectedSecContact
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=False)
|
expectedCreateCommand = self._convertPublicContactToEpp(
|
||||||
|
expectedSecContact, disclose=False, disclose_fields=self.all_disclose_fields
|
||||||
|
)
|
||||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||||
# Confirm that we are getting a default email
|
# Confirm that we are getting a default email
|
||||||
self.assertEqual(domain.security_contact.email, expectedSecContact.email)
|
self.assertEqual(domain.security_contact.email, expectedSecContact.email)
|
||||||
|
@ -1089,7 +1148,9 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
expectedTechContact.domain = domain
|
expectedTechContact.domain = domain
|
||||||
expectedTechContact.registry_id = "defaultTech"
|
expectedTechContact.registry_id = "defaultTech"
|
||||||
domain.technical_contact = expectedTechContact
|
domain.technical_contact = expectedTechContact
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(expectedTechContact, disclose_email=False)
|
expectedCreateCommand = self._convertPublicContactToEpp(
|
||||||
|
expectedTechContact, disclose=False, disclose_fields=self.all_disclose_fields
|
||||||
|
)
|
||||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||||
# Confirm that we are getting a default email
|
# Confirm that we are getting a default email
|
||||||
self.assertEqual(domain.technical_contact.email, expectedTechContact.email)
|
self.assertEqual(domain.technical_contact.email, expectedTechContact.email)
|
||||||
|
@ -1108,7 +1169,7 @@ class TestRegistrantContacts(MockEppLib):
|
||||||
expectedSecContact.domain = domain
|
expectedSecContact.domain = domain
|
||||||
expectedSecContact.email = "security@mail.gov"
|
expectedSecContact.email = "security@mail.gov"
|
||||||
domain.security_contact = expectedSecContact
|
domain.security_contact = expectedSecContact
|
||||||
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose_email=True)
|
expectedCreateCommand = self._convertPublicContactToEpp(expectedSecContact, disclose=False)
|
||||||
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
self.mockedSendFunction.assert_any_call(expectedCreateCommand, cleaned=True)
|
||||||
# Confirm that we are getting the desired email
|
# Confirm that we are getting the desired email
|
||||||
self.assertEqual(domain.security_contact.email, expectedSecContact.email)
|
self.assertEqual(domain.security_contact.email, expectedSecContact.email)
|
||||||
|
|
|
@ -376,8 +376,8 @@ class TestPortfolio(WebTest):
|
||||||
self.assertContains(page, "Non-Federal Agency")
|
self.assertContains(page, "Non-Federal Agency")
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_domain_org_name_address_form(self):
|
def test_org_form_invalid_update(self):
|
||||||
"""Submitting changes works on the org name address page."""
|
"""Organization form will not redirect on invalid formsets."""
|
||||||
with override_flag("organization_feature", active=True):
|
with override_flag("organization_feature", active=True):
|
||||||
self.app.set_user(self.user.username)
|
self.app.set_user(self.user.username)
|
||||||
portfolio_additional_permissions = [
|
portfolio_additional_permissions = [
|
||||||
|
@ -398,11 +398,79 @@ class TestPortfolio(WebTest):
|
||||||
|
|
||||||
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
success_result_page = portfolio_org_name_page.form.submit()
|
success_result_page = portfolio_org_name_page.form.submit()
|
||||||
|
# Form will not validate with missing required field (zipcode)
|
||||||
self.assertEqual(success_result_page.status_code, 200)
|
self.assertEqual(success_result_page.status_code, 200)
|
||||||
|
|
||||||
self.assertContains(success_result_page, "6 Downing st")
|
self.assertContains(success_result_page, "6 Downing st")
|
||||||
self.assertContains(success_result_page, "London")
|
self.assertContains(success_result_page, "London")
|
||||||
|
|
||||||
|
@less_console_noise_decorator
|
||||||
|
def test_org_form_valid_update(self):
|
||||||
|
"""Organization form will redirect on valid formsets."""
|
||||||
|
with override_flag("organization_feature", active=True):
|
||||||
|
self.app.set_user(self.user.username)
|
||||||
|
portfolio_additional_permissions = [
|
||||||
|
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
||||||
|
UserPortfolioPermissionChoices.EDIT_PORTFOLIO,
|
||||||
|
]
|
||||||
|
portfolio_permission, _ = UserPortfolioPermission.objects.get_or_create(
|
||||||
|
user=self.user, portfolio=self.portfolio, additional_permissions=portfolio_additional_permissions
|
||||||
|
)
|
||||||
|
|
||||||
|
self.portfolio.address_line1 = "1600 Penn Ave"
|
||||||
|
self.portfolio.save()
|
||||||
|
portfolio_org_name_page = self.app.get(reverse("organization"))
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
|
||||||
|
# Form validates and redirects with all required fields
|
||||||
|
portfolio_org_name_page.form["address_line1"] = "6 Downing st"
|
||||||
|
portfolio_org_name_page.form["city"] = "London"
|
||||||
|
portfolio_org_name_page.form["zipcode"] = "11111"
|
||||||
|
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
success_result_page = portfolio_org_name_page.form.submit()
|
||||||
|
self.assertEqual(success_result_page.status_code, 302)
|
||||||
|
|
||||||
|
@boto3_mocking.patching
|
||||||
|
@less_console_noise_decorator
|
||||||
|
@patch("registrar.views.portfolios.send_portfolio_update_emails_to_portfolio_admins")
|
||||||
|
def test_org_update_sends_admin_email(self, mock_send_organization_update_email):
|
||||||
|
"""Updating organization information emails organization admin."""
|
||||||
|
with override_flag("organization_feature", active=True):
|
||||||
|
self.app.set_user(self.user.username)
|
||||||
|
self.admin, _ = User.objects.get_or_create(
|
||||||
|
email="mayor@igorville.com", first_name="Hello", last_name="World"
|
||||||
|
)
|
||||||
|
|
||||||
|
portfolio_additional_permissions = [
|
||||||
|
UserPortfolioPermissionChoices.VIEW_PORTFOLIO,
|
||||||
|
UserPortfolioPermissionChoices.EDIT_PORTFOLIO,
|
||||||
|
]
|
||||||
|
portfolio_permission, _ = UserPortfolioPermission.objects.get_or_create(
|
||||||
|
user=self.user, portfolio=self.portfolio, additional_permissions=portfolio_additional_permissions
|
||||||
|
)
|
||||||
|
portfolio_permission_admin, _ = UserPortfolioPermission.objects.get_or_create(
|
||||||
|
user=self.admin,
|
||||||
|
portfolio=self.portfolio,
|
||||||
|
additional_permissions=portfolio_additional_permissions,
|
||||||
|
roles=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN],
|
||||||
|
)
|
||||||
|
|
||||||
|
self.portfolio.address_line1 = "1600 Penn Ave"
|
||||||
|
self.portfolio.save()
|
||||||
|
portfolio_org_name_page = self.app.get(reverse("organization"))
|
||||||
|
session_id = self.app.cookies[settings.SESSION_COOKIE_NAME]
|
||||||
|
portfolio_org_name_page.form["address_line1"] = "6 Downing st"
|
||||||
|
portfolio_org_name_page.form["city"] = "London"
|
||||||
|
portfolio_org_name_page.form["zipcode"] = "11111"
|
||||||
|
|
||||||
|
self.app.set_cookie(settings.SESSION_COOKIE_NAME, session_id)
|
||||||
|
success_result_page = portfolio_org_name_page.form.submit()
|
||||||
|
self.assertEqual(success_result_page.status_code, 302)
|
||||||
|
|
||||||
|
# Verify that the notification emails were sent to domain manager
|
||||||
|
mock_send_organization_update_email.assert_called_once()
|
||||||
|
|
||||||
@less_console_noise_decorator
|
@less_console_noise_decorator
|
||||||
def test_portfolio_in_session_when_organization_feature_active(self):
|
def test_portfolio_in_session_when_organization_feature_active(self):
|
||||||
"""When organization_feature flag is true and user has a portfolio,
|
"""When organization_feature flag is true and user has a portfolio,
|
||||||
|
|
|
@ -740,7 +740,7 @@ class DomainExport(BaseExport):
|
||||||
domain_type = f"{human_readable_domain_org_type} - {human_readable_domain_federal_type}"
|
domain_type = f"{human_readable_domain_org_type} - {human_readable_domain_federal_type}"
|
||||||
|
|
||||||
security_contact_email = model.get("security_contact_email")
|
security_contact_email = model.get("security_contact_email")
|
||||||
invalid_emails = {DefaultEmail.LEGACY_DEFAULT.value, DefaultEmail.PUBLIC_CONTACT_DEFAULT.value}
|
invalid_emails = DefaultEmail.get_all_emails()
|
||||||
if (
|
if (
|
||||||
not security_contact_email
|
not security_contact_email
|
||||||
or not isinstance(security_contact_email, str)
|
or not isinstance(security_contact_email, str)
|
||||||
|
|
|
@ -280,6 +280,56 @@ def send_portfolio_invitation_email(email: str, requestor, portfolio, is_admin_i
|
||||||
return all_admin_emails_sent
|
return all_admin_emails_sent
|
||||||
|
|
||||||
|
|
||||||
|
def send_portfolio_update_emails_to_portfolio_admins(editor, portfolio, updated_page):
|
||||||
|
"""
|
||||||
|
Sends an email notification to all portfolio admin when portfolio organization is updated.
|
||||||
|
|
||||||
|
Raises exceptions for validation or email-sending issues.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
editor (User): The user editing the portfolio organization.
|
||||||
|
portfolio (Portfolio): The portfolio object whose organization information is changed.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
Boolean indicating if all messages were sent successfully.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
MissingEmailError: If the requestor has no email associated with their account.
|
||||||
|
EmailSendingError: If there is an error while sending the email.
|
||||||
|
"""
|
||||||
|
all_emails_sent = True
|
||||||
|
# Get each portfolio admin from list
|
||||||
|
user_portfolio_permissions = UserPortfolioPermission.objects.filter(
|
||||||
|
portfolio=portfolio, roles__contains=[UserPortfolioRoleChoices.ORGANIZATION_ADMIN]
|
||||||
|
)
|
||||||
|
for user_portfolio_permission in user_portfolio_permissions:
|
||||||
|
# Send email to each portfolio_admin
|
||||||
|
user = user_portfolio_permission.user
|
||||||
|
try:
|
||||||
|
send_templated_email(
|
||||||
|
"emails/portfolio_org_update_notification.txt",
|
||||||
|
"emails/portfolio_org_update_notification_subject.txt",
|
||||||
|
to_address=user.email,
|
||||||
|
context={
|
||||||
|
"requested_user": user,
|
||||||
|
"portfolio": portfolio,
|
||||||
|
"editor": editor,
|
||||||
|
"portfolio_admin": user,
|
||||||
|
"date": date.today(),
|
||||||
|
"updated_info": updated_page,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
except EmailSendingError:
|
||||||
|
logger.warning(
|
||||||
|
"Could not send email organization admin notification to %s " "for portfolio: %s",
|
||||||
|
user.email,
|
||||||
|
portfolio,
|
||||||
|
exc_info=True,
|
||||||
|
)
|
||||||
|
all_emails_sent = False
|
||||||
|
return all_emails_sent
|
||||||
|
|
||||||
|
|
||||||
def send_portfolio_member_permission_update_email(requestor, permissions: UserPortfolioPermission):
|
def send_portfolio_member_permission_update_email(requestor, permissions: UserPortfolioPermission):
|
||||||
"""
|
"""
|
||||||
Sends an email notification to a portfolio member when their permissions are updated.
|
Sends an email notification to a portfolio member when their permissions are updated.
|
||||||
|
|
|
@ -29,18 +29,26 @@ class LogCode(Enum):
|
||||||
DEFAULT = 5
|
DEFAULT = 5
|
||||||
|
|
||||||
|
|
||||||
class DefaultEmail(Enum):
|
class DefaultEmail(StrEnum):
|
||||||
"""Stores the string values of default emails
|
"""Stores the string values of default emails
|
||||||
|
|
||||||
Overview of emails:
|
Overview of emails:
|
||||||
- PUBLIC_CONTACT_DEFAULT: "dotgov@cisa.dhs.gov"
|
- PUBLIC_CONTACT_DEFAULT: "help@get.gov"
|
||||||
|
- OLD_PUBLIC_CONTACT_DEFAULT: "dotgov@cisa.dhs.gov"
|
||||||
- LEGACY_DEFAULT: "registrar@dotgov.gov"
|
- LEGACY_DEFAULT: "registrar@dotgov.gov"
|
||||||
- HELP_EMAIL: "help@get.gov"
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
PUBLIC_CONTACT_DEFAULT = "dotgov@cisa.dhs.gov"
|
PUBLIC_CONTACT_DEFAULT = "help@get.gov"
|
||||||
|
# We used to use this email for default public contacts.
|
||||||
|
# This is retained for data correctness, but it will be phased out.
|
||||||
|
# help@get.gov is the current email that we use for these now.
|
||||||
|
OLD_PUBLIC_CONTACT_DEFAULT = "dotgov@cisa.dhs.gov"
|
||||||
LEGACY_DEFAULT = "registrar@dotgov.gov"
|
LEGACY_DEFAULT = "registrar@dotgov.gov"
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_all_emails(cls):
|
||||||
|
return [email for email in cls]
|
||||||
|
|
||||||
|
|
||||||
class DefaultUserValues(StrEnum):
|
class DefaultUserValues(StrEnum):
|
||||||
"""Stores default values for a default user.
|
"""Stores default values for a default user.
|
||||||
|
|
|
@ -402,7 +402,7 @@ class DomainView(DomainBaseView):
|
||||||
def get_context_data(self, **kwargs):
|
def get_context_data(self, **kwargs):
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
default_emails = [DefaultEmail.PUBLIC_CONTACT_DEFAULT.value, DefaultEmail.LEGACY_DEFAULT.value]
|
default_emails = DefaultEmail.get_all_emails()
|
||||||
|
|
||||||
context["hidden_security_emails"] = default_emails
|
context["hidden_security_emails"] = default_emails
|
||||||
|
|
||||||
|
@ -460,7 +460,7 @@ class DomainRenewalView(DomainBaseView):
|
||||||
|
|
||||||
context = super().get_context_data(**kwargs)
|
context = super().get_context_data(**kwargs)
|
||||||
|
|
||||||
default_emails = [DefaultEmail.PUBLIC_CONTACT_DEFAULT.value, DefaultEmail.LEGACY_DEFAULT.value]
|
default_emails = DefaultEmail.get_all_emails()
|
||||||
|
|
||||||
context["hidden_security_emails"] = default_emails
|
context["hidden_security_emails"] = default_emails
|
||||||
|
|
||||||
|
@ -1170,7 +1170,7 @@ class DomainSecurityEmailView(DomainFormBaseView):
|
||||||
initial = super().get_initial()
|
initial = super().get_initial()
|
||||||
security_contact = self.object.security_contact
|
security_contact = self.object.security_contact
|
||||||
|
|
||||||
invalid_emails = [DefaultEmail.PUBLIC_CONTACT_DEFAULT.value, DefaultEmail.LEGACY_DEFAULT.value]
|
invalid_emails = DefaultEmail.get_all_emails()
|
||||||
if security_contact is None or security_contact.email in invalid_emails:
|
if security_contact is None or security_contact.email in invalid_emails:
|
||||||
initial["security_email"] = None
|
initial["security_email"] = None
|
||||||
return initial
|
return initial
|
||||||
|
|
|
@ -36,6 +36,7 @@ from registrar.utility.email_invitations import (
|
||||||
send_portfolio_invitation_remove_email,
|
send_portfolio_invitation_remove_email,
|
||||||
send_portfolio_member_permission_remove_email,
|
send_portfolio_member_permission_remove_email,
|
||||||
send_portfolio_member_permission_update_email,
|
send_portfolio_member_permission_update_email,
|
||||||
|
send_portfolio_update_emails_to_portfolio_admins,
|
||||||
)
|
)
|
||||||
from registrar.utility.errors import MissingEmailError
|
from registrar.utility.errors import MissingEmailError
|
||||||
from registrar.utility.enums import DefaultUserValues
|
from registrar.utility.enums import DefaultUserValues
|
||||||
|
@ -930,6 +931,20 @@ class PortfolioOrganizationView(DetailView, FormMixin):
|
||||||
self.object = self.get_object()
|
self.object = self.get_object()
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
if form.is_valid():
|
if form.is_valid():
|
||||||
|
user = request.user
|
||||||
|
try:
|
||||||
|
if not send_portfolio_update_emails_to_portfolio_admins(
|
||||||
|
editor=user, portfolio=self.request.session.get("portfolio"), updated_page="Organization"
|
||||||
|
):
|
||||||
|
messages.warning(self.request, "Could not send email notification to all organization admins.")
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(
|
||||||
|
request,
|
||||||
|
f"An unexpected error occurred: {str(e)}. If the issue persists, "
|
||||||
|
f"please contact {DefaultUserValues.HELP_EMAIL}.",
|
||||||
|
)
|
||||||
|
logger.error(f"An unexpected error occurred: {str(e)}.", exc_info=True)
|
||||||
|
return None
|
||||||
return self.form_valid(form)
|
return self.form_valid(form)
|
||||||
else:
|
else:
|
||||||
return self.form_invalid(form)
|
return self.form_invalid(form)
|
||||||
|
@ -982,6 +997,45 @@ class PortfolioSeniorOfficialView(DetailView, FormMixin):
|
||||||
form = self.get_form()
|
form = self.get_form()
|
||||||
return self.render_to_response(self.get_context_data(form=form))
|
return self.render_to_response(self.get_context_data(form=form))
|
||||||
|
|
||||||
|
def post(self, request, *args, **kwargs):
|
||||||
|
"""Handle POST requests to process form submission."""
|
||||||
|
self.object = self.get_object()
|
||||||
|
form = self.get_form()
|
||||||
|
if form.is_valid():
|
||||||
|
user = request.user
|
||||||
|
try:
|
||||||
|
if not send_portfolio_update_emails_to_portfolio_admins(
|
||||||
|
editor=user, portfolio=self.request.session.get("portfolio"), updated_page="Senior Official"
|
||||||
|
):
|
||||||
|
messages.warning(self.request, "Could not send email notification to all organization admins.")
|
||||||
|
except Exception as e:
|
||||||
|
messages.error(
|
||||||
|
request,
|
||||||
|
f"An unexpected error occurred: {str(e)}. If the issue persists, "
|
||||||
|
f"please contact {DefaultUserValues.HELP_EMAIL}.",
|
||||||
|
)
|
||||||
|
logger.error(f"An unexpected error occurred: {str(e)}.", exc_info=True)
|
||||||
|
return None
|
||||||
|
return self.form_valid(form)
|
||||||
|
else:
|
||||||
|
return self.form_invalid(form)
|
||||||
|
|
||||||
|
def form_valid(self, form):
|
||||||
|
"""Handle the case when the form is valid."""
|
||||||
|
self.object = form.save(commit=False)
|
||||||
|
self.object.creator = self.request.user
|
||||||
|
self.object.save()
|
||||||
|
messages.success(self.request, "The senior official information for this portfolio has been updated.")
|
||||||
|
return super().form_valid(form)
|
||||||
|
|
||||||
|
def form_invalid(self, form):
|
||||||
|
"""Handle the case when the form is invalid."""
|
||||||
|
return self.render_to_response(self.get_context_data(form=form))
|
||||||
|
|
||||||
|
def get_success_url(self):
|
||||||
|
"""Redirect to the overview page for the portfolio."""
|
||||||
|
return reverse("senior-official")
|
||||||
|
|
||||||
|
|
||||||
@grant_access(HAS_PORTFOLIO_MEMBERS_ANY_PERM)
|
@grant_access(HAS_PORTFOLIO_MEMBERS_ANY_PERM)
|
||||||
class PortfolioMembersView(View):
|
class PortfolioMembersView(View):
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue