We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Installed Plugins NavMenuItem not adhering to `is_staff` permission
<!--
    NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.

    This form is only for reporting reproducible bugs. If you need assistance
    with Nautobot installation, or if you have a general question, please start a
    discussion instead: https://github.com/nautobot/nautobot/discussions

    Please describe the environment in which you are running Nautobot. Be sure
    that you are running an unmodified instance of the latest stable release
    before submitting a bug report, and that any plugins have been disabled.
-->
### Environment
* Python version: 3.8
* Nautobot version: 1.3.9

<!--
    Describe in detail the exact steps that someone else can take to reproduce
    this bug using the current stable release of Nautobot. Begin with the
    creation of any necessary database objects and call out every operation
    being performed explicitly. If reporting a bug in the REST API, be sure to
    reconstruct the raw HTTP request(s) being made: Don't rely on a client
    library such as pynautobot.
-->
Caused by #1844 

### Steps to Reproduce
1. Set user permissions to `is_active` and `is_staff` to true BUT with `is_superuser` to false
2. Attempt to browse to installed plugins from nav menu
3.

<!-- What did you expect to happen? -->
### Expected Behavior
`is_staff` provide visibility to menu item without requiring `is_superuser`

<!-- What happened instead? -->
### Observed Behavior
NavMenuItem is only present if user has `is_superuser`

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `nautobot/core/authentication.py`
Content:
```
1 import logging
2 from collections import defaultdict
3 
4 from django.conf import settings
5 from django.contrib.auth.backends import (
6     ModelBackend,
7     RemoteUserBackend as _RemoteUserBackend,
8 )
9 from django.contrib.auth.models import Group
10 from django.db.models import Q
11 
12 from nautobot.users.models import ObjectPermission
13 from nautobot.utilities.permissions import (
14     permission_is_exempt,
15     resolve_permission,
16     resolve_permission_ct,
17 )
18 
19 logger = logging.getLogger("nautobot.authentication")
20 
21 
22 class ObjectPermissionBackend(ModelBackend):
23     def get_all_permissions(self, user_obj, obj=None):
24         if not user_obj.is_active or user_obj.is_anonymous:
25             return dict()
26         if not hasattr(user_obj, "_object_perm_cache"):
27             user_obj._object_perm_cache = self.get_object_permissions(user_obj)
28         return user_obj._object_perm_cache
29 
30     def get_object_permissions(self, user_obj):
31         """
32         Return all permissions granted to the user by an ObjectPermission.
33         """
34         # Retrieve all assigned and enabled ObjectPermissions
35         object_permissions = ObjectPermission.objects.filter(
36             Q(users=user_obj) | Q(groups__user=user_obj), enabled=True
37         ).prefetch_related("object_types")
38 
39         # Create a dictionary mapping permissions to their constraints
40         perms = defaultdict(list)
41         for obj_perm in object_permissions:
42             for object_type in obj_perm.object_types.all():
43                 for action in obj_perm.actions:
44                     perm_name = f"{object_type.app_label}.{action}_{object_type.model}"
45                     perms[perm_name].extend(obj_perm.list_constraints())
46 
47         return perms
48 
49     def has_perm(self, user_obj, perm, obj=None):
50         if perm == "is_staff":
51             return user_obj.is_active and (user_obj.obj.is_staff or user_obj.is_superuser)
52 
53         app_label, action, model_name = resolve_permission(perm)
54 
55         if app_label == "users" and model_name == "admingroup":
56             perm = perm.replace("users", "auth").replace("admingroup", "group")
57 
58         # Superusers implicitly have all permissions
59         if user_obj.is_active and user_obj.is_superuser:
60             return True
61 
62         # Permission is exempt from enforcement (i.e. listed in EXEMPT_VIEW_PERMISSIONS)
63         if permission_is_exempt(perm):
64             return True
65 
66         # Handle inactive/anonymous users
67         if not user_obj.is_active or user_obj.is_anonymous:
68             return False
69 
70         # If no applicable ObjectPermissions have been created for this user/permission, deny permission
71         if perm not in self.get_all_permissions(user_obj):
72             return False
73 
74         # If no object has been specified, grant permission. (The presence of a permission in this set tells
75         # us that the user has permission for *some* objects, but not necessarily a specific object.)
76         if obj is None:
77             return True
78 
79         # Sanity check: Ensure that the requested permission applies to the specified object
80         model = obj._meta.model
81         if model._meta.label_lower != ".".join((app_label, model_name)):
82             raise ValueError(f"Invalid permission {perm} for model {model}")
83 
84         # Compile a query filter that matches all instances of the specified model
85         obj_perm_constraints = self.get_all_permissions(user_obj)[perm]
86         constraints = Q()
87         for perm_constraints in obj_perm_constraints:
88             if perm_constraints:
89                 constraints |= Q(**perm_constraints)
90             else:
91                 # Found ObjectPermission with null constraints; allow model-level access
92                 constraints = Q()
93                 break
94 
95         # Permission to perform the requested action on the object depends on whether the specified object matches
96         # the specified constraints. Note that this check is made against the *database* record representing the object,
97         # not the instance itself.
98         return model.objects.filter(constraints, pk=obj.pk).exists()
99 
100 
101 class RemoteUserBackend(_RemoteUserBackend):
102     """
103     Custom implementation of Django's RemoteUserBackend which provides configuration hooks for basic customization.
104     """
105 
106     @property
107     def create_unknown_user(self):
108         return settings.REMOTE_AUTH_AUTO_CREATE_USER
109 
110     def has_perm(self, user_obj, perm, obj=None):
111         return False
112 
113 
114 def assign_groups_to_user(user, groups=None):
115     """
116     Assign a specified user a given list of groups.
117 
118     :param user: The user to assign the permissions
119     :param group: A list of group names to add the user to
120     """
121     if groups is None:
122         groups = []
123     group_list = []
124     for name in groups:
125         try:
126             group_list.append(Group.objects.get(name=name))
127         except Group.DoesNotExist:
128             logging.error(f"Could not assign group {name} to remotely-authenticated user {user}: Group not found")
129     if group_list:
130         user.groups.add(*group_list)
131         logger.debug(f"Assigned groups to remotely-authenticated user {user}: {group_list}")
132 
133 
134 def assign_permissions_to_user(user, permissions=None):
135     """
136     Assign a specified user a given set of permissions.
137 
138     :param user: The user to assign the permissions
139     :param permissions: A dictionary of permissions, with the permission name <app_label>.<action>_<model> as the key and constraints as values
140     """
141     if permissions is None:
142         permissions = {}
143     permissions_list = []
144     for permission_name, constraints in permissions.items():
145         try:
146             object_type, action = resolve_permission_ct(permission_name)
147             # TODO: Merge multiple actions into a single ObjectPermission per content type
148             obj_perm = ObjectPermission(name=permission_name, actions=[action], constraints=constraints)
149             obj_perm.save()
150             obj_perm.users.add(user)
151             obj_perm.object_types.add(object_type)
152             permissions_list.append(permission_name)
153         except ValueError:
154             logging.error(
155                 f"Invalid permission name: '{permission_name}'. Permissions must be in the form "
156                 "<app>.<action>_<model>. (Example: dcim.add_site)"
157             )
158     if permissions_list:
159         logger.debug(f"Assigned permissions to remotely-authenticated user {user}: {permissions_list}")
160
```
--- 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/nautobot/core/authentication.py b/nautobot/core/authentication.py
--- a/nautobot/core/authentication.py
+++ b/nautobot/core/authentication.py
@@ -48,7 +48,7 @@
 
     def has_perm(self, user_obj, perm, obj=None):
         if perm == "is_staff":
-            return user_obj.is_active and (user_obj.obj.is_staff or user_obj.is_superuser)
+            return user_obj.is_active and (user_obj.is_staff or user_obj.is_superuser)
 
         app_label, action, model_name = resolve_permission(perm)

{"golden_diff": "diff --git a/nautobot/core/authentication.py b/nautobot/core/authentication.py\n--- a/nautobot/core/authentication.py\n+++ b/nautobot/core/authentication.py\n@@ -48,7 +48,7 @@\n \n     def has_perm(self, user_obj, perm, obj=None):\n         if perm == \"is_staff\":\n-            return user_obj.is_active and (user_obj.obj.is_staff or user_obj.is_superuser)\n+            return user_obj.is_active and (user_obj.is_staff or user_obj.is_superuser)\n \n         app_label, action, model_name = resolve_permission(perm)\n", "issue": "Installed Plugins NavMenuItem not adhering to `is_staff` permission\n<!--\r\n    NOTE: IF YOUR ISSUE DOES NOT FOLLOW THIS TEMPLATE, IT WILL BE CLOSED.\r\n\r\n    This form is only for reporting reproducible bugs. If you need assistance\r\n    with Nautobot installation, or if you have a general question, please start a\r\n    discussion instead: https://github.com/nautobot/nautobot/discussions\r\n\r\n    Please describe the environment in which you are running Nautobot. Be sure\r\n    that you are running an unmodified instance of the latest stable release\r\n    before submitting a bug report, and that any plugins have been disabled.\r\n-->\r\n### Environment\r\n* Python version: 3.8\r\n* Nautobot version: 1.3.9\r\n\r\n<!--\r\n    Describe in detail the exact steps that someone else can take to reproduce\r\n    this bug using the current stable release of Nautobot. Begin with the\r\n    creation of any necessary database objects and call out every operation\r\n    being performed explicitly. If reporting a bug in the REST API, be sure to\r\n    reconstruct the raw HTTP request(s) being made: Don't rely on a client\r\n    library such as pynautobot.\r\n-->\r\nCaused by #1844 \r\n\r\n### Steps to Reproduce\r\n1. Set user permissions to `is_active` and `is_staff` to true BUT with `is_superuser` to false\r\n2. Attempt to browse to installed plugins from nav menu\r\n3.\r\n\r\n<!-- What did you expect to happen? -->\r\n### Expected Behavior\r\n`is_staff` provide visibility to menu item without requiring `is_superuser`\r\n\r\n<!-- What happened instead? -->\r\n### Observed Behavior\r\nNavMenuItem is only present if user has `is_superuser`\n", "before_files": [{"content": "import logging\nfrom collections import defaultdict\n\nfrom django.conf import settings\nfrom django.contrib.auth.backends import (\n    ModelBackend,\n    RemoteUserBackend as _RemoteUserBackend,\n)\nfrom django.contrib.auth.models import Group\nfrom django.db.models import Q\n\nfrom nautobot.users.models import ObjectPermission\nfrom nautobot.utilities.permissions import (\n    permission_is_exempt,\n    resolve_permission,\n    resolve_permission_ct,\n)\n\nlogger = logging.getLogger(\"nautobot.authentication\")\n\n\nclass ObjectPermissionBackend(ModelBackend):\n    def get_all_permissions(self, user_obj, obj=None):\n        if not user_obj.is_active or user_obj.is_anonymous:\n            return dict()\n        if not hasattr(user_obj, \"_object_perm_cache\"):\n            user_obj._object_perm_cache = self.get_object_permissions(user_obj)\n        return user_obj._object_perm_cache\n\n    def get_object_permissions(self, user_obj):\n        \"\"\"\n        Return all permissions granted to the user by an ObjectPermission.\n        \"\"\"\n        # Retrieve all assigned and enabled ObjectPermissions\n        object_permissions = ObjectPermission.objects.filter(\n            Q(users=user_obj) | Q(groups__user=user_obj), enabled=True\n        ).prefetch_related(\"object_types\")\n\n        # Create a dictionary mapping permissions to their constraints\n        perms = defaultdict(list)\n        for obj_perm in object_permissions:\n            for object_type in obj_perm.object_types.all():\n                for action in obj_perm.actions:\n                    perm_name = f\"{object_type.app_label}.{action}_{object_type.model}\"\n                    perms[perm_name].extend(obj_perm.list_constraints())\n\n        return perms\n\n    def has_perm(self, user_obj, perm, obj=None):\n        if perm == \"is_staff\":\n            return user_obj.is_active and (user_obj.obj.is_staff or user_obj.is_superuser)\n\n        app_label, action, model_name = resolve_permission(perm)\n\n        if app_label == \"users\" and model_name == \"admingroup\":\n            perm = perm.replace(\"users\", \"auth\").replace(\"admingroup\", \"group\")\n\n        # Superusers implicitly have all permissions\n        if user_obj.is_active and user_obj.is_superuser:\n            return True\n\n        # Permission is exempt from enforcement (i.e. listed in EXEMPT_VIEW_PERMISSIONS)\n        if permission_is_exempt(perm):\n            return True\n\n        # Handle inactive/anonymous users\n        if not user_obj.is_active or user_obj.is_anonymous:\n            return False\n\n        # If no applicable ObjectPermissions have been created for this user/permission, deny permission\n        if perm not in self.get_all_permissions(user_obj):\n            return False\n\n        # If no object has been specified, grant permission. (The presence of a permission in this set tells\n        # us that the user has permission for *some* objects, but not necessarily a specific object.)\n        if obj is None:\n            return True\n\n        # Sanity check: Ensure that the requested permission applies to the specified object\n        model = obj._meta.model\n        if model._meta.label_lower != \".\".join((app_label, model_name)):\n            raise ValueError(f\"Invalid permission {perm} for model {model}\")\n\n        # Compile a query filter that matches all instances of the specified model\n        obj_perm_constraints = self.get_all_permissions(user_obj)[perm]\n        constraints = Q()\n        for perm_constraints in obj_perm_constraints:\n            if perm_constraints:\n                constraints |= Q(**perm_constraints)\n            else:\n                # Found ObjectPermission with null constraints; allow model-level access\n                constraints = Q()\n                break\n\n        # Permission to perform the requested action on the object depends on whether the specified object matches\n        # the specified constraints. Note that this check is made against the *database* record representing the object,\n        # not the instance itself.\n        return model.objects.filter(constraints, pk=obj.pk).exists()\n\n\nclass RemoteUserBackend(_RemoteUserBackend):\n    \"\"\"\n    Custom implementation of Django's RemoteUserBackend which provides configuration hooks for basic customization.\n    \"\"\"\n\n    @property\n    def create_unknown_user(self):\n        return settings.REMOTE_AUTH_AUTO_CREATE_USER\n\n    def has_perm(self, user_obj, perm, obj=None):\n        return False\n\n\ndef assign_groups_to_user(user, groups=None):\n    \"\"\"\n    Assign a specified user a given list of groups.\n\n    :param user: The user to assign the permissions\n    :param group: A list of group names to add the user to\n    \"\"\"\n    if groups is None:\n        groups = []\n    group_list = []\n    for name in groups:\n        try:\n            group_list.append(Group.objects.get(name=name))\n        except Group.DoesNotExist:\n            logging.error(f\"Could not assign group {name} to remotely-authenticated user {user}: Group not found\")\n    if group_list:\n        user.groups.add(*group_list)\n        logger.debug(f\"Assigned groups to remotely-authenticated user {user}: {group_list}\")\n\n\ndef assign_permissions_to_user(user, permissions=None):\n    \"\"\"\n    Assign a specified user a given set of permissions.\n\n    :param user: The user to assign the permissions\n    :param permissions: A dictionary of permissions, with the permission name <app_label>.<action>_<model> as the key and constraints as values\n    \"\"\"\n    if permissions is None:\n        permissions = {}\n    permissions_list = []\n    for permission_name, constraints in permissions.items():\n        try:\n            object_type, action = resolve_permission_ct(permission_name)\n            # TODO: Merge multiple actions into a single ObjectPermission per content type\n            obj_perm = ObjectPermission(name=permission_name, actions=[action], constraints=constraints)\n            obj_perm.save()\n            obj_perm.users.add(user)\n            obj_perm.object_types.add(object_type)\n            permissions_list.append(permission_name)\n        except ValueError:\n            logging.error(\n                f\"Invalid permission name: '{permission_name}'. Permissions must be in the form \"\n                \"<app>.<action>_<model>. (Example: dcim.add_site)\"\n            )\n    if permissions_list:\n        logger.debug(f\"Assigned permissions to remotely-authenticated user {user}: {permissions_list}\")\n", "path": "nautobot/core/authentication.py"}], "after_files": [{"content": "import logging\nfrom collections import defaultdict\n\nfrom django.conf import settings\nfrom django.contrib.auth.backends import (\n    ModelBackend,\n    RemoteUserBackend as _RemoteUserBackend,\n)\nfrom django.contrib.auth.models import Group\nfrom django.db.models import Q\n\nfrom nautobot.users.models import ObjectPermission\nfrom nautobot.utilities.permissions import (\n    permission_is_exempt,\n    resolve_permission,\n    resolve_permission_ct,\n)\n\nlogger = logging.getLogger(\"nautobot.authentication\")\n\n\nclass ObjectPermissionBackend(ModelBackend):\n    def get_all_permissions(self, user_obj, obj=None):\n        if not user_obj.is_active or user_obj.is_anonymous:\n            return dict()\n        if not hasattr(user_obj, \"_object_perm_cache\"):\n            user_obj._object_perm_cache = self.get_object_permissions(user_obj)\n        return user_obj._object_perm_cache\n\n    def get_object_permissions(self, user_obj):\n        \"\"\"\n        Return all permissions granted to the user by an ObjectPermission.\n        \"\"\"\n        # Retrieve all assigned and enabled ObjectPermissions\n        object_permissions = ObjectPermission.objects.filter(\n            Q(users=user_obj) | Q(groups__user=user_obj), enabled=True\n        ).prefetch_related(\"object_types\")\n\n        # Create a dictionary mapping permissions to their constraints\n        perms = defaultdict(list)\n        for obj_perm in object_permissions:\n            for object_type in obj_perm.object_types.all():\n                for action in obj_perm.actions:\n                    perm_name = f\"{object_type.app_label}.{action}_{object_type.model}\"\n                    perms[perm_name].extend(obj_perm.list_constraints())\n\n        return perms\n\n    def has_perm(self, user_obj, perm, obj=None):\n        if perm == \"is_staff\":\n            return user_obj.is_active and (user_obj.is_staff or user_obj.is_superuser)\n\n        app_label, action, model_name = resolve_permission(perm)\n\n        if app_label == \"users\" and model_name == \"admingroup\":\n            perm = perm.replace(\"users\", \"auth\").replace(\"admingroup\", \"group\")\n\n        # Superusers implicitly have all permissions\n        if user_obj.is_active and user_obj.is_superuser:\n            return True\n\n        # Permission is exempt from enforcement (i.e. listed in EXEMPT_VIEW_PERMISSIONS)\n        if permission_is_exempt(perm):\n            return True\n\n        # Handle inactive/anonymous users\n        if not user_obj.is_active or user_obj.is_anonymous:\n            return False\n\n        # If no applicable ObjectPermissions have been created for this user/permission, deny permission\n        if perm not in self.get_all_permissions(user_obj):\n            return False\n\n        # If no object has been specified, grant permission. (The presence of a permission in this set tells\n        # us that the user has permission for *some* objects, but not necessarily a specific object.)\n        if obj is None:\n            return True\n\n        # Sanity check: Ensure that the requested permission applies to the specified object\n        model = obj._meta.model\n        if model._meta.label_lower != \".\".join((app_label, model_name)):\n            raise ValueError(f\"Invalid permission {perm} for model {model}\")\n\n        # Compile a query filter that matches all instances of the specified model\n        obj_perm_constraints = self.get_all_permissions(user_obj)[perm]\n        constraints = Q()\n        for perm_constraints in obj_perm_constraints:\n            if perm_constraints:\n                constraints |= Q(**perm_constraints)\n            else:\n                # Found ObjectPermission with null constraints; allow model-level access\n                constraints = Q()\n                break\n\n        # Permission to perform the requested action on the object depends on whether the specified object matches\n        # the specified constraints. Note that this check is made against the *database* record representing the object,\n        # not the instance itself.\n        return model.objects.filter(constraints, pk=obj.pk).exists()\n\n\nclass RemoteUserBackend(_RemoteUserBackend):\n    \"\"\"\n    Custom implementation of Django's RemoteUserBackend which provides configuration hooks for basic customization.\n    \"\"\"\n\n    @property\n    def create_unknown_user(self):\n        return settings.REMOTE_AUTH_AUTO_CREATE_USER\n\n    def has_perm(self, user_obj, perm, obj=None):\n        return False\n\n\ndef assign_groups_to_user(user, groups=None):\n    \"\"\"\n    Assign a specified user a given list of groups.\n\n    :param user: The user to assign the permissions\n    :param group: A list of group names to add the user to\n    \"\"\"\n    if groups is None:\n        groups = []\n    group_list = []\n    for name in groups:\n        try:\n            group_list.append(Group.objects.get(name=name))\n        except Group.DoesNotExist:\n            logging.error(f\"Could not assign group {name} to remotely-authenticated user {user}: Group not found\")\n    if group_list:\n        user.groups.add(*group_list)\n        logger.debug(f\"Assigned groups to remotely-authenticated user {user}: {group_list}\")\n\n\ndef assign_permissions_to_user(user, permissions=None):\n    \"\"\"\n    Assign a specified user a given set of permissions.\n\n    :param user: The user to assign the permissions\n    :param permissions: A dictionary of permissions, with the permission name <app_label>.<action>_<model> as the key and constraints as values\n    \"\"\"\n    if permissions is None:\n        permissions = {}\n    permissions_list = []\n    for permission_name, constraints in permissions.items():\n        try:\n            object_type, action = resolve_permission_ct(permission_name)\n            # TODO: Merge multiple actions into a single ObjectPermission per content type\n            obj_perm = ObjectPermission(name=permission_name, actions=[action], constraints=constraints)\n            obj_perm.save()\n            obj_perm.users.add(user)\n            obj_perm.object_types.add(object_type)\n            permissions_list.append(permission_name)\n        except ValueError:\n            logging.error(\n                f\"Invalid permission name: '{permission_name}'. Permissions must be in the form \"\n                \"<app>.<action>_<model>. (Example: dcim.add_site)\"\n            )\n    if permissions_list:\n        logger.debug(f\"Assigned permissions to remotely-authenticated user {user}: {permissions_list}\")\n", "path": "nautobot/core/authentication.py"}]}