We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Don't fetch LDAP user and groups on all API request when FIND_GROUP_PERMS is disabled
### NetBox version

V3.0.9

### Feature type

Change to existing functionality

### Proposed functionality

Currently when using the LDAP backend for authentication, the AD is queried on every API request, regardless of other settings and regardless if the user is local or has been created by the LDAP backend. Additionally the LDAP cache built into django-auth-ldap does not function when using populate_user.

As the user is not actually authenticated against the AD when using the API (the token is used), I propose that the local user and it's group assignments are used when FIND_GROUP_PERMISSIONS is disabled.

I have a change ready for pull request if the issue is accepted.

For more info, please see the discussion I created: https://github.com/netbox-community/netbox/discussions/7708

This issue would also partly fix #6926 - it will not fix the caching, but the user who reported the issue is not using FIND_GROUP_PERMISSIONS.

### Use case

The end goal is vastly improved API performance when using the LDAP backend in most cases.

The above changes will result in the following changes for users:

**Not using the LDAP backend:**

No changes

**FIND_GROUP_PERMS  = True:**

No changes

**MIRROR_GROUPS = True and FIND_GROUP_PERMS = True:**

No changes

**MIRROR_GROUPS = True and FIND_GROUP_PERMS = False:**

Local user and group assignments will be used when calling the API and the user and groups are never reloaded from the LDAP server during API calls. This means that LDAP users utilizing the API will have to login to the web ui to update group memberships. The change also allows one to use locally created users to call the API with querying the LDAP server.

**MIRROR_GROUPS = False and FIND_GROUP_PERMS = False:**

The user performing the API request has to be locally assigned groups or have local user object permissions.

### Database changes

No database changes

### External dependencies

_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: `netbox/netbox/api/authentication.py`
Content:
```
1 from django.conf import settings
2 from rest_framework import authentication, exceptions
3 from rest_framework.permissions import BasePermission, DjangoObjectPermissions, SAFE_METHODS
4 
5 from users.models import Token
6 
7 
8 class TokenAuthentication(authentication.TokenAuthentication):
9     """
10     A custom authentication scheme which enforces Token expiration times.
11     """
12     model = Token
13 
14     def authenticate_credentials(self, key):
15         model = self.get_model()
16         try:
17             token = model.objects.prefetch_related('user').get(key=key)
18         except model.DoesNotExist:
19             raise exceptions.AuthenticationFailed("Invalid token")
20 
21         # Enforce the Token's expiration time, if one has been set.
22         if token.is_expired:
23             raise exceptions.AuthenticationFailed("Token expired")
24 
25         if not token.user.is_active:
26             raise exceptions.AuthenticationFailed("User inactive")
27 
28         # When LDAP authentication is active try to load user data from LDAP directory
29         if settings.REMOTE_AUTH_BACKEND == 'netbox.authentication.LDAPBackend':
30             from netbox.authentication import LDAPBackend
31             ldap_backend = LDAPBackend()
32             user = ldap_backend.populate_user(token.user.username)
33             # If the user is found in the LDAP directory use it, if not fallback to the local user
34             if user:
35                 return user, token
36 
37         return token.user, token
38 
39 
40 class TokenPermissions(DjangoObjectPermissions):
41     """
42     Custom permissions handler which extends the built-in DjangoModelPermissions to validate a Token's write ability
43     for unsafe requests (POST/PUT/PATCH/DELETE).
44     """
45     # Override the stock perm_map to enforce view permissions
46     perms_map = {
47         'GET': ['%(app_label)s.view_%(model_name)s'],
48         'OPTIONS': [],
49         'HEAD': ['%(app_label)s.view_%(model_name)s'],
50         'POST': ['%(app_label)s.add_%(model_name)s'],
51         'PUT': ['%(app_label)s.change_%(model_name)s'],
52         'PATCH': ['%(app_label)s.change_%(model_name)s'],
53         'DELETE': ['%(app_label)s.delete_%(model_name)s'],
54     }
55 
56     def __init__(self):
57 
58         # LOGIN_REQUIRED determines whether read-only access is provided to anonymous users.
59         self.authenticated_users_only = settings.LOGIN_REQUIRED
60 
61         super().__init__()
62 
63     def _verify_write_permission(self, request):
64 
65         # If token authentication is in use, verify that the token allows write operations (for unsafe methods).
66         if request.method in SAFE_METHODS or request.auth.write_enabled:
67             return True
68 
69     def has_permission(self, request, view):
70 
71         # Enforce Token write ability
72         if isinstance(request.auth, Token) and not self._verify_write_permission(request):
73             return False
74 
75         return super().has_permission(request, view)
76 
77     def has_object_permission(self, request, view, obj):
78 
79         # Enforce Token write ability
80         if isinstance(request.auth, Token) and not self._verify_write_permission(request):
81             return False
82 
83         return super().has_object_permission(request, view, obj)
84 
85 
86 class IsAuthenticatedOrLoginNotRequired(BasePermission):
87     """
88     Returns True if the user is authenticated or LOGIN_REQUIRED is False.
89     """
90     def has_permission(self, request, view):
91         if not settings.LOGIN_REQUIRED:
92             return True
93         return request.user.is_authenticated
94
```
--- 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/netbox/netbox/api/authentication.py b/netbox/netbox/api/authentication.py
--- a/netbox/netbox/api/authentication.py
+++ b/netbox/netbox/api/authentication.py
@@ -29,10 +29,13 @@
         if settings.REMOTE_AUTH_BACKEND == 'netbox.authentication.LDAPBackend':
             from netbox.authentication import LDAPBackend
             ldap_backend = LDAPBackend()
-            user = ldap_backend.populate_user(token.user.username)
-            # If the user is found in the LDAP directory use it, if not fallback to the local user
-            if user:
-                return user, token
+
+            # Load from LDAP if FIND_GROUP_PERMS is active
+            if ldap_backend.settings.FIND_GROUP_PERMS:
+                user = ldap_backend.populate_user(token.user.username)
+                # If the user is found in the LDAP directory use it, if not fallback to the local user
+                if user:
+                    return user, token
 
         return token.user, token

{"golden_diff": "diff --git a/netbox/netbox/api/authentication.py b/netbox/netbox/api/authentication.py\n--- a/netbox/netbox/api/authentication.py\n+++ b/netbox/netbox/api/authentication.py\n@@ -29,10 +29,13 @@\n         if settings.REMOTE_AUTH_BACKEND == 'netbox.authentication.LDAPBackend':\n             from netbox.authentication import LDAPBackend\n             ldap_backend = LDAPBackend()\n-            user = ldap_backend.populate_user(token.user.username)\n-            # If the user is found in the LDAP directory use it, if not fallback to the local user\n-            if user:\n-                return user, token\n+\n+            # Load from LDAP if FIND_GROUP_PERMS is active\n+            if ldap_backend.settings.FIND_GROUP_PERMS:\n+                user = ldap_backend.populate_user(token.user.username)\n+                # If the user is found in the LDAP directory use it, if not fallback to the local user\n+                if user:\n+                    return user, token\n \n         return token.user, token\n", "issue": "Don't fetch LDAP user and groups on all API request when FIND_GROUP_PERMS is disabled\n### NetBox version\n\nV3.0.9\n\n### Feature type\n\nChange to existing functionality\n\n### Proposed functionality\n\nCurrently when using the LDAP backend for authentication, the AD is queried on every API request, regardless of other settings and regardless if the user is local or has been created by the LDAP backend. Additionally the LDAP cache built into django-auth-ldap does not function when using populate_user.\r\n\r\nAs the user is not actually authenticated against the AD when using the API (the token is used), I propose that the local user and it's group assignments are used when FIND_GROUP_PERMISSIONS is disabled.\r\n\r\nI have a change ready for pull request if the issue is accepted.\r\n\r\nFor more info, please see the discussion I created: https://github.com/netbox-community/netbox/discussions/7708\r\n\r\nThis issue would also partly fix #6926 - it will not fix the caching, but the user who reported the issue is not using FIND_GROUP_PERMISSIONS.\n\n### Use case\n\nThe end goal is vastly improved API performance when using the LDAP backend in most cases.\r\n\r\nThe above changes will result in the following changes for users:\r\n\r\n**Not using the LDAP backend:**\r\n\r\nNo changes\r\n\r\n**FIND_GROUP_PERMS  = True:**\r\n\r\nNo changes\r\n\r\n**MIRROR_GROUPS = True and FIND_GROUP_PERMS = True:**\r\n\r\nNo changes\r\n\r\n**MIRROR_GROUPS = True and FIND_GROUP_PERMS = False:**\r\n\r\nLocal user and group assignments will be used when calling the API and the user and groups are never reloaded from the LDAP server during API calls. This means that LDAP users utilizing the API will have to login to the web ui to update group memberships. The change also allows one to use locally created users to call the API with querying the LDAP server.\r\n\r\n**MIRROR_GROUPS = False and FIND_GROUP_PERMS = False:**\r\n\r\nThe user performing the API request has to be locally assigned groups or have local user object permissions.\n\n### Database changes\n\nNo database changes\n\n### External dependencies\n\n_No response_\n", "before_files": [{"content": "from django.conf import settings\nfrom rest_framework import authentication, exceptions\nfrom rest_framework.permissions import BasePermission, DjangoObjectPermissions, SAFE_METHODS\n\nfrom users.models import Token\n\n\nclass TokenAuthentication(authentication.TokenAuthentication):\n    \"\"\"\n    A custom authentication scheme which enforces Token expiration times.\n    \"\"\"\n    model = Token\n\n    def authenticate_credentials(self, key):\n        model = self.get_model()\n        try:\n            token = model.objects.prefetch_related('user').get(key=key)\n        except model.DoesNotExist:\n            raise exceptions.AuthenticationFailed(\"Invalid token\")\n\n        # Enforce the Token's expiration time, if one has been set.\n        if token.is_expired:\n            raise exceptions.AuthenticationFailed(\"Token expired\")\n\n        if not token.user.is_active:\n            raise exceptions.AuthenticationFailed(\"User inactive\")\n\n        # When LDAP authentication is active try to load user data from LDAP directory\n        if settings.REMOTE_AUTH_BACKEND == 'netbox.authentication.LDAPBackend':\n            from netbox.authentication import LDAPBackend\n            ldap_backend = LDAPBackend()\n            user = ldap_backend.populate_user(token.user.username)\n            # If the user is found in the LDAP directory use it, if not fallback to the local user\n            if user:\n                return user, token\n\n        return token.user, token\n\n\nclass TokenPermissions(DjangoObjectPermissions):\n    \"\"\"\n    Custom permissions handler which extends the built-in DjangoModelPermissions to validate a Token's write ability\n    for unsafe requests (POST/PUT/PATCH/DELETE).\n    \"\"\"\n    # Override the stock perm_map to enforce view permissions\n    perms_map = {\n        'GET': ['%(app_label)s.view_%(model_name)s'],\n        'OPTIONS': [],\n        'HEAD': ['%(app_label)s.view_%(model_name)s'],\n        'POST': ['%(app_label)s.add_%(model_name)s'],\n        'PUT': ['%(app_label)s.change_%(model_name)s'],\n        'PATCH': ['%(app_label)s.change_%(model_name)s'],\n        'DELETE': ['%(app_label)s.delete_%(model_name)s'],\n    }\n\n    def __init__(self):\n\n        # LOGIN_REQUIRED determines whether read-only access is provided to anonymous users.\n        self.authenticated_users_only = settings.LOGIN_REQUIRED\n\n        super().__init__()\n\n    def _verify_write_permission(self, request):\n\n        # If token authentication is in use, verify that the token allows write operations (for unsafe methods).\n        if request.method in SAFE_METHODS or request.auth.write_enabled:\n            return True\n\n    def has_permission(self, request, view):\n\n        # Enforce Token write ability\n        if isinstance(request.auth, Token) and not self._verify_write_permission(request):\n            return False\n\n        return super().has_permission(request, view)\n\n    def has_object_permission(self, request, view, obj):\n\n        # Enforce Token write ability\n        if isinstance(request.auth, Token) and not self._verify_write_permission(request):\n            return False\n\n        return super().has_object_permission(request, view, obj)\n\n\nclass IsAuthenticatedOrLoginNotRequired(BasePermission):\n    \"\"\"\n    Returns True if the user is authenticated or LOGIN_REQUIRED is False.\n    \"\"\"\n    def has_permission(self, request, view):\n        if not settings.LOGIN_REQUIRED:\n            return True\n        return request.user.is_authenticated\n", "path": "netbox/netbox/api/authentication.py"}], "after_files": [{"content": "from django.conf import settings\nfrom rest_framework import authentication, exceptions\nfrom rest_framework.permissions import BasePermission, DjangoObjectPermissions, SAFE_METHODS\n\nfrom users.models import Token\n\n\nclass TokenAuthentication(authentication.TokenAuthentication):\n    \"\"\"\n    A custom authentication scheme which enforces Token expiration times.\n    \"\"\"\n    model = Token\n\n    def authenticate_credentials(self, key):\n        model = self.get_model()\n        try:\n            token = model.objects.prefetch_related('user').get(key=key)\n        except model.DoesNotExist:\n            raise exceptions.AuthenticationFailed(\"Invalid token\")\n\n        # Enforce the Token's expiration time, if one has been set.\n        if token.is_expired:\n            raise exceptions.AuthenticationFailed(\"Token expired\")\n\n        if not token.user.is_active:\n            raise exceptions.AuthenticationFailed(\"User inactive\")\n\n        # When LDAP authentication is active try to load user data from LDAP directory\n        if settings.REMOTE_AUTH_BACKEND == 'netbox.authentication.LDAPBackend':\n            from netbox.authentication import LDAPBackend\n            ldap_backend = LDAPBackend()\n\n            # Load from LDAP if FIND_GROUP_PERMS is active\n            if ldap_backend.settings.FIND_GROUP_PERMS:\n                user = ldap_backend.populate_user(token.user.username)\n                # If the user is found in the LDAP directory use it, if not fallback to the local user\n                if user:\n                    return user, token\n\n        return token.user, token\n\n\nclass TokenPermissions(DjangoObjectPermissions):\n    \"\"\"\n    Custom permissions handler which extends the built-in DjangoModelPermissions to validate a Token's write ability\n    for unsafe requests (POST/PUT/PATCH/DELETE).\n    \"\"\"\n    # Override the stock perm_map to enforce view permissions\n    perms_map = {\n        'GET': ['%(app_label)s.view_%(model_name)s'],\n        'OPTIONS': [],\n        'HEAD': ['%(app_label)s.view_%(model_name)s'],\n        'POST': ['%(app_label)s.add_%(model_name)s'],\n        'PUT': ['%(app_label)s.change_%(model_name)s'],\n        'PATCH': ['%(app_label)s.change_%(model_name)s'],\n        'DELETE': ['%(app_label)s.delete_%(model_name)s'],\n    }\n\n    def __init__(self):\n\n        # LOGIN_REQUIRED determines whether read-only access is provided to anonymous users.\n        self.authenticated_users_only = settings.LOGIN_REQUIRED\n\n        super().__init__()\n\n    def _verify_write_permission(self, request):\n\n        # If token authentication is in use, verify that the token allows write operations (for unsafe methods).\n        if request.method in SAFE_METHODS or request.auth.write_enabled:\n            return True\n\n    def has_permission(self, request, view):\n\n        # Enforce Token write ability\n        if isinstance(request.auth, Token) and not self._verify_write_permission(request):\n            return False\n\n        return super().has_permission(request, view)\n\n    def has_object_permission(self, request, view, obj):\n\n        # Enforce Token write ability\n        if isinstance(request.auth, Token) and not self._verify_write_permission(request):\n            return False\n\n        return super().has_object_permission(request, view, obj)\n\n\nclass IsAuthenticatedOrLoginNotRequired(BasePermission):\n    \"\"\"\n    Returns True if the user is authenticated or LOGIN_REQUIRED is False.\n    \"\"\"\n    def has_permission(self, request, view):\n        if not settings.LOGIN_REQUIRED:\n            return True\n        return request.user.is_authenticated\n", "path": "netbox/netbox/api/authentication.py"}]}