mirror of
https://github.com/cisagov/manage.get.gov.git
synced 2025-08-15 05:54:11 +02:00
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:
commit
d6a7f69a00
2 changed files with 148 additions and 7 deletions
|
@ -4,6 +4,9 @@ from django.contrib.auth.models import AbstractUser
|
||||||
from django.db import models
|
from django.db import models
|
||||||
|
|
||||||
from .domain_invitation import DomainInvitation
|
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
|
from phonenumber_field.modelfields import PhoneNumberField # type: ignore
|
||||||
|
|
||||||
|
@ -62,12 +65,9 @@ class User(AbstractUser):
|
||||||
def is_restricted(self):
|
def is_restricted(self):
|
||||||
return self.status == self.RESTRICTED
|
return self.status == self.RESTRICTED
|
||||||
|
|
||||||
def first_login(self):
|
def check_domain_invitations_on_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."""
|
||||||
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(
|
for invitation in DomainInvitation.objects.filter(
|
||||||
email=self.email, status=DomainInvitation.INVITED
|
email=self.email, status=DomainInvitation.INVITED
|
||||||
):
|
):
|
||||||
|
@ -82,6 +82,101 @@ class User(AbstractUser):
|
||||||
"Failed to retrieve invitation %s", invitation, exc_info=True
|
"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:
|
class Meta:
|
||||||
permissions = [
|
permissions = [
|
||||||
("analyst_access_permission", "Analyst Access Permission"),
|
("analyst_access_permission", "Analyst Access Permission"),
|
||||||
|
|
|
@ -14,7 +14,8 @@ from registrar.models import (
|
||||||
UserDomainRole,
|
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 .common import MockSESClient, less_console_noise, completed_application
|
||||||
from django_fsm import TransitionNotAllowed
|
from django_fsm import TransitionNotAllowed
|
||||||
|
|
||||||
|
@ -612,3 +613,48 @@ class TestInvitations(TestCase):
|
||||||
"""A new user's first_login callback retrieves their invitations."""
|
"""A new user's first_login callback retrieves their invitations."""
|
||||||
self.user.first_login()
|
self.user.first_login()
|
||||||
self.assertTrue(UserDomainRole.objects.get(user=self.user, domain=self.domain))
|
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))
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue