Skip to content

Commit 0b4ea14

Browse files
committed
Closes #11489: Refactor & combine core middleware
1 parent 2381317 commit 0b4ea14

File tree

2 files changed

+62
-115
lines changed

2 files changed

+62
-115
lines changed

netbox/netbox/middleware.py

+60-109
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,73 @@
1414
from netbox.views import handler_500
1515
from utilities.api import is_api_request, rest_api_server_error
1616

17+
__all__ = (
18+
'CoreMiddleware',
19+
'RemoteUserMiddleware',
20+
)
21+
22+
23+
class CoreMiddleware:
1724

18-
class LoginRequiredMiddleware:
19-
"""
20-
If LOGIN_REQUIRED is True, redirect all non-authenticated users to the login page.
21-
"""
2225
def __init__(self, get_response):
2326
self.get_response = get_response
2427

2528
def __call__(self, request):
26-
# Redirect unauthenticated requests (except those exempted) to the login page if LOGIN_REQUIRED is true
27-
if settings.LOGIN_REQUIRED and not request.user.is_authenticated:
2829

29-
# Redirect unauthenticated requests
30-
if not request.path_info.startswith(settings.EXEMPT_PATHS):
31-
login_url = f'{settings.LOGIN_URL}?next={parse.quote(request.get_full_path_info())}'
32-
return HttpResponseRedirect(login_url)
30+
# Assign a random unique ID to the request. This will be used for change logging.
31+
request.id = uuid.uuid4()
32+
33+
# Enforce the LOGIN_REQUIRED config parameter. If true, redirect all non-exempt unauthenticated requests
34+
# to the login page.
35+
if (
36+
settings.LOGIN_REQUIRED and
37+
not request.user.is_authenticated and
38+
not request.path_info.startswith(settings.AUTH_EXEMPT_PATHS)
39+
):
40+
login_url = f'{settings.LOGIN_URL}?next={parse.quote(request.get_full_path_info())}'
41+
return HttpResponseRedirect(login_url)
42+
43+
# Enable the change_logging context manager and process the request.
44+
with change_logging(request):
45+
response = self.get_response(request)
46+
47+
# If this is an API request, attach an HTTP header annotating the API version (e.g. '3.5').
48+
if is_api_request(request):
49+
response['API-Version'] = settings.REST_FRAMEWORK_VERSION
50+
51+
# Clear any cached dynamic config parameters after each request.
52+
clear_config()
53+
54+
return response
3355

34-
return self.get_response(request)
56+
def process_exception(self, request, exception):
57+
"""
58+
Implement custom error handling logic for production deployments.
59+
"""
60+
# Don't catch exceptions when in debug mode
61+
if settings.DEBUG:
62+
return
63+
64+
# Cleanly handle exceptions that occur from REST API requests
65+
if is_api_request(request):
66+
return rest_api_server_error(request)
67+
68+
# Ignore Http404s (defer to Django's built-in 404 handling)
69+
if isinstance(exception, Http404):
70+
return
71+
72+
# Determine the type of exception. If it's a common issue, return a custom error page with instructions.
73+
custom_template = None
74+
if isinstance(exception, ProgrammingError):
75+
custom_template = 'exceptions/programming_error.html'
76+
elif isinstance(exception, ImportError):
77+
custom_template = 'exceptions/import_error.html'
78+
elif isinstance(exception, PermissionError):
79+
custom_template = 'exceptions/permission_error.html'
80+
81+
# Return a custom error message, or fall back to Django's default 500 error handling
82+
if custom_template:
83+
return handler_500(request, template_name=custom_template)
3584

3685

3786
class RemoteUserMiddleware(RemoteUserMiddleware_):
@@ -104,101 +153,3 @@ def _get_groups(self, request):
104153
groups = []
105154
logger.debug(f"Groups are {groups}")
106155
return groups
107-
108-
109-
class ObjectChangeMiddleware:
110-
"""
111-
This middleware performs three functions in response to an object being created, updated, or deleted:
112-
113-
1. Create an ObjectChange to reflect the modification to the object in the changelog.
114-
2. Enqueue any relevant webhooks.
115-
3. Increment the metric counter for the event type.
116-
117-
The post_save and post_delete signals are employed to catch object modifications, however changes are recorded a bit
118-
differently for each. Objects being saved are cached into thread-local storage for action *after* the response has
119-
completed. This ensures that serialization of the object is performed only after any related objects (e.g. tags)
120-
have been created. Conversely, deletions are acted upon immediately, so that the serialized representation of the
121-
object is recorded before it (and any related objects) are actually deleted from the database.
122-
"""
123-
124-
def __init__(self, get_response):
125-
self.get_response = get_response
126-
127-
def __call__(self, request):
128-
# Assign a random unique ID to the request. This will be used to associate multiple object changes made during
129-
# the same request.
130-
request.id = uuid.uuid4()
131-
132-
# Process the request with change logging enabled
133-
with change_logging(request):
134-
response = self.get_response(request)
135-
136-
return response
137-
138-
139-
class APIVersionMiddleware:
140-
"""
141-
If the request is for an API endpoint, include the API version as a response header.
142-
"""
143-
144-
def __init__(self, get_response):
145-
self.get_response = get_response
146-
147-
def __call__(self, request):
148-
response = self.get_response(request)
149-
if is_api_request(request):
150-
response['API-Version'] = settings.REST_FRAMEWORK_VERSION
151-
return response
152-
153-
154-
class DynamicConfigMiddleware:
155-
"""
156-
Store the cached NetBox configuration in thread-local storage for the duration of the request.
157-
"""
158-
def __init__(self, get_response):
159-
self.get_response = get_response
160-
161-
def __call__(self, request):
162-
response = self.get_response(request)
163-
clear_config()
164-
return response
165-
166-
167-
class ExceptionHandlingMiddleware:
168-
"""
169-
Intercept certain exceptions which are likely indicative of installation issues and provide helpful instructions
170-
to the user.
171-
"""
172-
173-
def __init__(self, get_response):
174-
self.get_response = get_response
175-
176-
def __call__(self, request):
177-
return self.get_response(request)
178-
179-
def process_exception(self, request, exception):
180-
181-
# Handle exceptions that occur from REST API requests
182-
# if is_api_request(request):
183-
# return rest_api_server_error(request)
184-
185-
# Don't catch exceptions when in debug mode
186-
if settings.DEBUG:
187-
return
188-
189-
# Ignore Http404s (defer to Django's built-in 404 handling)
190-
if isinstance(exception, Http404):
191-
return
192-
193-
# Determine the type of exception. If it's a common issue, return a custom error page with instructions.
194-
custom_template = None
195-
if isinstance(exception, ProgrammingError):
196-
custom_template = 'exceptions/programming_error.html'
197-
elif isinstance(exception, ImportError):
198-
custom_template = 'exceptions/import_error.html'
199-
elif isinstance(exception, PermissionError):
200-
custom_template = 'exceptions/permission_error.html'
201-
202-
# Return a custom error message, or fall back to Django's default 500 error handling
203-
if custom_template:
204-
return handler_500(request, template_name=custom_template)

netbox/netbox/settings.py

+2-6
Original file line numberDiff line numberDiff line change
@@ -358,12 +358,8 @@ def _setting(name, default=None):
358358
'django.contrib.messages.middleware.MessageMiddleware',
359359
'django.middleware.clickjacking.XFrameOptionsMiddleware',
360360
'django.middleware.security.SecurityMiddleware',
361-
'netbox.middleware.ExceptionHandlingMiddleware',
362361
'netbox.middleware.RemoteUserMiddleware',
363-
'netbox.middleware.LoginRequiredMiddleware',
364-
'netbox.middleware.DynamicConfigMiddleware',
365-
'netbox.middleware.APIVersionMiddleware',
366-
'netbox.middleware.ObjectChangeMiddleware',
362+
'netbox.middleware.CoreMiddleware',
367363
'django_prometheus.middleware.PrometheusAfterMiddleware',
368364
]
369365

@@ -448,7 +444,7 @@ def _setting(name, default=None):
448444
)
449445

450446
# All URLs starting with a string listed here are exempt from login enforcement
451-
EXEMPT_PATHS = (
447+
AUTH_EXEMPT_PATHS = (
452448
f'/{BASE_PATH}api/',
453449
f'/{BASE_PATH}graphql/',
454450
f'/{BASE_PATH}login/',

0 commit comments

Comments
 (0)