We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Move first_login logic so that it occurs on all logins
### Issue description

Currently, we have a function (first_login in User.py) that is triggered when it is the users first time logging into our system. In this function, we are checking 1.) are they part of a transition domain, if so make sure there is a domain inviation for them and a domain information object, 2) for all domain invitations they have,  we add a user domain role to their account so they can access the domains.

The problem is when it comes time for transition, if anyone logs into our system BEFORE we run the migration scripts then they will already have a User row and account on our system and they will not trigger the checks above. This is a bit of a race condition that is easily avoided by just having the first_login logic triggered on every login.

### Acceptance criteria

- [ ] rename first_login and all uses of it  (as it is no longer used just at first login
- [ ] move the formerly named first_login to be called for all users. 
- [ ] add or update existing unit tests 

### Additional context

To accomplish the second AC above I suggest moving the highlight lint (user.first_login()) in the backend.py Authenticate function to right above the return on line 59 so it is called on each user. See picture below:
![image](https://github.com/cisagov/getgov/assets/109625347/39ca23f7-d547-4ce4-b32d-4f55e4aee4d9)


### Links to other issues

_No response_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `src/djangooidc/backends.py`
Content:
```
1 # coding: utf-8
2 from __future__ import unicode_literals
3 
4 import logging
5 
6 from django.conf import settings
7 from django.contrib.auth import get_user_model
8 from django.contrib.auth.backends import ModelBackend
9 from django.utils import timezone
10 
11 logger = logging.getLogger(__name__)
12 
13 
14 class OpenIdConnectBackend(ModelBackend):
15     """
16     This backend checks a previously performed OIDC authentication.
17     If it is OK and the user already exists in the database, it is returned.
18     If it is OK and user does not exist in the database, it is created and
19         returned unless setting OIDC_CREATE_UNKNOWN_USER is False.
20     In all other cases, None is returned.
21     """
22 
23     def authenticate(self, request, **kwargs):
24         logger.debug("kwargs %s" % kwargs)
25         user = None
26         if not kwargs or "sub" not in kwargs.keys():
27             return user
28 
29         UserModel = get_user_model()
30         username = self.clean_username(kwargs["sub"])
31 
32         # Some OP may actually choose to withhold some information, so we must
33         # test if it is present
34         openid_data = {"last_login": timezone.now()}
35         openid_data["first_name"] = kwargs.get("given_name", "")
36         openid_data["last_name"] = kwargs.get("family_name", "")
37         openid_data["email"] = kwargs.get("email", "")
38         openid_data["phone"] = kwargs.get("phone", "")
39 
40         # Note that this could be accomplished in one try-except clause, but
41         # instead we use get_or_create when creating unknown users since it has
42         # built-in safeguards for multiple threads.
43         if getattr(settings, "OIDC_CREATE_UNKNOWN_USER", True):
44             args = {
45                 UserModel.USERNAME_FIELD: username,
46                 # defaults _will_ be updated, these are not fallbacks
47                 "defaults": openid_data,
48             }
49             user, created = UserModel.objects.update_or_create(**args)
50             if created:
51                 user = self.configure_user(user, **kwargs)
52                 # run a newly created user's callback for a first-time login
53                 user.first_login()
54         else:
55             try:
56                 user = UserModel.objects.get_by_natural_key(username)
57             except UserModel.DoesNotExist:
58                 return None
59         return user
60 
61     def clean_username(self, username):
62         """
63         Performs any cleaning on the "username" prior to using it to get or
64         create the user object.  Returns the cleaned username.
65         """
66         return username
67 
68     def configure_user(self, user, **kwargs):
69         """
70         Configures a user after creation and returns the updated user.
71         """
72         user.set_unusable_password()
73         return user
74
```

Path: `src/registrar/models/user.py`
Content:
```
1 import logging
2 
3 from django.contrib.auth.models import AbstractUser
4 from django.db import models
5 
6 from .domain_invitation import DomainInvitation
7 from .transition_domain import TransitionDomain
8 from .domain_information import DomainInformation
9 from .domain import Domain
10 
11 from phonenumber_field.modelfields import PhoneNumberField  # type: ignore
12 
13 
14 logger = logging.getLogger(__name__)
15 
16 
17 class User(AbstractUser):
18     """
19     A custom user model that performs identically to the default user model
20     but can be customized later.
21     """
22 
23     # #### Constants for choice fields ####
24     RESTRICTED = "restricted"
25     STATUS_CHOICES = ((RESTRICTED, RESTRICTED),)
26 
27     status = models.CharField(
28         max_length=10,
29         choices=STATUS_CHOICES,
30         default=None,  # Set the default value to None
31         null=True,  # Allow the field to be null
32         blank=True,  # Allow the field to be blank
33     )
34 
35     domains = models.ManyToManyField(
36         "registrar.Domain",
37         through="registrar.UserDomainRole",
38         related_name="users",
39     )
40 
41     phone = PhoneNumberField(
42         null=True,
43         blank=True,
44         help_text="Phone",
45         db_index=True,
46     )
47 
48     def __str__(self):
49         # this info is pulled from Login.gov
50         if self.first_name or self.last_name:
51             return f"{self.first_name or ''} {self.last_name or ''} {self.email or ''}"
52         elif self.email:
53             return self.email
54         else:
55             return self.username
56 
57     def restrict_user(self):
58         self.status = self.RESTRICTED
59         self.save()
60 
61     def unrestrict_user(self):
62         self.status = None
63         self.save()
64 
65     def is_restricted(self):
66         return self.status == self.RESTRICTED
67 
68     def check_domain_invitations_on_login(self):
69         """When a user first arrives on the site, we need to retrieve any domain
70         invitations that match their email address."""
71         for invitation in DomainInvitation.objects.filter(
72             email=self.email, status=DomainInvitation.INVITED
73         ):
74             try:
75                 invitation.retrieve()
76                 invitation.save()
77             except RuntimeError:
78                 # retrieving should not fail because of a missing user, but
79                 # if it does fail, log the error so a new user can continue
80                 # logging in
81                 logger.warn(
82                     "Failed to retrieve invitation %s", invitation, exc_info=True
83                 )
84 
85     def create_domain_and_invite(self, transition_domain: TransitionDomain):
86         transition_domain_name = transition_domain.domain_name
87         transition_domain_status = transition_domain.status
88         transition_domain_email = transition_domain.username
89 
90         # type safety check.  name should never be none
91         if transition_domain_name is not None:
92             new_domain = Domain(
93                 name=transition_domain_name, state=transition_domain_status
94             )
95             new_domain.save()
96             # check that a domain invitation doesn't already
97             # exist for this e-mail / Domain pair
98             domain_email_already_in_domain_invites = DomainInvitation.objects.filter(
99                 email=transition_domain_email.lower(), domain=new_domain
100             ).exists()
101             if not domain_email_already_in_domain_invites:
102                 # Create new domain invitation
103                 new_domain_invitation = DomainInvitation(
104                     email=transition_domain_email.lower(), domain=new_domain
105                 )
106                 new_domain_invitation.save()
107 
108     def check_transition_domains_on_login(self):
109         """When a user first arrives on the site, we need to check
110         if they are logging in with the same e-mail as a
111         transition domain and update our database accordingly."""
112 
113         for transition_domain in TransitionDomain.objects.filter(username=self.email):
114             # Looks like the user logged in with the same e-mail as
115             # one or more corresponding transition domains.
116             # Create corresponding DomainInformation objects.
117 
118             # NOTE: adding an ADMIN user role for this user
119             # for each domain should already be done
120             # in the invitation.retrieve() method.
121             # However, if the migration scripts for transition
122             # domain objects were not executed correctly,
123             # there could be transition domains without
124             # any corresponding Domain & DomainInvitation objects,
125             # which means the invitation.retrieve() method might
126             # not execute.
127             # Check that there is a corresponding domain object
128             # for this transition domain.  If not, we have an error
129             # with our data and migrations need to be run again.
130 
131             # Get the domain that corresponds with this transition domain
132             domain_exists = Domain.objects.filter(
133                 name=transition_domain.domain_name
134             ).exists()
135             if not domain_exists:
136                 logger.warn(
137                     """There are transition domains without
138                             corresponding domain objects!
139                             Please run migration scripts for transition domains
140                             (See data_migration.md)"""
141                 )
142                 # No need to throw an exception...just create a domain
143                 # and domain invite, then proceed as normal
144                 self.create_domain_and_invite(transition_domain)
145 
146             domain = Domain.objects.get(name=transition_domain.domain_name)
147 
148             # Create a domain information object, if one doesn't
149             # already exist
150             domain_info_exists = DomainInformation.objects.filter(
151                 domain=domain
152             ).exists()
153             if not domain_info_exists:
154                 new_domain_info = DomainInformation(creator=self, domain=domain)
155                 new_domain_info.save()
156 
157     def first_login(self):
158         """Callback when the user is authenticated for the very first time.
159 
160         When a user first arrives on the site, we need to retrieve any domain
161         invitations that match their email address.
162 
163         We also need to check if they are logging in with the same e-mail
164         as a transition domain and update our domainInfo objects accordingly.
165         """
166 
167         # PART 1: TRANSITION DOMAINS
168         #
169         # NOTE: THIS MUST RUN FIRST
170         # (If we have an issue where transition domains were
171         # not fully converted into Domain and DomainInvitation
172         # objects, this method will fill in the gaps.
173         # This will ensure the Domain Invitations method
174         # runs correctly (no missing invites))
175         self.check_transition_domains_on_login()
176 
177         # PART 2: DOMAIN INVITATIONS
178         self.check_domain_invitations_on_login()
179 
180     class Meta:
181         permissions = [
182             ("analyst_access_permission", "Analyst Access Permission"),
183             ("full_access_permission", "Full Access Permission"),
184         ]
185
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/src/djangooidc/backends.py b/src/djangooidc/backends.py
--- a/src/djangooidc/backends.py
+++ b/src/djangooidc/backends.py
@@ -49,13 +49,13 @@
             user, created = UserModel.objects.update_or_create(**args)
             if created:
                 user = self.configure_user(user, **kwargs)
-                # run a newly created user's callback for a first-time login
-                user.first_login()
         else:
             try:
                 user = UserModel.objects.get_by_natural_key(username)
             except UserModel.DoesNotExist:
                 return None
+        # run this callback for a each login
+        user.on_each_login()
         return user
 
     def clean_username(self, username):
diff --git a/src/registrar/models/user.py b/src/registrar/models/user.py
--- a/src/registrar/models/user.py
+++ b/src/registrar/models/user.py
@@ -154,10 +154,10 @@
                 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.
+    def on_each_login(self):
+        """Callback each time the user is authenticated.
 
-        When a user first arrives on the site, we need to retrieve any domain
+        When a user arrives on the site each time, 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

{"golden_diff": "diff --git a/src/djangooidc/backends.py b/src/djangooidc/backends.py\n--- a/src/djangooidc/backends.py\n+++ b/src/djangooidc/backends.py\n@@ -49,13 +49,13 @@\n             user, created = UserModel.objects.update_or_create(**args)\n             if created:\n                 user = self.configure_user(user, **kwargs)\n-                # run a newly created user's callback for a first-time login\n-                user.first_login()\n         else:\n             try:\n                 user = UserModel.objects.get_by_natural_key(username)\n             except UserModel.DoesNotExist:\n                 return None\n+        # run this callback for a each login\n+        user.on_each_login()\n         return user\n \n     def clean_username(self, username):\ndiff --git a/src/registrar/models/user.py b/src/registrar/models/user.py\n--- a/src/registrar/models/user.py\n+++ b/src/registrar/models/user.py\n@@ -154,10 +154,10 @@\n                 new_domain_info = DomainInformation(creator=self, domain=domain)\n                 new_domain_info.save()\n \n-    def first_login(self):\n-        \"\"\"Callback when the user is authenticated for the very first time.\n+    def on_each_login(self):\n+        \"\"\"Callback each time the user is authenticated.\n \n-        When a user first arrives on the site, we need to retrieve any domain\n+        When a user arrives on the site each time, we need to retrieve any domain\n         invitations that match their email address.\n \n         We also need to check if they are logging in with the same e-mail\n", "issue": "Move first_login logic so that it occurs on all logins\n### Issue description\n\nCurrently, we have a function (first_login in User.py) that is triggered when it is the users first time logging into our system. In this function, we are checking 1.) are they part of a transition domain, if so make sure there is a domain inviation for them and a domain information object, 2) for all domain invitations they have,  we add a user domain role to their account so they can access the domains.\r\n\r\nThe problem is when it comes time for transition, if anyone logs into our system BEFORE we run the migration scripts then they will already have a User row and account on our system and they will not trigger the checks above. This is a bit of a race condition that is easily avoided by just having the first_login logic triggered on every login.\n\n### Acceptance criteria\n\n- [ ] rename first_login and all uses of it  (as it is no longer used just at first login\r\n- [ ] move the formerly named first_login to be called for all users. \r\n- [ ] add or update existing unit tests \n\n### Additional context\n\nTo accomplish the second AC above I suggest moving the highlight lint (user.first_login()) in the backend.py Authenticate function to right above the return on line 59 so it is called on each user. See picture below:\r\n![image](https://github.com/cisagov/getgov/assets/109625347/39ca23f7-d547-4ce4-b32d-4f55e4aee4d9)\r\n\n\n### Links to other issues\n\n_No response_\n", "before_files": [{"content": "# coding: utf-8\nfrom __future__ import unicode_literals\n\nimport logging\n\nfrom django.conf import settings\nfrom django.contrib.auth import get_user_model\nfrom django.contrib.auth.backends import ModelBackend\nfrom django.utils import timezone\n\nlogger = logging.getLogger(__name__)\n\n\nclass OpenIdConnectBackend(ModelBackend):\n    \"\"\"\n    This backend checks a previously performed OIDC authentication.\n    If it is OK and the user already exists in the database, it is returned.\n    If it is OK and user does not exist in the database, it is created and\n        returned unless setting OIDC_CREATE_UNKNOWN_USER is False.\n    In all other cases, None is returned.\n    \"\"\"\n\n    def authenticate(self, request, **kwargs):\n        logger.debug(\"kwargs %s\" % kwargs)\n        user = None\n        if not kwargs or \"sub\" not in kwargs.keys():\n            return user\n\n        UserModel = get_user_model()\n        username = self.clean_username(kwargs[\"sub\"])\n\n        # Some OP may actually choose to withhold some information, so we must\n        # test if it is present\n        openid_data = {\"last_login\": timezone.now()}\n        openid_data[\"first_name\"] = kwargs.get(\"given_name\", \"\")\n        openid_data[\"last_name\"] = kwargs.get(\"family_name\", \"\")\n        openid_data[\"email\"] = kwargs.get(\"email\", \"\")\n        openid_data[\"phone\"] = kwargs.get(\"phone\", \"\")\n\n        # Note that this could be accomplished in one try-except clause, but\n        # instead we use get_or_create when creating unknown users since it has\n        # built-in safeguards for multiple threads.\n        if getattr(settings, \"OIDC_CREATE_UNKNOWN_USER\", True):\n            args = {\n                UserModel.USERNAME_FIELD: username,\n                # defaults _will_ be updated, these are not fallbacks\n                \"defaults\": openid_data,\n            }\n            user, created = UserModel.objects.update_or_create(**args)\n            if created:\n                user = self.configure_user(user, **kwargs)\n                # run a newly created user's callback for a first-time login\n                user.first_login()\n        else:\n            try:\n                user = UserModel.objects.get_by_natural_key(username)\n            except UserModel.DoesNotExist:\n                return None\n        return user\n\n    def clean_username(self, username):\n        \"\"\"\n        Performs any cleaning on the \"username\" prior to using it to get or\n        create the user object.  Returns the cleaned username.\n        \"\"\"\n        return username\n\n    def configure_user(self, user, **kwargs):\n        \"\"\"\n        Configures a user after creation and returns the updated user.\n        \"\"\"\n        user.set_unusable_password()\n        return user\n", "path": "src/djangooidc/backends.py"}, {"content": "import logging\n\nfrom django.contrib.auth.models import AbstractUser\nfrom django.db import models\n\nfrom .domain_invitation import DomainInvitation\nfrom .transition_domain import TransitionDomain\nfrom .domain_information import DomainInformation\nfrom .domain import Domain\n\nfrom phonenumber_field.modelfields import PhoneNumberField  # type: ignore\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass User(AbstractUser):\n    \"\"\"\n    A custom user model that performs identically to the default user model\n    but can be customized later.\n    \"\"\"\n\n    # #### Constants for choice fields ####\n    RESTRICTED = \"restricted\"\n    STATUS_CHOICES = ((RESTRICTED, RESTRICTED),)\n\n    status = models.CharField(\n        max_length=10,\n        choices=STATUS_CHOICES,\n        default=None,  # Set the default value to None\n        null=True,  # Allow the field to be null\n        blank=True,  # Allow the field to be blank\n    )\n\n    domains = models.ManyToManyField(\n        \"registrar.Domain\",\n        through=\"registrar.UserDomainRole\",\n        related_name=\"users\",\n    )\n\n    phone = PhoneNumberField(\n        null=True,\n        blank=True,\n        help_text=\"Phone\",\n        db_index=True,\n    )\n\n    def __str__(self):\n        # this info is pulled from Login.gov\n        if self.first_name or self.last_name:\n            return f\"{self.first_name or ''} {self.last_name or ''} {self.email or ''}\"\n        elif self.email:\n            return self.email\n        else:\n            return self.username\n\n    def restrict_user(self):\n        self.status = self.RESTRICTED\n        self.save()\n\n    def unrestrict_user(self):\n        self.status = None\n        self.save()\n\n    def is_restricted(self):\n        return self.status == self.RESTRICTED\n\n    def check_domain_invitations_on_login(self):\n        \"\"\"When a user first arrives on the site, we need to retrieve any domain\n        invitations that match their email address.\"\"\"\n        for invitation in DomainInvitation.objects.filter(\n            email=self.email, status=DomainInvitation.INVITED\n        ):\n            try:\n                invitation.retrieve()\n                invitation.save()\n            except RuntimeError:\n                # retrieving should not fail because of a missing user, but\n                # if it does fail, log the error so a new user can continue\n                # logging in\n                logger.warn(\n                    \"Failed to retrieve invitation %s\", invitation, exc_info=True\n                )\n\n    def create_domain_and_invite(self, transition_domain: TransitionDomain):\n        transition_domain_name = transition_domain.domain_name\n        transition_domain_status = transition_domain.status\n        transition_domain_email = transition_domain.username\n\n        # type safety check.  name should never be none\n        if transition_domain_name is not None:\n            new_domain = Domain(\n                name=transition_domain_name, state=transition_domain_status\n            )\n            new_domain.save()\n            # check that a domain invitation doesn't already\n            # exist for this e-mail / Domain pair\n            domain_email_already_in_domain_invites = DomainInvitation.objects.filter(\n                email=transition_domain_email.lower(), domain=new_domain\n            ).exists()\n            if not domain_email_already_in_domain_invites:\n                # Create new domain invitation\n                new_domain_invitation = DomainInvitation(\n                    email=transition_domain_email.lower(), domain=new_domain\n                )\n                new_domain_invitation.save()\n\n    def check_transition_domains_on_login(self):\n        \"\"\"When a user first arrives on the site, we need to check\n        if they are logging in with the same e-mail as a\n        transition domain and update our database accordingly.\"\"\"\n\n        for transition_domain in TransitionDomain.objects.filter(username=self.email):\n            # Looks like the user logged in with the same e-mail as\n            # one or more corresponding transition domains.\n            # Create corresponding DomainInformation objects.\n\n            # NOTE: adding an ADMIN user role for this user\n            # for each domain should already be done\n            # in the invitation.retrieve() method.\n            # However, if the migration scripts for transition\n            # domain objects were not executed correctly,\n            # there could be transition domains without\n            # any corresponding Domain & DomainInvitation objects,\n            # which means the invitation.retrieve() method might\n            # not execute.\n            # Check that there is a corresponding domain object\n            # for this transition domain.  If not, we have an error\n            # with our data and migrations need to be run again.\n\n            # Get the domain that corresponds with this transition domain\n            domain_exists = Domain.objects.filter(\n                name=transition_domain.domain_name\n            ).exists()\n            if not domain_exists:\n                logger.warn(\n                    \"\"\"There are transition domains without\n                            corresponding domain objects!\n                            Please run migration scripts for transition domains\n                            (See data_migration.md)\"\"\"\n                )\n                # No need to throw an exception...just create a domain\n                # and domain invite, then proceed as normal\n                self.create_domain_and_invite(transition_domain)\n\n            domain = Domain.objects.get(name=transition_domain.domain_name)\n\n            # Create a domain information object, if one doesn't\n            # already exist\n            domain_info_exists = DomainInformation.objects.filter(\n                domain=domain\n            ).exists()\n            if not domain_info_exists:\n                new_domain_info = DomainInformation(creator=self, domain=domain)\n                new_domain_info.save()\n\n    def first_login(self):\n        \"\"\"Callback when the user is authenticated for the very first time.\n\n        When a user first arrives on the site, we need to retrieve any domain\n        invitations that match their email address.\n\n        We also need to check if they are logging in with the same e-mail\n        as a transition domain and update our domainInfo objects accordingly.\n        \"\"\"\n\n        # PART 1: TRANSITION DOMAINS\n        #\n        # NOTE: THIS MUST RUN FIRST\n        # (If we have an issue where transition domains were\n        # not fully converted into Domain and DomainInvitation\n        # objects, this method will fill in the gaps.\n        # This will ensure the Domain Invitations method\n        # runs correctly (no missing invites))\n        self.check_transition_domains_on_login()\n\n        # PART 2: DOMAIN INVITATIONS\n        self.check_domain_invitations_on_login()\n\n    class Meta:\n        permissions = [\n            (\"analyst_access_permission\", \"Analyst Access Permission\"),\n            (\"full_access_permission\", \"Full Access Permission\"),\n        ]\n", "path": "src/registrar/models/user.py"}], "after_files": [{"content": "# coding: utf-8\nfrom __future__ import unicode_literals\n\nimport logging\n\nfrom django.conf import settings\nfrom django.contrib.auth import get_user_model\nfrom django.contrib.auth.backends import ModelBackend\nfrom django.utils import timezone\n\nlogger = logging.getLogger(__name__)\n\n\nclass OpenIdConnectBackend(ModelBackend):\n    \"\"\"\n    This backend checks a previously performed OIDC authentication.\n    If it is OK and the user already exists in the database, it is returned.\n    If it is OK and user does not exist in the database, it is created and\n        returned unless setting OIDC_CREATE_UNKNOWN_USER is False.\n    In all other cases, None is returned.\n    \"\"\"\n\n    def authenticate(self, request, **kwargs):\n        logger.debug(\"kwargs %s\" % kwargs)\n        user = None\n        if not kwargs or \"sub\" not in kwargs.keys():\n            return user\n\n        UserModel = get_user_model()\n        username = self.clean_username(kwargs[\"sub\"])\n\n        # Some OP may actually choose to withhold some information, so we must\n        # test if it is present\n        openid_data = {\"last_login\": timezone.now()}\n        openid_data[\"first_name\"] = kwargs.get(\"given_name\", \"\")\n        openid_data[\"last_name\"] = kwargs.get(\"family_name\", \"\")\n        openid_data[\"email\"] = kwargs.get(\"email\", \"\")\n        openid_data[\"phone\"] = kwargs.get(\"phone\", \"\")\n\n        # Note that this could be accomplished in one try-except clause, but\n        # instead we use get_or_create when creating unknown users since it has\n        # built-in safeguards for multiple threads.\n        if getattr(settings, \"OIDC_CREATE_UNKNOWN_USER\", True):\n            args = {\n                UserModel.USERNAME_FIELD: username,\n                # defaults _will_ be updated, these are not fallbacks\n                \"defaults\": openid_data,\n            }\n            user, created = UserModel.objects.update_or_create(**args)\n            if created:\n                user = self.configure_user(user, **kwargs)\n        else:\n            try:\n                user = UserModel.objects.get_by_natural_key(username)\n            except UserModel.DoesNotExist:\n                return None\n        # run this callback for a each login\n        user.on_each_login()\n        return user\n\n    def clean_username(self, username):\n        \"\"\"\n        Performs any cleaning on the \"username\" prior to using it to get or\n        create the user object.  Returns the cleaned username.\n        \"\"\"\n        return username\n\n    def configure_user(self, user, **kwargs):\n        \"\"\"\n        Configures a user after creation and returns the updated user.\n        \"\"\"\n        user.set_unusable_password()\n        return user\n", "path": "src/djangooidc/backends.py"}, {"content": "import logging\n\nfrom django.contrib.auth.models import AbstractUser\nfrom django.db import models\n\nfrom .domain_invitation import DomainInvitation\nfrom .transition_domain import TransitionDomain\nfrom .domain_information import DomainInformation\nfrom .domain import Domain\n\nfrom phonenumber_field.modelfields import PhoneNumberField  # type: ignore\n\n\nlogger = logging.getLogger(__name__)\n\n\nclass User(AbstractUser):\n    \"\"\"\n    A custom user model that performs identically to the default user model\n    but can be customized later.\n    \"\"\"\n\n    # #### Constants for choice fields ####\n    RESTRICTED = \"restricted\"\n    STATUS_CHOICES = ((RESTRICTED, RESTRICTED),)\n\n    status = models.CharField(\n        max_length=10,\n        choices=STATUS_CHOICES,\n        default=None,  # Set the default value to None\n        null=True,  # Allow the field to be null\n        blank=True,  # Allow the field to be blank\n    )\n\n    domains = models.ManyToManyField(\n        \"registrar.Domain\",\n        through=\"registrar.UserDomainRole\",\n        related_name=\"users\",\n    )\n\n    phone = PhoneNumberField(\n        null=True,\n        blank=True,\n        help_text=\"Phone\",\n        db_index=True,\n    )\n\n    def __str__(self):\n        # this info is pulled from Login.gov\n        if self.first_name or self.last_name:\n            return f\"{self.first_name or ''} {self.last_name or ''} {self.email or ''}\"\n        elif self.email:\n            return self.email\n        else:\n            return self.username\n\n    def restrict_user(self):\n        self.status = self.RESTRICTED\n        self.save()\n\n    def unrestrict_user(self):\n        self.status = None\n        self.save()\n\n    def is_restricted(self):\n        return self.status == self.RESTRICTED\n\n    def check_domain_invitations_on_login(self):\n        \"\"\"When a user first arrives on the site, we need to retrieve any domain\n        invitations that match their email address.\"\"\"\n        for invitation in DomainInvitation.objects.filter(\n            email=self.email, status=DomainInvitation.INVITED\n        ):\n            try:\n                invitation.retrieve()\n                invitation.save()\n            except RuntimeError:\n                # retrieving should not fail because of a missing user, but\n                # if it does fail, log the error so a new user can continue\n                # logging in\n                logger.warn(\n                    \"Failed to retrieve invitation %s\", invitation, exc_info=True\n                )\n\n    def create_domain_and_invite(self, transition_domain: TransitionDomain):\n        transition_domain_name = transition_domain.domain_name\n        transition_domain_status = transition_domain.status\n        transition_domain_email = transition_domain.username\n\n        # type safety check.  name should never be none\n        if transition_domain_name is not None:\n            new_domain = Domain(\n                name=transition_domain_name, state=transition_domain_status\n            )\n            new_domain.save()\n            # check that a domain invitation doesn't already\n            # exist for this e-mail / Domain pair\n            domain_email_already_in_domain_invites = DomainInvitation.objects.filter(\n                email=transition_domain_email.lower(), domain=new_domain\n            ).exists()\n            if not domain_email_already_in_domain_invites:\n                # Create new domain invitation\n                new_domain_invitation = DomainInvitation(\n                    email=transition_domain_email.lower(), domain=new_domain\n                )\n                new_domain_invitation.save()\n\n    def check_transition_domains_on_login(self):\n        \"\"\"When a user first arrives on the site, we need to check\n        if they are logging in with the same e-mail as a\n        transition domain and update our database accordingly.\"\"\"\n\n        for transition_domain in TransitionDomain.objects.filter(username=self.email):\n            # Looks like the user logged in with the same e-mail as\n            # one or more corresponding transition domains.\n            # Create corresponding DomainInformation objects.\n\n            # NOTE: adding an ADMIN user role for this user\n            # for each domain should already be done\n            # in the invitation.retrieve() method.\n            # However, if the migration scripts for transition\n            # domain objects were not executed correctly,\n            # there could be transition domains without\n            # any corresponding Domain & DomainInvitation objects,\n            # which means the invitation.retrieve() method might\n            # not execute.\n            # Check that there is a corresponding domain object\n            # for this transition domain.  If not, we have an error\n            # with our data and migrations need to be run again.\n\n            # Get the domain that corresponds with this transition domain\n            domain_exists = Domain.objects.filter(\n                name=transition_domain.domain_name\n            ).exists()\n            if not domain_exists:\n                logger.warn(\n                    \"\"\"There are transition domains without\n                            corresponding domain objects!\n                            Please run migration scripts for transition domains\n                            (See data_migration.md)\"\"\"\n                )\n                # No need to throw an exception...just create a domain\n                # and domain invite, then proceed as normal\n                self.create_domain_and_invite(transition_domain)\n\n            domain = Domain.objects.get(name=transition_domain.domain_name)\n\n            # Create a domain information object, if one doesn't\n            # already exist\n            domain_info_exists = DomainInformation.objects.filter(\n                domain=domain\n            ).exists()\n            if not domain_info_exists:\n                new_domain_info = DomainInformation(creator=self, domain=domain)\n                new_domain_info.save()\n\n    def on_each_login(self):\n        \"\"\"Callback each time the user is authenticated.\n\n        When a user arrives on the site each time, we need to retrieve any domain\n        invitations that match their email address.\n\n        We also need to check if they are logging in with the same e-mail\n        as a transition domain and update our domainInfo objects accordingly.\n        \"\"\"\n\n        # PART 1: TRANSITION DOMAINS\n        #\n        # NOTE: THIS MUST RUN FIRST\n        # (If we have an issue where transition domains were\n        # not fully converted into Domain and DomainInvitation\n        # objects, this method will fill in the gaps.\n        # This will ensure the Domain Invitations method\n        # runs correctly (no missing invites))\n        self.check_transition_domains_on_login()\n\n        # PART 2: DOMAIN INVITATIONS\n        self.check_domain_invitations_on_login()\n\n    class Meta:\n        permissions = [\n            (\"analyst_access_permission\", \"Analyst Access Permission\"),\n            (\"full_access_permission\", \"Full Access Permission\"),\n        ]\n", "path": "src/registrar/models/user.py"}]}