Merge pull request #1176 from cisagov/nl/980-User-login-transition-domain-info

Issue 980: user login transition domain info
This commit is contained in:
CuriousX 2023-10-25 16:12:59 -06:00 committed by GitHub
commit d6a7f69a00
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 148 additions and 7 deletions

View file

@ -4,6 +4,9 @@ from django.contrib.auth.models import AbstractUser
from django.db import models
from .domain_invitation import DomainInvitation
from .transition_domain import TransitionDomain
from .domain_information import DomainInformation
from .domain import Domain
from phonenumber_field.modelfields import PhoneNumberField # type: ignore
@ -62,12 +65,9 @@ class User(AbstractUser):
def is_restricted(self):
return self.status == self.RESTRICTED
def first_login(self):
"""Callback when the user is authenticated for the very first time.
When a user first arrives on the site, we need to retrieve any domain
invitations that match their email address.
"""
def check_domain_invitations_on_login(self):
"""When a user first arrives on the site, we need to retrieve any domain
invitations that match their email address."""
for invitation in DomainInvitation.objects.filter(
email=self.email, status=DomainInvitation.INVITED
):
@ -82,6 +82,101 @@ class User(AbstractUser):
"Failed to retrieve invitation %s", invitation, exc_info=True
)
def create_domain_and_invite(self, transition_domain: TransitionDomain):
transition_domain_name = transition_domain.domain_name
transition_domain_status = transition_domain.status
transition_domain_email = transition_domain.username
# type safety check. name should never be none
if transition_domain_name is not None:
new_domain = Domain(
name=transition_domain_name, state=transition_domain_status
)
new_domain.save()
# check that a domain invitation doesn't already
# exist for this e-mail / Domain pair
domain_email_already_in_domain_invites = DomainInvitation.objects.filter(
email=transition_domain_email.lower(), domain=new_domain
).exists()
if not domain_email_already_in_domain_invites:
# Create new domain invitation
new_domain_invitation = DomainInvitation(
email=transition_domain_email.lower(), domain=new_domain
)
new_domain_invitation.save()
def check_transition_domains_on_login(self):
"""When a user first arrives on the site, we need to check
if they are logging in with the same e-mail as a
transition domain and update our database accordingly."""
for transition_domain in TransitionDomain.objects.filter(username=self.email):
# Looks like the user logged in with the same e-mail as
# one or more corresponding transition domains.
# Create corresponding DomainInformation objects.
# NOTE: adding an ADMIN user role for this user
# for each domain should already be done
# in the invitation.retrieve() method.
# However, if the migration scripts for transition
# domain objects were not executed correctly,
# there could be transition domains without
# any corresponding Domain & DomainInvitation objects,
# which means the invitation.retrieve() method might
# not execute.
# Check that there is a corresponding domain object
# for this transition domain. If not, we have an error
# with our data and migrations need to be run again.
# Get the domain that corresponds with this transition domain
domain_exists = Domain.objects.filter(
name=transition_domain.domain_name
).exists()
if not domain_exists:
logger.warn(
"""There are transition domains without
corresponding domain objects!
Please run migration scripts for transition domains
(See data_migration.md)"""
)
# No need to throw an exception...just create a domain
# and domain invite, then proceed as normal
self.create_domain_and_invite(transition_domain)
domain = Domain.objects.get(name=transition_domain.domain_name)
# Create a domain information object, if one doesn't
# already exist
domain_info_exists = DomainInformation.objects.filter(
domain=domain
).exists()
if not domain_info_exists:
new_domain_info = DomainInformation(creator=self, domain=domain)
new_domain_info.save()
def first_login(self):
"""Callback when the user is authenticated for the very first time.
When a user first arrives on the site, we need to retrieve any domain
invitations that match their email address.
We also need to check if they are logging in with the same e-mail
as a transition domain and update our domainInfo objects accordingly.
"""
# PART 1: TRANSITION DOMAINS
#
# NOTE: THIS MUST RUN FIRST
# (If we have an issue where transition domains were
# not fully converted into Domain and DomainInvitation
# objects, this method will fill in the gaps.
# This will ensure the Domain Invitations method
# runs correctly (no missing invites))
self.check_transition_domains_on_login()
# PART 2: DOMAIN INVITATIONS
self.check_domain_invitations_on_login()
class Meta:
permissions = [
("analyst_access_permission", "Analyst Access Permission"),

View file

@ -14,7 +14,8 @@ from registrar.models import (
UserDomainRole,
)
import boto3_mocking # type: ignore
import boto3_mocking
from registrar.models.transition_domain import TransitionDomain # type: ignore
from .common import MockSESClient, less_console_noise, completed_application
from django_fsm import TransitionNotAllowed
@ -612,3 +613,48 @@ class TestInvitations(TestCase):
"""A new user's first_login callback retrieves their invitations."""
self.user.first_login()
self.assertTrue(UserDomainRole.objects.get(user=self.user, domain=self.domain))
class TestUser(TestCase):
"""For now, just test actions that
occur on user login."""
def setUp(self):
self.email = "mayor@igorville.gov"
self.domain_name = "igorvilleInTransition.gov"
self.user, _ = User.objects.get_or_create(email=self.email)
# clean out the roles each time
UserDomainRole.objects.all().delete()
TransitionDomain.objects.get_or_create(
username="mayor@igorville.gov", domain_name=self.domain_name
)
def tearDown(self):
super().tearDown()
Domain.objects.all().delete()
DomainInvitation.objects.all().delete()
DomainInformation.objects.all().delete()
TransitionDomain.objects.all().delete()
User.objects.all().delete()
def test_check_transition_domains_on_login(self):
"""A new user's first_login callback checks transition domains.
Makes DomainInformation object."""
self.domain, _ = Domain.objects.get_or_create(name=self.domain_name)
self.user.first_login()
self.assertTrue(DomainInformation.objects.get(domain=self.domain))
def test_check_transition_domains_without_domains_on_login(self):
"""A new user's first_login callback checks transition domains.
This test makes sure that in the event a domain does not exist
for a given transition domain, both a domain and domain invitation
are created."""
self.user.first_login()
self.assertTrue(Domain.objects.get(name=self.domain_name))
domain = Domain.objects.get(name=self.domain_name)
self.assertTrue(DomainInvitation.objects.get(email=self.email, domain=domain))
self.assertTrue(DomainInformation.objects.get(domain=domain))