Files
clinicpro/docs/qa/bug-report-2026-06-20.md
T

6.0 KiB
Raw Blame History

گزارش باگ — ممیزی ۱۴۰۵/۰۳/۳۰ (2026-06-20)

ممیزی رفتاریِ کل APIها (۲۴۳ endpoint) با توکن واقعیِ هر ۶ نقش (admin/clinic/doctor/secretary/representation/user) روی https://clinic-pro.ddev.site. مقایسه با docs/api/*.

خلاصه

سطح تعداد
CRITICAL 0
HIGH 1
MEDIUM 2
LOW 0
رفع‌شده در همین جلسه 1 (BUG-1)

نتیجه‌ی کلیِ امنیتی مثبت: لایه‌ی authorization محکم است — همه‌ی /api/v1/admin/* برای نقش‌های غیرادمین 403 دادند؛ scope نماینده درست محدود است؛ IDOR روی پروفایل کاربر و ویرایش کلینیکِ غیرمالک 403/404 می‌گیرد؛ ورودی‌های مرزیِ لیست‌ها (page/limit بزرگ/منفی/غیرعددی، تاریخ نامعتبر) همگی graceful بودند (بدون ۵۰۰).


باگ‌ها

[HIGH] BUG-3 — ساخت نماینده با شماره موبایلِ نامعتبر (بدون اعتبارسنجی فرمت)

  • دامنه: representation
  • endpoint: POST /api/v1/representation (ROLE_ADMIN)
  • بازتولید:
    curl -sk -X POST .../api/v1/representation -H "Authorization: Bearer <ADMIN>" \
      -H "Content-Type: application/json" -d '{"full_name":"qa","mobile_number":"not-a-mobile"}'
    
  • انتظار: 422 (شماره موبایل نامعتبر).
  • واقعیت: 201 Created — یک نماینده ساخته شد و یک User("not-a-mobile") ایجاد شد (در پاسخ mobile_number: null).
  • اثر: رکورد آشغال + کاربرِ بدون شماره‌ی لاگین معتبر؛ شماره موبایل کلیدِ ورود است، پس نماینده‌ای ساخته می‌شود که نمی‌تواند وارد شود/پیامک بگیرد.
  • ریشه: src/Representation/Controller/RepresentationController.php:86,89 — فقط empty($mobile) چک می‌شود، فرمت ایران (^09\d{9}$) چک نمی‌شود.
  • رفع پیشنهادی: بعد از empty چک، if (!preg_match('/^09\d{9}$/', $mobile)) return $this->error(ERR_VALIDATION_001, 'شماره موبایل نامعتبر', 422); (همان regex فرانت).
  • وضعیت: گزارش‌شده (رفع نشده — نیاز به تأیید).

[MEDIUM] BUG-2 — کد ملی نامعتبر بدون اعتبارسنجی سمت سرور ذخیره می‌شود

  • دامنه: user-profile
  • endpoint: PATCH /api/v1/user-profile/{uuid}
  • بازتولید:
    curl -sk -X PATCH .../api/v1/user-profile/<own-uuid> -H "Authorization: Bearer <USER>" \
      -H "Content-Type: application/json" -d '{"national_code":"1111111111"}'
    
  • انتظار: 422 (رقم کنترلیِ کد ملی نامعتبر است).
  • واقعیت: 200 و national_code: "1111111111" ذخیره شد.
  • اثر: اعتبارسنجی کد ملی فقط در فرانت (isValidIranNationalCode) است؛ هر کلاینت/فرانتِ دورزده‌شده می‌تواند کد ملی نامعتبر ثبت کند.
  • ریشه: src/UserProfile/Controller/UserProfileController.phphydrate() فقط setNationalCode($data['national_code']) می‌کند، بدون validation.
  • رفع پیشنهادی: اعتبارسنجی الگوریتم رقم کنترلیِ ایران سمت سرور قبل از setNationalCode (پورت همان منطق فرانت).
  • وضعیت: گزارش‌شده.

[MEDIUM] BUG-1 — ۵۰۰ روی ساخت بلاگ با تایپ اشتباهِ body رفع شد

  • دامنه: blog
  • endpoint: POST /api/v1/blog (ROLE_ADMIN)
  • بازتولید: -d '{"title":"valid","body":["x"]}'
  • انتظار: 422.
  • واقعیت (قبل رفع): HTTP 500 / ERR_INTERNAL_001.
  • ریشه: src/Blog/Controller/BlogController.php:178trim($data['body'] ?? '') وقتی body آرایه است → TypeError در PHP 8.
  • رفع انجام‌شده: چک is_string روی title/body (۴۲۲ صریح) + cast (string). تست شد: body آرایه → 422، بلاگ معتبر همچنان 201.
  • یادداشت: اسکن سراسری نشان داد بقیه‌ی controllerها از (string) cast استفاده می‌کنند؛ این تنها نقطه‌ی بدون cast بود.

مواردی که تست شدند و سالم بودند (شواهد مثبت)

تست نتیجه
/api/v1/admin/* (users, payments, settlements, doctors, clinics, sms/logs) با ۵ نقش غیرادمین همه 403 ✓ (admin 200)
representation/{me,doctors,clinics,appointments} rep 200، بقیه 403
IDOR: user → PATCH پروفایلِ کاربر دیگر 404 (canAccess ownership) ✓
IDOR: doctor → PATCH کلینیکِ غیرمالک 403 (ERR_AUTH_006) ✓
لیست‌های paginated: page=99999, limit=-1/0, page=abc همه 200 graceful ✓
detail بلاگ‌ها (کلاس باگ proxy author) همه 200 ✓ (قبلاً رفع شده بود)
POST با بدنه‌ی خالی {} (doctor/clinic/representation/blog/send-code) همه 422
tsc --noEmit + yarn dev (admin build) بدون خطا ✓

توصیه‌ها

  1. یک Trait/سرویس اعتبارسنجی مشترک سمت سرور برای موبایل و کد ملی بساز (الان منطق فقط در فرانت تکرار شده). BUG-2 و BUG-3 هر دو از همین خلأ می‌آیند.
  2. الگوی trim((string) ($data[...] ?? '')) را به‌عنوان قاعده در همه‌ی هندلرها رعایت کن (BUG-1).
  3. این ممیزی روی محورهای پرریسک متمرکز بود؛ برای پوشش کامل ۲۴۳ endpoint، یک suite phpunit رفتاری (functional) ارزش سرمایه‌گذاری دارد (الان فقط tests/bootstrap.php هست).