Security review fixes: admin permissions, guest sessions, payment edge cases, assistant leaks
- Admin: refund/cancel/mark-resolved/Bazaar-link actions need change permission; question and curriculum import need add/delete; content browser needs view_question; guest device ids are shown to superusers only. - Accounts: registering a guest's phone revokes the guest's earlier sessions; self-deletion keeps Redemption, UserOffer and the phone's OTP rows; the public demo guest cannot be deleted (DEMO_GUEST_DEVICE_IDS); display-name contact filter covers any separator and social handles. - Payment: callback without Authority is 400; a second Bazaar purchase on a paid order is 409; orders snapshot credits/hearts/days at creation (payment.0011). - Assistant: a broken diagram no longer makes a streamed answer free; a late off-topic marker refuses the answer (not stored, not cached); a disconnect saves the partial answer; only KNOWN_COLLECTIONS are searched. - Infra: secret-key file is created atomically (workers no longer split keys); no default INTERNAL_SERVICE_KEY; Kavenegar key and Bazaar token kept out of error logs; Swagger/ReDoc pinned; schema throttled; docker compose bound to 127.0.0.1; non-ASCII digit ids are 400; ticket text capped at 5000. - docs/security-before-production.md: remaining open items 24-31 and the guard tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
import json
|
||||
|
||||
from django.contrib import admin, messages
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db.models import Count, Q
|
||||
from django.http import HttpResponse
|
||||
from django.shortcuts import redirect, render
|
||||
@@ -54,6 +55,9 @@ class GradeAdmin(admin.ModelAdmin):
|
||||
return response
|
||||
|
||||
def import_curriculum_view(self, request):
|
||||
# admin_view فقط is_staff را میسنجد؛ این صفحه پایه/فصل/درس میسازد و متنِ درس را عوض میکند.
|
||||
if not (self.has_add_permission(request) and self.has_change_permission(request)):
|
||||
raise PermissionDenied
|
||||
if request.method == 'POST':
|
||||
uploaded = request.FILES.get('file')
|
||||
if uploaded is None:
|
||||
@@ -178,6 +182,9 @@ class QuestionAdmin(admin.ModelAdmin):
|
||||
return response
|
||||
|
||||
def import_view(self, request):
|
||||
# admin_view فقط is_staff را میسنجد؛ «جایگزینی» سوالها و جوابهای دانشآموزان به آنها را پاک میکند.
|
||||
if not self.has_add_permission(request):
|
||||
raise PermissionDenied
|
||||
if request.method == 'POST':
|
||||
uploaded = request.FILES.get('file')
|
||||
if uploaded is None:
|
||||
@@ -185,6 +192,8 @@ class QuestionAdmin(admin.ModelAdmin):
|
||||
return redirect(request.path)
|
||||
|
||||
replace = request.POST.get('replace') == 'on'
|
||||
if replace and not self.has_delete_permission(request):
|
||||
raise PermissionDenied
|
||||
try:
|
||||
rows = parse(uploaded.read())
|
||||
result = import_questions(rows, replace=replace)
|
||||
@@ -238,7 +247,7 @@ class QuestionReportAdmin(admin.ModelAdmin):
|
||||
autocomplete_fields = ['question']
|
||||
actions = ['mark_resolved']
|
||||
|
||||
@admin.action(description='علامت زدن بهعنوانِ بررسیشده')
|
||||
@admin.action(description='علامت زدن بهعنوانِ بررسیشده', permissions=['change'])
|
||||
def mark_resolved(self, request, queryset):
|
||||
updated = queryset.update(status='resolved', resolved_at=timezone.now())
|
||||
self.message_user(request, f'{updated} گزارش بررسیشده علامت خورد.', messages.SUCCESS)
|
||||
|
||||
Reference in New Issue
Block a user