Security review: staff admin lockdown, unicode-safe login/phone/grade inputs, demo guest not upgradable, home cache not floodable
- accounts admin: phone/is_guest read-only for non-superusers, device_id filter lookups refused, auth.User/Group admin writable only by superusers - campaigns: delivery log needs view permission - phone/OTP/birth date: any Unicode digit normalised or rejected with 400; unknown gradeId is 400 - shared demo guest is never turned into a person's account by logging in - /api/home with unknown gradeId is no longer cached (LocMemCache flood reset throttle counters) - /api/progress/nodes: one query, huge unit is 400 - BazaarCredential token filter lookups refused Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -67,6 +67,13 @@ class DeviceIdForSuperuserOnly:
|
||||
exclude = tuple(super().get_exclude(request, obj) or ())
|
||||
return exclude if request.user.is_superuser else exclude + ('device_id',)
|
||||
|
||||
def lookup_allowed(self, lookup, value, request=None):
|
||||
# جنگو هر فیلترِ querystring روی فیلدِ خودِ مدل را میپذیرد (?device_id__startswith=a)؛
|
||||
# بیاین، با شمردنِ نتیجه شناسه حرفبهحرف درمیآید. جنگو برای فیلترِ ردشده ۴۰۰ میدهد.
|
||||
if lookup.split('__')[0] == 'device_id' and not (request and request.user.is_superuser):
|
||||
return False
|
||||
return super().lookup_allowed(lookup, value, request)
|
||||
|
||||
|
||||
@admin.register(User)
|
||||
class UserAdmin(DeviceIdForSuperuserOnly, admin.ModelAdmin):
|
||||
@@ -90,6 +97,13 @@ class UserAdmin(DeviceIdForSuperuserOnly, admin.ModelAdmin):
|
||||
list_filter = ('is_guest',)
|
||||
actions = ['send_sms_to_selected']
|
||||
|
||||
def get_readonly_fields(self, request, obj=None):
|
||||
# شماره کلیدِ ورود است: کارمندی با change_user که شمارهی کاربری را به شمارهی خودش عوض
|
||||
# کند، کد میگیرد و واردِ همان حساب میشود. is_guest هم همینطور ورود با شناسهی دستگاه را
|
||||
# روی حسابِ شمارهدار باز میکند. هر دو فقط برای ابرکاربر قابلِ تغییرند.
|
||||
fields = tuple(super().get_readonly_fields(request, obj))
|
||||
return fields if request.user.is_superuser else fields + ('phone', 'is_guest')
|
||||
|
||||
# permissions=['change']: بدونِ این، کاربرِ فقط-view (گروهِ Supervisor)
|
||||
# میتونست با POSTِ مستقیم این اکشن رو صدا بزنه بدونِ دیدنِ دکمه در UI؛
|
||||
# این پارامتر باعث میشه ModelAdmin فقط برای change_user در دسترس بذارتش.
|
||||
@@ -250,3 +264,37 @@ def _admin_get_urls_with_supervisor():
|
||||
|
||||
|
||||
admin.site.get_urls = _admin_get_urls_with_supervisor
|
||||
|
||||
|
||||
# حسابهای کارمندانِ پنل (auth.User/Group جنگو، جدا از accounts.User). در پیشفرضِ جنگو کارمندی با
|
||||
# change_group همهی اجازهها را به گروهِ خودش میدهد و با change_user (auth) تیکِ ابرکاربر را برای
|
||||
# خودش میزند. ساختن/تغییر/حذفِ این دو فقط برای ابرکاربر است؛ دیدن همان اجازهی view را میخواهد.
|
||||
from django.contrib.auth import get_user_model # noqa: E402
|
||||
from django.contrib.auth.admin import GroupAdmin as _DjangoGroupAdmin # noqa: E402
|
||||
from django.contrib.auth.admin import UserAdmin as _DjangoUserAdmin # noqa: E402
|
||||
from django.contrib.auth.models import Group # noqa: E402
|
||||
|
||||
|
||||
class _SuperuserWritesOnly:
|
||||
def has_add_permission(self, request):
|
||||
return request.user.is_superuser
|
||||
|
||||
def has_change_permission(self, request, obj=None):
|
||||
return request.user.is_superuser
|
||||
|
||||
def has_delete_permission(self, request, obj=None):
|
||||
return request.user.is_superuser
|
||||
|
||||
|
||||
class StaffUserAdmin(_SuperuserWritesOnly, _DjangoUserAdmin):
|
||||
pass
|
||||
|
||||
|
||||
class StaffGroupAdmin(_SuperuserWritesOnly, _DjangoGroupAdmin):
|
||||
pass
|
||||
|
||||
|
||||
admin.site.unregister(get_user_model())
|
||||
admin.site.register(get_user_model(), StaffUserAdmin)
|
||||
admin.site.unregister(Group)
|
||||
admin.site.register(Group, StaffGroupAdmin)
|
||||
|
||||
Reference in New Issue
Block a user