Files
MathApp/accounts/phone.py
Zareian 28b8527760 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>
2026-10-05 09:34:42 +03:30

44 lines
2.3 KiB
Python
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
نرمال‌سازیِ شماره‌ی موبایلِ ایران به شکلِ یکتای ‎09XXXXXXXXX‎.
چرا: پیش از این `phone` رشته‌ی خام بود و ‎0912…‎، ‎+98912…‎، ‎98912…‎ و همان
شماره با ارقامِ فارسی چهار «شماره»ی جدا بودند — هر کدام cooldown و سقفِ پیامکِ
خودش را داشت و حسابِ جدا می‌ساخت (security-before-production.md، بندِ ۱۷).
هرچه شکلِ ‎09\\d{9}‎ نگیرد رد می‌شود، پس شماره‌ی خارجی یا خطِ ثابت هم به
کاوه‌نگار نمی‌رسد (پیامکِ بین‌المللی گران است و هدفِ SMS pumping).
عمداً بی‌وابستگی به DRF است تا config/settings.py هم بتواند واردش کند
(کلیدهای TEST_LOGIN_PHONES)؛ فیلدِ serializer در accounts/serializers.py است.
"""
import re
import unicodedata
_SEPARATORS = re.compile(r'[\s\-().\u200c\u200e\u200f]')
# [0-9] نه \d: در پایتون \d هر رقمِ یونیکد (تمام‌عرض، ریاضی، دوناگری…) را هم می‌گیرد.
_MOBILE = re.compile(r'09[0-9]{9}')
def ascii_digits(value: str) -> str:
"""
هر رقمِ دهدهیِ یونیکد (فارسی، عربی، تمام‌عرض «0»، ریاضی «𝟎»، …) → رقمِ لاتین.
بدونِ این «09123456789» شماره‌ی جدایی می‌شد با سقفِ پیامک و حسابِ جدا. «²» و «①» رقمِ
دهدهی نیستند و دست نمی‌خورند، پس شماره‌ای که آن‌ها را دارد رد می‌شود.
"""
return ''.join(str(unicodedata.decimal(ch)) if not ch.isascii() and ch.isdecimal() else ch
for ch in value)
def normalize_phone(value) -> str | None:
"""شکلِ یکتای ‎09XXXXXXXXX‎، یا None اگر موبایلِ ایران نباشد."""
s = _SEPARATORS.sub('', ascii_digits(str(value or '')))
if s.startswith('+98'):
s = '0' + s[3:]
elif s.startswith('0098'):
s = '0' + s[4:]
elif s.startswith('98') and len(s) == 12:
s = '0' + s[2:]
elif s.startswith('9') and len(s) == 10:
s = '0' + s
return s if _MOBILE.fullmatch(s) else None