Skip to content

Commit f6dff4d

Browse files
authored
Merge branch 'main' into feature/cyber-challenges-quiz-compiler-clean
2 parents a5974ae + 95243c4 commit f6dff4d

23 files changed

Lines changed: 31837 additions & 28047 deletions

core/middleware.py

Lines changed: 47 additions & 167 deletions
Original file line numberDiff line numberDiff line change
@@ -1,191 +1,27 @@
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-
1521
class 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

core/settings.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@
156156
# 'core.middleware.AutoLogoutMiddleware', # TEMPORARILY DISABLED - causing OAuth redirect issues
157157

158158
"django_user_agents.middleware.UserAgentMiddleware",
159+
"core.middleware.HoneypotMiddleware",
159160
# "core.middleware.LogRequestMiddleware", # TEMPORARILY DISABLED - causing OAuth redirect issues
160161
# "home.views.force_oauth_redirect_middleware", # DISABLED - causing redirect loops
161162

@@ -529,6 +530,11 @@
529530
'level': 'INFO',
530531
'propagate': False,
531532
},
533+
"honeypot_logger": {
534+
"handlers": ["console"],
535+
"level": "WARNING",
536+
"propagate": False,
537+
},
532538
},
533539
}
534540

core/urls.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
1. Import the include() function: from django.urls import include, path
1414
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
1515
"""
16+
from django.conf import settings
17+
from django.conf.urls.static import static
1618
from django.contrib import admin
1719
from django.conf.urls.i18n import i18n_patterns
1820
from django.urls import include, path
@@ -121,4 +123,9 @@
121123

122124
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT) + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT) + static('/static/assets/', document_root=settings.BASE_DIR / 'custom_static/assets')
123125

126+
]
124127

128+
# Static & media files (served by Django in dev)
129+
urlpatterns += static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)
130+
urlpatterns += static('/static/assets/', document_root=settings.BASE_DIR / 'custom_static/assets')
131+
urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)

0 commit comments

Comments
 (0)