feat(tenant): implement tenant filter scope to manage cross-tenant data visibility

This commit is contained in:
hamed
2026-08-10 16:22:00 +03:30
parent ee2682e222
commit 2459625c41
8 changed files with 304 additions and 20 deletions
+6
View File
@@ -547,6 +547,12 @@ Get all appointments for the authenticated user.
**Permission:** `AUTH` — عمداً بدون مجوزِ رجیستری.
> **فیلتر محیط اینجا اعمال نمی‌شود.** رکورد در محیطِ پزشکِ مقصد ثبت می‌شود، ولی
> مالکش از راه `user_id` تعیین می‌شود. کاربری که خودش صاحب محیط دیگری است — پزشک،
> منشی، مالک کلینیک — با فیلترِ محیطِ خودش رکورد خودش را نمی‌دید و ۴۰۴ می‌گرفت.
> دورزدن فیلتر فقط از راه `App\Shared\Tenant\TenantFilterScope` انجام می‌شود و
> مجوز دست‌نخورده باقی می‌ماند.
> این اندپوینت `a.user = خودِ کاربر` را می‌دهد، یعنی نوبت‌های خودِ فرد **به‌عنوان
> بیمار**، نه دادهٔ محیط. مصرف‌کننده‌اش داشبورد بیمار در `nobat724_front` است.
> آدیت ۲۰۲۶-۰۸-۰۷ آن را در فهرست گَپ‌ها آورده بود؛ در ۲۰۲۶-۰۸-۰۸ مثبت کاذب تشخیص
+12
View File
@@ -131,6 +131,12 @@ List the **authenticated user's own** payments (derived from the token — there
**Permission:** `IS_AUTHENTICATED_FULLY`
> **فیلتر محیط اینجا اعمال نمی‌شود.** رکورد در محیطِ پزشکِ مقصد ثبت می‌شود، ولی
> مالکش از راه `user_id` تعیین می‌شود. کاربری که خودش صاحب محیط دیگری است — پزشک،
> منشی، مالک کلینیک — با فیلترِ محیطِ خودش رکورد خودش را نمی‌دید و ۴۰۴ می‌گرفت.
> دورزدن فیلتر فقط از راه `App\Shared\Tenant\TenantFilterScope` انجام می‌شود و
> مجوز دست‌نخورده باقی می‌ماند.
### Query Parameters
| Param | Type | Default | Description |
|-------|------|---------|-------------|
@@ -405,6 +411,12 @@ Get payment status and details.
**Permission:** `AUTH` — must be the payment owner or `ROLE_ADMIN`
> **فیلتر محیط اینجا اعمال نمی‌شود.** رکورد در محیطِ پزشکِ مقصد ثبت می‌شود، ولی
> مالکش از راه `user_id` تعیین می‌شود. کاربری که خودش صاحب محیط دیگری است — پزشک،
> منشی، مالک کلینیک — با فیلترِ محیطِ خودش رکورد خودش را نمی‌دید و ۴۰۴ می‌گرفت.
> دورزدن فیلتر فقط از راه `App\Shared\Tenant\TenantFilterScope` انجام می‌شود و
> مجوز دست‌نخورده باقی می‌ماند.
### Path Parameters
| Param | Type | Description |
|-------|------|-------------|
+24
View File
@@ -76,6 +76,30 @@ clinic_uuid صریحِ درخواست > UserActiveContext ذخیره‌شده
| SQL خام DBAL | ❌ |
| فرزندان aggregate | ❌ — همیشه از ریشه JOIN کن |
### استثنای مجاز: دادهٔ «مالِ خودِ کاربر»
چند رکورد در محیطِ یک مطب ثبت می‌شوند ولی مالکشان بیمار است، نه آن مطب: پرداختِ نوبت و
خودِ نوبتِ بیمار. مجوزشان با `user_id` بررسی می‌شود، نه با محیط.
اینجا فیلتر چیزی به امنیت اضافه نمی‌کند و فقط ضرر می‌زند: کاربری که خودش صاحب محیط
دیگری است — پزشکی که از مطب دیگری نوبت می‌گیرد، منشی‌ای که جایی بیمار است — رکورد
خودش را نمی‌دید و صفحهٔ نتیجهٔ پرداخت ۴۰۴ می‌شد.
تنها راه مجاز دورزدن، `App\Shared\Tenant\TenantFilterScope::withoutFilter()` است.
عمداً یک کلاس جداست تا جاهای دورزدن قابل شمردن بمانند، و فیلتر را در `finally`
برمی‌گرداند تا بقیهٔ همان درخواست دوباره محدود شود.
مصرف‌کنندگان فعلی — هر سه کاربرمحور، نه محیط‌محور:
| اندپوینت | چرا |
|---|---|
| `GET /api/v1/payment/{uuid}` | پرداخت‌کننده باید پرداخت خودش را ببیند |
| `GET /api/v1/my/payments` | همان، به شکل فهرست |
| `GET /api/v1/appointments/user` | نوبت‌های خودِ فرد به‌عنوان بیمار |
`PayerSeesOwnPaymentTest` هر سه را می‌بندد و هم‌زمان تأیید می‌کند که پرداختِ کاربر
دیگر همچنان ۴۰۳ می‌گیرد.
**فیلتر جایگزین authorization نیست.** `AppointmentAccessChecker`، `ClinicDoctorAccessChecker`، `SecretaryAccessChecker` و `PatientRecordScopeResolver` سر جایشان می‌مانند: آن‌ها «چه کاری مجاز است» را جواب می‌دهند، فیلتر فقط «کدام ردیف‌ها».
### تغییر رفتار: ۴۰۴ به‌جای ۴۰۳