Basic logic

This commit is contained in:
zandercymatics 2024-04-18 15:30:45 -06:00
parent c565655aa8
commit 5cba82b343
No known key found for this signature in database
GPG key ID: FF4636ABEC9682B7
4 changed files with 83 additions and 26 deletions

View file

@ -490,7 +490,7 @@ class MyUserAdmin(BaseUserAdmin):
fieldsets = ( fieldsets = (
( (
None, None,
{"fields": ("username", "password", "status")}, {"fields": ("username", "password", "status", "verification_type")},
), ),
("Personal Info", {"fields": ("first_name", "last_name", "email")}), ("Personal Info", {"fields": ("first_name", "last_name", "email")}),
( (
@ -508,6 +508,8 @@ class MyUserAdmin(BaseUserAdmin):
("Important dates", {"fields": ("last_login", "date_joined")}), ("Important dates", {"fields": ("last_login", "date_joined")}),
) )
readonly_fields = ("verification_type")
# Hide Username (uuid), Groups and Permissions # Hide Username (uuid), Groups and Permissions
# Q: Now that we're using Groups and Permissions, # Q: Now that we're using Groups and Permissions,
# do we expose those to analysts to view? # do we expose those to analysts to view?

View file

@ -1,3 +1,4 @@
from enum import Enum
import logging import logging
from django.contrib.auth.models import AbstractUser from django.contrib.auth.models import AbstractUser
@ -23,6 +24,16 @@ class User(AbstractUser):
but can be customized later. but can be customized later.
""" """
class VerificationTypeChoices(models.TextChoices):
"""
Users achieve access to our system in a few different ways.
These choices reflect those pathways.
"""
GRANDFATHERED = "grandfathered", "Legacy user"
VERIFIED_BY_STAFF = "verified_by_staff", "Verified by staff"
REGULAR = "regular", "Verified by Login.gov"
INVITED = "invited", "Invited by a domain manager"
# #### Constants for choice fields #### # #### Constants for choice fields ####
RESTRICTED = "restricted" RESTRICTED = "restricted"
STATUS_CHOICES = ((RESTRICTED, RESTRICTED),) STATUS_CHOICES = ((RESTRICTED, RESTRICTED),)
@ -48,6 +59,13 @@ class User(AbstractUser):
db_index=True, db_index=True,
) )
verification_type = models.CharField(
choices=VerificationTypeChoices,
null=True,
blank=True,
help_text="The means through which this user was verified",
)
def __str__(self): def __str__(self):
# this info is pulled from Login.gov # this info is pulled from Login.gov
if self.first_name or self.last_name: if self.first_name or self.last_name:
@ -95,6 +113,22 @@ class User(AbstractUser):
def has_contact_info(self): def has_contact_info(self):
return bool(self.contact.title or self.contact.email or self.contact.phone) return bool(self.contact.title or self.contact.email or self.contact.phone)
@classmethod
def get_existing_user_from_uuid(cls, uuid):
existing_user = None
try:
existing_user = cls.objects.get(username=uuid)
if existing_user and UserDomainRole.objects.filter(user=existing_user).exists():
return (False, existing_user)
except cls.DoesNotExist:
# Do nothing when the user is not found, as we're checking for existence.
pass
except Exception as err:
raise err
return (True, existing_user)
@classmethod @classmethod
def needs_identity_verification(cls, email, uuid): def needs_identity_verification(cls, email, uuid):
"""A method used by our oidc classes to test whether a user needs email/uuid verification """A method used by our oidc classes to test whether a user needs email/uuid verification
@ -102,33 +136,52 @@ class User(AbstractUser):
# An existing user who is a domain manager of a domain (that is, # An existing user who is a domain manager of a domain (that is,
# they have an entry in UserDomainRole for their User) # they have an entry in UserDomainRole for their User)
try: user_exists, existing_user = cls.existing_user(uuid)
existing_user = cls.objects.get(username=uuid) if not user_exists:
if existing_user and UserDomainRole.objects.filter(user=existing_user).exists(): return False
return False
except cls.DoesNotExist:
# Do nothing when the user is not found, as we're checking for existence.
pass
except Exception as err:
raise err
# A new incoming user who is a domain manager for one of the domains # The user needs identity verification if they don't meet
# that we inputted from Verisign (that is, their email address appears # any special criteria, i.e. we are validating them "regularly"
# in the username field of a TransitionDomain) existing_user.verification_type = cls.get_verification_type_from_email(email)
return existing_user.verification_type == cls.VerificationTypeChoices.REGULAR
@classmethod
def get_verification_type_from_email(cls, email, invitation_status=DomainInvitation.DomainInvitationStatus.INVITED):
"""Retrieves the verification type based off of a provided email address"""
verification_type = None
if TransitionDomain.objects.filter(username=email).exists(): if TransitionDomain.objects.filter(username=email).exists():
return False # A new incoming user who is a domain manager for one of the domains
# that we inputted from Verisign (that is, their email address appears
# in the username field of a TransitionDomain)
verification_type = cls.VerificationTypeChoices.GRANDFATHERED
elif VerifiedByStaff.objects.filter(email=email).exists():
# New users flagged by Staff to bypass ial2
verification_type = cls.VerificationTypeChoices.VERIFIED_BY_STAFF
elif DomainInvitation.objects.filter(email=email, status=invitation_status).exists():
# A new incoming user who is being invited to be a domain manager (that is,
# their email address is in DomainInvitation for an invitation that is not yet "retrieved").
verification_type = cls.VerificationTypeChoices.INVITED
else:
verification_type = cls.VerificationTypeChoices.REGULAR
return verification_type
# New users flagged by Staff to bypass ial2 def user_verification_type(self, check_if_user_exists=False):
if VerifiedByStaff.objects.filter(email=email).exists(): if self.verification_type is None:
return False # Would need to check audit log
retrieved = DomainInvitation.DomainInvitationStatus.RETRIEVED
user_exists, _ = self.existing_user(self.username)
verification_type = self.get_verification_type_from_email(self.email, invitation_status=retrieved)
# A new incoming user who is being invited to be a domain manager (that is, # This should check if the type is unknown, use check_if_user_exists?
# their email address is in DomainInvitation for an invitation that is not yet "retrieved"). if verification_type == self.VerificationTypeChoices.REGULAR and not user_exists:
invited = DomainInvitation.DomainInvitationStatus.INVITED raise ValueError(f"No verification_type was found for {self} with id: {self.pk}")
if DomainInvitation.objects.filter(email=email, status=invited).exists(): else:
return False self.verification_type = verification_type
return self.verification_type
return True else:
return self.verification_type
def check_domain_invitations_on_login(self): def check_domain_invitations_on_login(self):
"""When a user first arrives on the site, we need to retrieve any domain """When a user first arrives on the site, we need to retrieve any domain

View file

@ -3,7 +3,7 @@
<address class="{% if no_title_top_padding %}margin-top-neg-1__detail-list{% endif %} {% if user.has_contact_info %}margin-bottom-1{% endif %} dja-address-contact-list"> <address class="{% if no_title_top_padding %}margin-top-neg-1__detail-list{% endif %} {% if user.has_contact_info %}margin-bottom-1{% endif %} dja-address-contact-list">
{% if show_formatted_name %} {% if show_formatted_name %}
{% if contact.get_formatted_name %} {% if user.get_formatted_name %}
<a href="{% url 'admin:registrar_contact_change' user.id %}">{{ user.get_formatted_name }}</a><br /> <a href="{% url 'admin:registrar_contact_change' user.id %}">{{ user.get_formatted_name }}</a><br />
{% else %} {% else %}
None<br /> None<br />

View file

@ -6,7 +6,9 @@ This is using a custom implementation fieldset.html (see admin/fieldset.html)
{% endcomment %} {% endcomment %}
{% block field_readonly %} {% block field_readonly %}
{% with all_contacts=original_object.other_contacts.all %} {% with all_contacts=original_object.other_contacts.all %}
{% if field.field.name == "other_contacts" %} {% if field.field.name == "creator" %}
<div class="readonly">{{ field.contents }} ({{ user.verification_type }})</div>
{% elif field.field.name == "other_contacts" %}
{% if all_contacts.count > 2 %} {% if all_contacts.count > 2 %}
<div class="readonly"> <div class="readonly">
{% for contact in all_contacts %} {% for contact in all_contacts %}