Skip to content

Commit 3e9c88a

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

23 files changed

Lines changed: 31830 additions & 29833 deletions

core/middleware.py

Lines changed: 56 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,12 @@
77
from django.urls import reverse
88
from django.conf import settings
99
from django.middleware.locale import LocaleMiddleware
10+
from django.http import HttpResponseNotFound
1011
from django.utils import translation
12+
from django.shortcuts import render
13+
1114
logger = logging.getLogger('admin_logout_logger')
15+
Honeypot_logger = logging.getLogger("honeypot_logger")
1216

1317
class AutoLogoutMiddleware(MiddlewareMixin):
1418
"""
@@ -27,9 +31,6 @@ def process_request(self, request):
2731
request.session['admin_session'] = True
2832

2933

30-
31-
32-
3334
class LogRequestMiddleware:
3435
"""
3536
Middleware to log IP address and accessed URL.
@@ -39,18 +40,15 @@ def __init__(self, get_response):
3940
self.get_response = get_response
4041

4142
def __call__(self, request):
42-
# Process the request
4343
response = self.get_response(request)
4444

45-
# Log the IP and page accessed
4645
if request.user.is_authenticated:
4746
session_ip = request.session.get('ip_address')
4847
session_ua = request.session.get('user_agent')
4948
session_token = request.session.get('session_token')
5049
current_ip = self.get_client_ip(request)
5150
current_ua = request.META.get('HTTP_USER_AGENT')
5251

53-
# Skip hijacking checks for OAuth authentication paths to prevent redirect loops
5452
is_oauth_path = (
5553
request.path.startswith('/complete/') or
5654
request.path.startswith('/oauth/') or
@@ -59,60 +57,38 @@ def __call__(self, request):
5957
'complete' in request.path or
6058
'azuread' in request.path
6159
)
62-
63-
# Check if user was authenticated via OAuth
6460
is_oauth_user = request.session.get('oauth_authenticated', False)
65-
66-
# Check if this is a social auth completion
6761
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
62+
7063
if is_oauth_path or is_oauth_user or is_social_auth:
71-
# Set session data for OAuth users to prevent future hijacking checks
7264
request.session['ip_address'] = current_ip
7365
request.session['user_agent'] = current_ua
7466
request.session['session_token'] = request.session.session_key
7567
logger.info(f"OAuth/social auth path/user detected - setting session data for user {request.user.email}")
7668
else:
77-
# Only perform hijacking checks for non-OAuth paths
78-
# Check for IP address mismatch
7969
if session_ip and session_ip != current_ip:
8070
logger.warning(f"Session IP mismatch! Session IP: {session_ip}, Current IP: {current_ip}")
8171
request.session.flush()
82-
return redirect('login') # Redirect to login
72+
return redirect('login')
8373

84-
# Check for User-Agent mismatch
8574
if session_ua and session_ua != current_ua:
8675
logger.warning(f"Session UA mismatch! Session UA: {session_ua}, Current UA: {current_ua}")
8776
request.session.flush()
8877
return redirect('login')
8978

90-
# Check for session token mismatch
9179
if session_token and session_token != request.session.session_key:
9280
logger.warning(f"Session token mismatch! Session token: {session_token}, Current session ID: {request.session.session_key}")
9381
request.session.flush()
9482
return redirect('login')
95-
96-
# Log the IP and accessed URL if no hijacking is detected
9783
self.log_request(request)
98-
9984
return response
10085

101-
# Continue processing the request
102-
return self.get_response(request)
103-
104-
105-
10686
def log_request(self, request):
10787
ip = self.get_client_ip(request)
10888
path = request.path
109-
110-
# Log the information
11189
logger.info(f"IP: {ip} accessed {path}")
90+
11291
def get_client_ip(self, request):
113-
"""
114-
Extracts client IP address from request headers.
115-
"""
11692
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
11793
if x_forwarded_for:
11894
ip = x_forwarded_for.split(',')[0]
@@ -121,8 +97,6 @@ def get_client_ip(self, request):
12197
return ip
12298

12399

124-
125-
126100
try:
127101
from django.utils.translation import LANGUAGE_SESSION_KEY # Django 4+
128102
except Exception:
@@ -134,15 +108,13 @@ class LocaleMiddlewareDefaultEnglish(LocaleMiddleware):
134108
2) If neither exists => Ignore the browser's Accept-Language setting and force settings.LANGUAGE_CODE (English)
135109
"""
136110
def process_request(self, request):
137-
# check cookie/session
138111
cookie_name = getattr(settings, "LANGUAGE_COOKIE_NAME", "django_language")
139112
lang = request.COOKIES.get(cookie_name)
140113

141114
if not lang and hasattr(request, "session"):
142115
lang = request.session.get(LANGUAGE_SESSION_KEY)
143116

144117
if not lang:
145-
# No user selection => Force default to English, ignore Accept-Language
146118
lang = settings.LANGUAGE_CODE
147119

148120
translation.activate(lang)
@@ -154,38 +126,25 @@ class SecurityHeadersMiddleware:
154126
Middleware to add security headers that help mitigate vulnerabilities
155127
in third-party JavaScript libraries and prevent common attacks.
156128
"""
157-
158129
def __init__(self, get_response):
159130
self.get_response = get_response
160131

161132
def __call__(self, request):
162133
response = self.get_response(request)
163-
164-
# Content Security Policy to prevent XSS and code injection
165134
response['Content-Security-Policy'] = (
166135
"default-src 'self'; "
167-
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; " # unsafe-eval needed for some dev tools
136+
"script-src 'self' 'unsafe-inline' 'unsafe-eval'; "
168137
"style-src 'self' 'unsafe-inline'; "
169138
"img-src 'self' data: https:; "
170139
"font-src 'self' https:; "
171140
"connect-src 'self'; "
172141
"frame-ancestors 'none'; "
173142
"base-uri 'self';"
174143
)
175-
176-
# Prevent clickjacking
177144
response['X-Frame-Options'] = 'DENY'
178-
179-
# Prevent MIME type sniffing
180145
response['X-Content-Type-Options'] = 'nosniff'
181-
182-
# Enable XSS protection
183146
response['X-XSS-Protection'] = '1; mode=block'
184-
185-
# Prevent referrer leakage
186147
response['Referrer-Policy'] = 'strict-origin-when-cross-origin'
187-
188-
# Permissions policy
189148
response['Permissions-Policy'] = (
190149
'geolocation=(), '
191150
'microphone=(), '
@@ -196,5 +155,51 @@ def __call__(self, request):
196155
'accelerometer=(), '
197156
'gyroscope=()'
198157
)
199-
200-
return response
158+
return response
159+
160+
class HoneypotMiddleware:
161+
"""
162+
Middleware to detect and log requests to honeypot (fake) paths.
163+
Normal users will never visit these paths.
164+
If triggered, log the attempt and return a 404 page.
165+
"""
166+
HONEYPOT_PATHS = [
167+
"/admin-secret",
168+
"/Main-admin",
169+
"/superuser",
170+
"/dashboard-old",
171+
"/backup.sql",
172+
"/db.dump",
173+
"/config.php",
174+
"/env.bak",
175+
"/administration-login",
176+
"/auth-test",
177+
"/secure-login",
178+
"/test-page",
179+
"/debug/",
180+
"/old-site/"
181+
]
182+
183+
def __init__(self, get_response):
184+
self.get_response = get_response
185+
186+
def __call__(self, request):
187+
path = request.path
188+
189+
if path in self.HONEYPOT_PATHS:
190+
ip = self.get_client_ip(request)
191+
ua = request.META.get("HTTP_USER_AGENT", "unknown")
192+
Honeypot_logger.warning(
193+
f"HONEYPOT TRIGGERED: Path={path}, IP={ip}, User-Agent={ua}"
194+
)
195+
return render(request, "404.html", status=404)
196+
197+
return self.get_response(request)
198+
199+
def get_client_ip(self, request):
200+
x_forwarded_for = request.META.get("HTTP_X_FORWARDED_FOR")
201+
if x_forwarded_for:
202+
ip = x_forwarded_for.split(",")[0]
203+
else:
204+
ip = request.META.get("REMOTE_ADDR", "")
205+
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: 15 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -13,26 +13,20 @@
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
18-
from django.urls import include, path
19-
from django.conf.urls.static import static
20-
from django.conf import settings
20+
from django.urls import include, path, re_path
2121
from django.contrib.auth import views as auth_views
22-
from django.conf import settings
23-
from django.conf.urls.static import static
2422
from home import views
2523
from rest_framework import permissions
2624
from home.views_securitytxt import security_txt
2725
from drf_yasg.views import get_schema_view
2826
from drf_yasg import openapi
29-
from django.urls import path, re_path
30-
from .admin import admin_statistics_view
31-
from home.views_securitytxt import security_txt
27+
from .admin import admin_statistics_view, admin_dashboard
3228
from home.views_robotstxt import robots_txt
3329

34-
from .admin import admin_dashboard
35-
3630
handler404 = 'home.views.error_404_view'
3731
schema_view = get_schema_view(
3832
openapi.Info(
@@ -69,15 +63,15 @@
6963
# blog
7064
path('blog/', views.blog, name='blog'),
7165
path('tinymce/', include('tinymce.urls')),
72-
# Custom Microsoft OAuth login and callback
73-
path('oauth/login/', views.microsoft_oauth_login, name='microsoft_oauth_login'),
74-
path('oauth/callback/', views.microsoft_oauth_callback, name='microsoft_oauth_callback'),
75-
# Override the social_django complete URL to use our custom handler (MUST be before social_django URLs)
76-
path('complete/azuread-tenant-oauth2/', views.microsoft_oauth_callback, name='microsoft_oauth_callback_override'),
77-
# Custom OAuth completion view for other backends
78-
path('complete/<str:backend>/', views.oauth_complete_redirect, name='oauth_complete_redirect'),
79-
# Python Social Auth URLs (Microsoft OAuth implementation)
80-
path('', include(('social_django.urls', 'social_django'), namespace='social')),
66+
# Custom Microsoft OAuth login and callback
67+
path('oauth/login/', views.microsoft_oauth_login, name='microsoft_oauth_login'),
68+
path('oauth/callback/', views.microsoft_oauth_callback, name='microsoft_oauth_callback'),
69+
# Override the social_django complete URL to use our custom handler (MUST be before social_django URLs)
70+
path('complete/azuread-tenant-oauth2/', views.microsoft_oauth_callback, name='microsoft_oauth_callback_override'),
71+
# Custom OAuth completion view for other backends
72+
path('complete/<str:backend>/', views.oauth_complete_redirect, name='oauth_complete_redirect'),
73+
# Python Social Auth URLs (Microsoft OAuth implementation)
74+
path('', include(('social_django.urls', 'social_django'), namespace='social')),
8175

8276
path("verifyEmail/", views.VerifyOTP, name="verifyEmail"),
8377
path("test-login/", views.test_login, name="test_login"),
@@ -88,37 +82,5 @@
8882
path('accounts/logout/', views.logout_view, name='logout'),
8983
path('accounts/register/', views.register, name='register'),
9084
path('accounts/registerclient/', views.register_client, name='register_client'),
91-
path('accounts/password-gen/', views.password_gen, name='password_gen'),
92-
path('accounts/password-change/', views.UserPasswordChangeView.as_view(), name='password_change'),
93-
path('accounts/password-change-done/', auth_views.PasswordChangeDoneView.as_view(
94-
template_name = 'accounts/password_change_done.html'
95-
), name='password_change_done'),
96-
path('accounts/password-reset/', views.UserPasswordResetView.as_view(), name='password_reset'),
97-
path('accounts/password-reset-done/', auth_views.PasswordResetDoneView.as_view(
98-
template_name='accounts/password_reset_done.html'
99-
), name='password_reset_done'),
100-
path('accounts/password-reset-confirm/<uidb64>/<token>/',
101-
views.UserPasswordResetConfirmView.as_view(), name='password_reset_confirm'),
102-
path('accounts/password-reset-complete/', auth_views.PasswordResetCompleteView.as_view(
103-
template_name='accounts/password_reset_complete.html'
104-
), name='password_reset_complete'),
105-
path('comphrensive-report', views.comphrehensive_reports, name='comphrehensive_report'),
106-
path('pen-testing', views.pen_testing, name='pen-testing'),
107-
path('secure-code-review', views.secure_code_review, name='secure-code-review'),
108-
path('dashboard/', views.dashboard, name='dashboard'),
109-
path('update_progress/<int:progress_id>/', views.update_progress, name='update_progress'),
110-
re_path(r'^swagger(?P<format>\.json|\.yaml)$', schema_view.without_ui(cache_timeout=0), name='schema-json'),
111-
path('swagger/', schema_view.with_ui('swagger', cache_timeout=0), name='schema-swagger-ui'),
112-
path('redoc/', schema_view.with_ui('redoc', cache_timeout=0), name='schema-redoc'),
113-
path('i18n/', include('django.conf.urls.i18n')),
114-
path('', include('home.urls')),
115-
path("api/tip/today/", views.tip_today, name="tip_today"),
116-
117-
118-
]
119-
120-
121-
122-
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')
123-
124-
85+
path('accounts/password-gen/', views.password_gen, name](#)
86+

0 commit comments

Comments
 (0)