-
-
Notifications
You must be signed in to change notification settings - Fork 177
fix: API endpoint hardening and JSON serialization fixes #1004
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
5981703
833e612
6463348
65f127e
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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, | ||
| } | ||
|
|
@@ -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) | ||
|
|
||
| # 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"], | ||
|
|
@@ -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( | ||
| { | ||
|
|
@@ -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, | ||
|
|
@@ -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) | ||
|
|
||
|
ankushchk marked this conversation as resolved.
|
||
| category = get_object_or_404(ForumCategory, id=data["category"]) | ||
|
Comment on lines
+2468
to
2480
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 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: 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 |
||
| topic = ForumTopic.objects.create( | ||
| title=data["title"], | ||
|
|
@@ -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, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Reject non-object JSON bodies before field validation.
json.loads()also accepts valid payloads like[],null, or"text". Those skip theJSONDecodeErrorbranch, anddata.get(...)on Line 2320 then raisesAttributeError, 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