1- import logging
2- from django .utils .timezone import now
3- from django .shortcuts import redirect
4-
5- from django .contrib .auth import logout
6- from django .utils .deprecation import MiddlewareMixin
7- from django .urls import reverse
8- from django .conf import settings
9- from django .middleware .locale import LocaleMiddleware
10- from django .utils import translation
11- logger = logging .getLogger ('admin_logout_logger' )
12-
13- class AutoLogoutMiddleware (MiddlewareMixin ):
14- """
15- Middleware to log out users (except superusers) when they leave the admin area.
16- """
17- def process_request (self , request ):
18- user = request .user
19- if user .is_authenticated :
20- is_admin_page = request .path .startswith ('/admin' )
21- if not user .is_superuser :
22- if not is_admin_page and request .session .get ('admin_session' ):
23- logout (request )
24- request .session .pop ('admin_session' , None )
25- logger .info (f"User { user .username } has been logged out after leaving the admin area." )
26- elif is_admin_page :
27- request .session ['admin_session' ] = True
28-
29-
30-
31-
32-
33- class LogRequestMiddleware :
34- """
35- Middleware to log IP address and accessed URL.
36- """
37-
38- def __init__ (self , get_response ):
39- self .get_response = get_response
40-
41- def __call__ (self , request ):
42- # Process the request
43- response = self .get_response (request )
44-
45- # Log the IP and page accessed
46- if request .user .is_authenticated :
47- session_ip = request .session .get ('ip_address' )
48- session_ua = request .session .get ('user_agent' )
49- session_token = request .session .get ('session_token' )
50- current_ip = self .get_client_ip (request )
51- current_ua = request .META .get ('HTTP_USER_AGENT' )
52-
53- # Skip hijacking checks for OAuth authentication paths to prevent redirect loops
54- is_oauth_path = (
55- request .path .startswith ('/complete/' ) or
56- request .path .startswith ('/oauth/' ) or
57- request .path == '/dashboard/' or
58- 'oauth' in request .path or
59- 'complete' in request .path or
60- 'azuread' in request .path
61- )
62-
63- # Check if user was authenticated via OAuth
64- is_oauth_user = request .session .get ('oauth_authenticated' , False )
65-
66- # Check if this is a social auth completion
67- is_social_auth = 'social' in request .path or 'complete' in request .path
68-
69- # If this is an OAuth path, dashboard, OAuth user, or social auth, set session data instead of checking
70- if is_oauth_path or is_oauth_user or is_social_auth :
71- # Set session data for OAuth users to prevent future hijacking checks
72- request .session ['ip_address' ] = current_ip
73- request .session ['user_agent' ] = current_ua
74- request .session ['session_token' ] = request .session .session_key
75- logger .info (f"OAuth/social auth path/user detected - setting session data for user { request .user .email } " )
76- else :
77- # Only perform hijacking checks for non-OAuth paths
78- # Check for IP address mismatch
79- if session_ip and session_ip != current_ip :
80- logger .warning (f"Session IP mismatch! Session IP: { session_ip } , Current IP: { current_ip } " )
81- request .session .flush ()
82- return redirect ('login' ) # Redirect to login
83-
84- # Check for User-Agent mismatch
85- if session_ua and session_ua != current_ua :
86- logger .warning (f"Session UA mismatch! Session UA: { session_ua } , Current UA: { current_ua } " )
87- request .session .flush ()
88- return redirect ('login' )
89-
90- # Check for session token mismatch
91- if session_token and session_token != request .session .session_key :
92- logger .warning (f"Session token mismatch! Session token: { session_token } , Current session ID: { request .session .session_key } " )
93- request .session .flush ()
94- return redirect ('login' )
95-
96- # Log the IP and accessed URL if no hijacking is detected
97- self .log_request (request )
98-
99- return response
100-
101- # Continue processing the request
102- return self .get_response (request )
103-
104-
105-
106- def log_request (self , request ):
107- ip = self .get_client_ip (request )
108- path = request .path
109-
110- # Log the information
111- logger .info (f"IP: { ip } accessed { path } " )
112- def get_client_ip (self , request ):
113- """
114- Extracts client IP address from request headers.
115- """
116- x_forwarded_for = request .META .get ('HTTP_X_FORWARDED_FOR' )
117- if x_forwarded_for :
118- ip = x_forwarded_for .split (',' )[0 ]
119- else :
120- ip = request .META .get ('REMOTE_ADDR' )
121- return ip
122-
123-
124-
125-
126- try :
127- from django .utils .translation import LANGUAGE_SESSION_KEY # Django 4+
128- except Exception :
129- LANGUAGE_SESSION_KEY = 'django_language'
130-
131- class LocaleMiddlewareDefaultEnglish (LocaleMiddleware ):
132- """
133- 1) If a language cookie or session exists => Respect the user's selected language
134- 2) If neither exists => Ignore the browser's Accept-Language setting and force settings.LANGUAGE_CODE (English)
135- """
136- def process_request (self , request ):
137- # check cookie/session
138- cookie_name = getattr (settings , "LANGUAGE_COOKIE_NAME" , "django_language" )
139- lang = request .COOKIES .get (cookie_name )
140-
141- if not lang and hasattr (request , "session" ):
142- lang = request .session .get (LANGUAGE_SESSION_KEY )
143-
144- if not lang :
145- # No user selection => Force default to English, ignore Accept-Language
146- lang = settings .LANGUAGE_CODE
147-
148- translation .activate (lang )
149- request .LANGUAGE_CODE = translation .get_language ()
150-
151-
1521class SecurityHeadersMiddleware :
1532 """
1543 Middleware to add security headers that help mitigate vulnerabilities
1554 in third-party JavaScript libraries and prevent common attacks.
1565 """
157-
1586 def __init__ (self , get_response ):
1597 self .get_response = get_response
1608
1619 def __call__ (self , request ):
16210 response = self .get_response (request )
163-
164- # Content Security Policy to prevent XSS and code injection
16511 response ['Content-Security-Policy' ] = (
16612 "default-src 'self'; "
167- "script-src 'self' 'unsafe-inline' 'unsafe-eval'; " # unsafe-eval needed for some dev tools
13+ "script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
16814 "style-src 'self' 'unsafe-inline'; "
16915 "img-src 'self' data: https:; "
17016 "font-src 'self' https:; "
17117 "connect-src 'self'; "
17218 "frame-ancestors 'none'; "
17319 "base-uri 'self';"
17420 )
175-
176- # Prevent clickjacking
17721 response ['X-Frame-Options' ] = 'DENY'
178-
179- # Prevent MIME type sniffing
18022 response ['X-Content-Type-Options' ] = 'nosniff'
181-
182- # Enable XSS protection
18323 response ['X-XSS-Protection' ] = '1; mode=block'
184-
185- # Prevent referrer leakage
18624 response ['Referrer-Policy' ] = 'strict-origin-when-cross-origin'
187-
188- # Permissions policy
18925 response ['Permissions-Policy' ] = (
19026 'geolocation=(), '
19127 'microphone=(), '
@@ -196,5 +32,49 @@ def __call__(self, request):
19632 'accelerometer=(), '
19733 'gyroscope=()'
19834 )
199-
200- return response
35+ return response
36+
37+ class HoneypotMiddleware :
38+ """
39+ Middleware to detect and log requests to honeypot (fake) paths.
40+ Normal users will never visit these paths.
41+ If triggered, log the attempt and return a 404 page.
42+ """
43+ HONEYPOT_PATHS = [
44+ "/admin-secret" ,
45+ "/Main-admin" ,
46+ "/superuser" ,
47+ "/dashboard-old" ,
48+ "/backup.sql" ,
49+ "/db.dump" ,
50+ "/config.php" ,
51+ "/env.bak" ,
52+ "/administration-login" ,
53+ "/auth-test" ,
54+ "/secure-login" ,
55+ "/test-page" ,
56+ "/debug/" ,
57+ "/old-site/"
58+ ]
59+
60+ def __init__ (self , get_response ):
61+ self .get_response = get_response
62+
63+ def __call__ (self , request ):
64+ path = request .path
65+ if path in self .HONEYPOT_PATHS :
66+ ip = self .get_client_ip (request )
67+ ua = request .META .get ("HTTP_USER_AGENT" , "unknown" )
68+ Honeypot_logger .warning (
69+ f"HONEYPOT TRIGGERED: Path={ path } , IP={ ip } , User-Agent={ ua } "
70+ )
71+ return render (request , "404.html" , status = 404 )
72+ return self .get_response (request )
73+
74+ def get_client_ip (self , request ):
75+ x_forwarded_for = request .META .get ("HTTP_X_FORWARDED_FOR" )
76+ if x_forwarded_for :
77+ ip = x_forwarded_for .split ("," )[0 ]
78+ else :
79+ ip = request .META .get ("REMOTE_ADDR" , "" )
80+ return ip
0 commit comments