Skip to content
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
63 changes: 56 additions & 7 deletions web/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -2293,7 +2293,7 @@ def api_course_list(request):
"description": course.description,
"teacher": course.teacher.username,
"price": str(course.price),
"subject": course.subject,
"subject": course.subject.name,
"level": course.level,
"slug": course.slug,
}
Expand All @@ -2309,7 +2309,34 @@ def api_course_create(request):
if request.method != "POST":
return JsonResponse({"error": "Only POST method is allowed"}, status=405)

data = json.loads(request.body)
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({"error": "Invalid JSON"}, status=400)
Comment on lines +2312 to +2315
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Reject non-object JSON bodies before field validation.

json.loads() also accepts valid payloads like [], null, or "text". Those skip the JSONDecodeError branch, and data.get(...) on Line 2320 then raises AttributeError, so this endpoint still 500s on malformed-but-valid JSON.

Suggested hardening
     try:
         data = json.loads(request.body)
     except json.JSONDecodeError:
         return JsonResponse({"error": "Invalid JSON"}, status=400)
+    if not isinstance(data, dict):
+        return JsonResponse({"error": "JSON body must be an object"}, status=400)

Also applies to: 2317-2322

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/views.py` around lines 2312 - 2315, After parsing request.body with
json.loads into data, ensure data is a JSON object (dict) before using
data.get(...) by rejecting non-object payloads (arrays, null, strings, numbers);
update the try/except block around json.loads to, on success, check
isinstance(data, dict) and return JsonResponse({"error": "JSON must be an
object"}, status=400) if not, so subsequent uses of data.get(...) are safe (look
for the json.loads(...) call and the variable data and the places that call
data.get(...) to apply this check).


# Required fields check (must be present and not empty)
required_fields = ["title", "description", "learning_objectives", "price", "max_students", "subject"]
for field in required_fields:
val = data.get(field)
if val is None or (isinstance(val, str) and not val.strip()):
return JsonResponse({"error": f"Missing or empty required field: {field}"}, status=400)

try:
subject = Subject.objects.get(id=data["subject"])
except (Subject.DoesNotExist, ValueError, TypeError):
return JsonResponse({"error": "Invalid subject ID"}, status=400)

# Normalize and validate level
level = data.get("level")
if level is None or (isinstance(level, str) and not level.strip()):
level = "beginner"
else:
level = str(level).strip().lower()

valid_levels = dict(Course._meta.get_field("level").choices).keys()
if level not in valid_levels:
return JsonResponse({"error": f"Invalid level. Must be one of: {', '.join(valid_levels)}"}, status=400)

course = Course.objects.create(
teacher=request.user,
title=data["title"],
Expand All @@ -2318,8 +2345,8 @@ def api_course_create(request):
prerequisites=data.get("prerequisites", ""),
price=data["price"],
max_students=data["max_students"],
subject=data["subject"],
level=data["level"],
subject=subject,
level=level,
)
return JsonResponse(
{
Expand All @@ -2341,7 +2368,7 @@ def api_course_detail(request, slug):
"description": course.description,
"teacher": course.teacher.username,
"price": str(course.price),
"subject": course.subject,
"subject": course.subject.name,
"level": course.level,
"prerequisites": course.prerequisites,
"learning_objectives": course.learning_objectives,
Expand Down Expand Up @@ -2438,7 +2465,18 @@ def api_forum_topic_create(request):
if request.method != "POST":
return JsonResponse({"error": "Only POST method is allowed"}, status=405)

data = json.loads(request.body)
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({"error": "Invalid JSON"}, status=400)

# Required fields check (must be present and not empty)
required_fields = ["title", "content", "category"]
for field in required_fields:
val = data.get(field)
if val is None or (isinstance(val, str) and not val.strip()):
return JsonResponse({"error": f"Missing or empty required field: {field}"}, status=400)

Comment thread
ankushchk marked this conversation as resolved.
category = get_object_or_404(ForumCategory, id=data["category"])
Comment on lines +2468 to 2480
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Return JSON validation errors for malformed forum payloads and bad relation IDs.

These two endpoints still have two malformed-input paths that bypass the new hardening: json.loads("[]")/null makes data.get(...) fail, and the lookups on Lines 2480 and 2514 can still return an HTML 404 page or bubble a conversion error instead of a JSON error response.

Suggested fix pattern for both endpoints
     try:
         data = json.loads(request.body)
     except json.JSONDecodeError:
         return JsonResponse({"error": "Invalid JSON"}, status=400)
+    if not isinstance(data, dict):
+        return JsonResponse({"error": "JSON body must be an object"}, status=400)
@@
-    category = get_object_or_404(ForumCategory, id=data["category"])
+    try:
+        category_id = int(data["category"])
+        if isinstance(data["category"], bool):
+            raise TypeError
+        category = ForumCategory.objects.get(id=category_id)
+    except (TypeError, ValueError, ForumCategory.DoesNotExist):
+        return JsonResponse({"error": "Invalid category ID"}, status=400)
     try:
         data = json.loads(request.body)
     except json.JSONDecodeError:
         return JsonResponse({"error": "Invalid JSON"}, status=400)
+    if not isinstance(data, dict):
+        return JsonResponse({"error": "JSON body must be an object"}, status=400)
@@
-    topic = get_object_or_404(ForumTopic, id=data["topic"])
+    try:
+        topic_id = int(data["topic"])
+        if isinstance(data["topic"], bool):
+            raise TypeError
+        topic = ForumTopic.objects.get(id=topic_id)
+    except (TypeError, ValueError, ForumTopic.DoesNotExist):
+        return JsonResponse({"error": "Invalid topic ID"}, status=400)

Also applies to: 2502-2514

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@web/views.py` around lines 2468 - 2480, The handler currently assumes
json.loads(request.body) yields a dict and that get_object_or_404(ForumCategory,
id=data["category"]) will always succeed; first validate that the parsed data is
a dict (return JsonResponse with a clear error if it's a list/null/primitive)
before accessing data.get(...) to avoid attribute errors, and when resolving
relations (ForumCategory) replace or wrap get_object_or_404 with an explicit
lookup that catches ValueError (bad id types) and ForumCategory.DoesNotExist (or
return JsonResponse on failure) so the endpoint always returns JSON validation
errors instead of HTML 404s or uncaught exceptions (update the code paths around
json.loads, the data variable usage, and the ForumCategory lookup).

topic = ForumTopic.objects.create(
title=data["title"],
Expand All @@ -2461,7 +2499,18 @@ def api_forum_reply_create(request):
if request.method != "POST":
return JsonResponse({"error": "Only POST method is allowed"}, status=405)

data = json.loads(request.body)
try:
data = json.loads(request.body)
except json.JSONDecodeError:
return JsonResponse({"error": "Invalid JSON"}, status=400)

# Required fields check (must be present and not empty)
required_fields = ["topic", "content"]
for field in required_fields:
val = data.get(field)
if val is None or (isinstance(val, str) and not val.strip()):
return JsonResponse({"error": f"Missing or empty required field: {field}"}, status=400)

topic = get_object_or_404(ForumTopic, id=data["topic"])
reply = ForumReply.objects.create(
topic=topic,
Expand Down