پرش به محتویات

تغییرات

منتشرنشده

  • فروشگاه نمونه فارسی حرف می‌زند - منو، سفارش‌ها، فیلدها و وضعیت‌هایشان، و دامنه‌هایش - تا عکس‌های فارسی مستندات سرتاسر فارسی باشند. nox -s messages catalogue آن را هم نگه می‌دارد.

0.2.1 - آنچه عکس‌ها نشان دادند

آنچه عکس‌های پنل ادمین، برای مستندات، نشان دادند. چیزی که پروژه‌ای نوشته تغییر نمی‌کند.

  • صفحه‌های دسترسی متن خودشان را به فارسی می‌گویند - سرستون‌های جدول، مجوزهای خود کتابخانه («می‌تواند دسترسی بسازد») و اسم اپ‌ها انگلیسی بودند.
  • breadcrumbهای صفحه‌های دسترسی همان نوار Django‌اند، روی Django 6.1 هم، که فقط نشانه‌گذاری تازه‌اش را رنگ می‌کند.
  • سازنده‌ی قانون عملگرها را با کلمه نشان می‌دهد - «یکی است از»، نه is_one_of - به زبان خواننده.
  • جدول یک بسته در صفحه‌ی ۱۲۸۰ پیکسلی جا می‌شود. مجوزهای دیگر یک مدل - تأیید، چاپ، مجوزهای یک فیلد - در ردیف جدای خودشان زیر چهار مجوز اصلی می‌آیند، جایی که پیش‌تر از کنار صفحه بیرون می‌زدند.
  • کسی که بسته‌ای را از راه نقشش دارد، در همه‌ی صفحه‌های دسترسی هست. صفحه‌ی افراد و عدد نمای کلی او را جا می‌انداختند، فهرست بسته‌ها برای بسته‌ای که یک نقش به افراد می‌دهد هیچ کس را نمی‌شمرد، صفحه‌ی خود آن «هیچ کس آن را ندارد» نشان می‌داد، و حذف آن طوری نشان داده می‌شد که انگار از هیچ کس چیزی نمی‌گیرد. حالا فهرست می‌شوند، با علامت از نقششان.
  • فیلدی که در کد محافظت شده، به فارسی می‌گوید کجا از محافظت خارج می‌شود - «فقط در کد» - جایی که طوری خوانده می‌شد که انگار آن‌جا از محافظت خارج شده.
  • صفحه‌ی مجوزها با کلمه‌ی فارسی خود کتابخانه نام برده می‌شود - منو کلمه‌ی Django را برای آن می‌گفت، و بقیه‌ی صفحه‌ها کلمه‌ی کتابخانه را.

0.2.0 - بازبینی امنیتی

یافته‌های بازبینی امنیتی 0.1.0. کارهایی را که 0.1.0 می‌کرد طوری عوض می‌کند که یک پروژه ممکن است متوجه شود - هر کدام با تغییر علامت خورده - پس به عنوان 0.2.0 منتشر می‌شود، نه یک وصله. هنوز alpha است: API ممکن است پیش از 1.0 عوض شود.

  • صفحه‌ی کاربر در سایت ادمین باز می‌شود. افزودن یا تغییر کاربر با UserAdmin جنگو برای همه خطای سرور بود.
  • حساب یک superuser فقط مال superuser است که در سایت ادمین تغییرش دهد یا حذفش کند - رمزش هم. پیش‌تر هر کسی که می‌توانست کاربران را تغییر دهد رمز یک superuser را می‌گذاشت و با حساب او وارد می‌شد.
  • فیلدی که نقش‌ها از آن خوانده می‌شوند را فقط superuser می‌نویسد در سایت ادمین، پس با AttributeResolver هیچ‌کس به خودش نقش نمی‌دهد. resolver خود پروژه فیلدهایش را با RoleResolver.fields() نام می‌برد.
  • آخرین کسی که می‌تواند دسترسی‌ها را مدیریت کند در صفحه‌ی کاربر غیرفعال یا حذف نمی‌شود.

  • دامنه برای چیزی که نوشته می‌شود هم برقرار است. ردیفی که بیرون از دامنه‌ی create ساخته شود، یا با ویرایش از ردیف‌هایی که شخص به آن‌ها می‌رسد بیرون برود، رد می‌شود و نوشتن برگردانده می‌شود - در ویوهای Django، در DRF و در سایت ادمین. clearance.writes.require_within_scope همین را در ویویی که دستی ذخیره می‌کند می‌پرسد. تغییر: اعطای create بدون دامنه حالا به default_scope مدل می‌رسد، مثل هر اعطای دیگر - یعنی روی ScopedQuerySetای که آن را نام نبرده، به هیچ ردیفی. به create دامنه بدهید.

  • inline و فهرست قابل‌ویرایش فقط ردیف‌هایی را تغییر می‌دهند که شخص اجازه‌ی ویرایششان را دارد، و inline فقط آن‌هایی را حذف می‌کند که اجازه‌ی حذفشان را دارد. پیش‌تر هر ردیفی که می‌دید آن‌جا قابل تغییر بود.
  • اکشن ادمینی که permissions= جنگو را نام می‌برد روی ردیف‌هایی اجرا می‌شود که آن مجوزها به آن‌ها می‌رسند، نه هر ردیفی که شخص می‌بیند.

  • قانون‌های ایمنی از چهار راه دیگر هم برقرارند. هر کدام برای کسی که دسترسی را مدیریت می‌کرد و هیچ‌چیز از آنچه عوض می‌کرد نداشت باز بود:

  • تغییر ردیف‌های یک دامنه‌ی قانونی، هر مجوزی را که در آن داده شده روی همه‌ی ردیف‌ها لازم دارد؛
  • پس دادن مجوزی که گرفته شده، داشتن آن را لازم دارد - با دامنه‌هایی که بسته‌های آن شخص می‌دهند؛
  • اینکه یک اکشن وضعیت از مقدارهای بیشتری بنویسد کار superuser و فایل ACCESS است - تغییر: پیش‌تر کسی که آن را نداشت می‌توانست؛
  • دادن نقش دومی که سقف را بازتر کند یا بردارد به کسی که زیر سقف است، role_ceiling_edit می‌خواهد.
  • بسته‌ای که شخص با نقشش شروع می‌کند بسته‌ی خود اوست: آن را تغییر نمی‌دهد.

  • فیلد محافظت‌شده از راه دیگری هم دانسته نمی‌شود. بدون مجوز دیدنش باز هم خوانده می‌شد، یا مقدارش تنگ می‌شد، از این راه‌ها:

  • فیلتری که در نوار نشانی پنل ادمین تایپ شود (?price__gte=)، و سلسله‌مراتب تاریخ - هر دو حالا رد می‌شوند؛
  • serializerی که داخل یک serializer محافظت‌شده است، چه نوشته شده باشد چه depth ساخته باشدش؛
  • SearchFilter و OrderingFilter خود DRF که نامش را ببرند، روی ویویی با ProtectedSerializerMixin (که ScopedModelViewSet دارد)؛
  • proxy مدلش، یا مدلی که آن را از والدی با جدول خودش به ارث می‌برد - روی همه‌ی آن‌ها همان یک جفت مجوز را دارد.
  • یک مقدار با هر نامی که فرستاده شود رد می‌شود: فیلد serializer با source='status' می‌توانست مقداری را بنویسد که یک اکشن وضعیت ادعا کرده، و یک رابطه می‌توانست با نام ستونش (officer_id) نوشته شود.
  • فرم پنل ادمین فقط ردیف‌های مرتبطی را پیشنهاد می‌دهد که شخص اجازه‌ی دیدنشان را دارد، و آن یکی را که ردیف از قبل دارد. تغییر: انتخاب ردیف مرتبط در آن‌جا view روی مدل آن را می‌خواهد.

  • visible() از خود queryset می‌پرسد. ویو، پنل ادمین و policy تابع visible() را صدا می‌زدند که هیچ‌وقت از visible_to خود ScopedQuerySet نمی‌پرسید - پس SignedInReadQuerySet به ویو هیچ‌چیز نشان نمی‌داد، و بازنویسی‌ای که تنگ‌تر می‌کرد نادیده گرفته می‌شد. حالا پاسخ خود queryset را می‌گیرند.

  • دامنه‌ای که با queryset جواب می‌دهد ردیف‌هایی را که درباره‌شان پرسیده شده نگه می‌دارد. وقتی تنها بود جای queryset را می‌گرفت، و هر فیلتری که ویو گذاشته بود - مثلاً tenant - از بین می‌رفت.
  • E007 هر backendی را که روی ModelBackend ساخته شده رد می‌کند، نه فقط همان یک نام را: اگر پیش از backend کتابخانه می‌آمد، همه‌ی سلب‌ها را برمی‌گرداند.
  • with_perm همان را می‌گوید که has_perm - بدون کسی که مجوز از او گرفته شده یا سقفش جلویش را می‌گیرد، و با کسی که با نقشش آن را دارد.
  • صفحه‌ها به‌جای خطا جواب می‌دهند. کلید بسته‌ای که عدد نیست 404 است؛ نامی که از ستونش بلندتر است رد می‌شود و دلیلش گفته می‌شود - SQLite آن را کامل نگه می‌داشت و PostgreSQL خطای سرور می‌داد.
  • قانونی که با None مقایسه می‌کند موقع ذخیره رد می‌شود: به شکل '' نگه داشته می‌شد و از آن به بعد به هیچ ردیفی نمی‌رسید. is_empty همین را می‌گوید.
  • کوئری کمتر. صفحه‌ی نمای کلی و صفحه‌ی اکشن‌ها پایگاه داده را به تعداد ثابتی می‌خوانند (پیش‌تر حدود ۹۰ بار، و یکی بیشتر برای هر بسته؛ حالا ۱۹ بار)، و ذخیره‌ی یک بسته دیگر برای هر مجوز یک بار آن را نمی‌خواند.

  • رد شدن به زبان خواننده گفته می‌شود. آنچه قانون‌های ایمنی رد می‌کنند، و ایراد یک قانون، یک اکشن وضعیت، یک فیلد محافظت‌شده یا یک نام، هر زبانی که صفحه داشت به انگلیسی گفته می‌شد. هر کدام برای ترجمه علامت خورده و به فارسی گفته می‌شود؛ AccessRefused.rule همچنان قانون را برای کد نام می‌برد.

  • serializer محافظت‌شده فقط ردیف‌های مرتبطی را می‌پذیرد که شخص اجازه‌ی دیدنشان را دارد، مثل فرم پنل ادمین. تغییر: APIای که ردیف مرتبط انتخاب می‌کند view روی مدل آن را می‌خواهد؛ داده‌ی مرجعی که همه از آن انتخاب می‌کنند با SignedInReadQuerySet خوانده می‌شود، مثل شعبه‌های پروژه‌ی نمونه.

  • مجوزی که مدل دیگری دارد دوباره ساخته نمی‌شود. مجوز یک فیلد، یا یک اکشن وضعیت، با codename مجوز مدل دیگری در همان app همان یکی می‌شد: در پنل ادمین رد می‌شود، و موقع راه‌اندازی برای فیلدی که در کد محافظت شده (E012).
  • دو تغییری که هر کدام می‌توانند هیچ‌کس را برای مدیریت دسترسی باقی نگذارند نوبت می‌گیرند: بررسی، ردیف آن مجوز را قفل می‌کند، هر جا پایگاه داده بتواند.
  • مهاجرت 0006 بدون از دست رفتن چیزی برمی‌گردد: ردیف‌ها به ستون‌ها برمی‌گردند. در جهت جلو، شرطی که با None مقایسه می‌کرد is_empty می‌شود.
  • موارد کوچک‌تر. بله یا نه‌ای که مثل فرم فرستاده شود (on، true) همان خوانده می‌شود، جایی که یک اکشن وضعیت ادعایش کرده. قانون روی رابطه‌ی generic به‌جای خطا رد می‌شود. اکشن وضعیتی در فایل ACCESS که to یا from آن فهرست نیست یک ایراد گفته‌شده است، نه traceback. syncای که روی پایگاه داده‌ی تست رد می‌شود این را می‌گوید.

و آنچه اصلاح‌های این بازبینی جا انداخته بودند:

  • ردیف‌هایی که با هم فرستاده می‌شوند هم در دامنه می‌مانند. فهرستی که یک‌جا ساخته می‌شد (many=True) ردیف به ردیف و بدون بررسی‌ای که یک ردیف تنها داشت ذخیره می‌شد؛ حالا تک‌تک ردیف‌ها بررسی می‌شوند، و فهرست یا کامل ذخیره می‌شود یا اصلاً.
  • visible_to خود یک queryset می‌تواند visible(self, ...) را صدا بزند، همان‌طور که 0.1.0 می‌کرد: درون آن، visible از روی اعطاها جواب می‌دهد. پیش‌تر دوباره همان بازنویسی را صدا می‌زد، بی‌پایان.
  • API ردیف مرتبطی را که یک ردیف از قبل دارد نگه می‌دارد، حتی اگر شخص اجازه‌ی دیدنش را نداشته باشد - ویرایش پرداختی که شخص وامش را نمی‌بیند رد می‌شد. این بررسی source یک فیلد را پیش از آنکه DRF تنظیمش کند می‌خواند.
  • هزینه‌ی with_perm هر چند نفر که مجوز را داشته باشند یکی است: سقف از همه با هم پرسیده می‌شود، نه از تک‌تک‌شان.
  • هر خط کتابخانه را یک تست اجرا می‌کند، و اگر کمتر باشد تست‌ها fail می‌شوند (fail_under = 100).

و آنچه این بازبینی باز گذاشته بود:

  • یک حساب را فقط کسی تغییر می‌دهد یا حذف می‌کند که دست‌کم همه‌ی آنچه آن حساب دارد را داشته باشد. کسی که می‌توانست کاربران را تغییر دهد رمز یکی از مدیران دسترسی را می‌گذاشت و با حساب او وارد می‌شد؛ فقط حساب superuser محافظت شده بود. این در policy.allows پرسیده می‌شود، پس همه‌ی درها آن را می‌پرسند. تغییر: پشتیبانی که رمزها را عوض می‌کند باید آنچه حساب‌هایش دارند را داشته باشد.
  • حذف چیزی را که شخص اجازه‌ی حذفش را ندارد با خود نمی‌برد. حذف یک وام از طریق API پرداخت‌هایش را هم حذف می‌کرد، برای کسی که اجازه‌ی حذف هیچ پرداختی را نداشت. حذف آبشاری به مدلی که کتابخانه از آن محافظت می‌کند در perform_destroy خود DRF، DeleteView خود Django و پنل ادمین پرسیده می‌شود؛ clearance.writes.require_deletable همین را دستی می‌پرسد.
  • قانونی که به مدل دیگری می‌رسد نویسنده‌ای می‌خواهد که همه‌ی آن مدل را ببیند. شرطی روی applicant__email افراد را برای کسی می‌خواند که اجازه‌ی دیدن هیچ‌کدام را نداشت.
  • کلاس مجوز فیلد تغییرنام‌داده را با فیلدی که می‌نویسد می‌خواند. viewای که serializer محافظت‌نشده را موقع اجرا انتخاب می‌کرد - بیرون از دید E003 - می‌گذاشت decision با source='status' وضعیت را بنویسد.
  • DjangoFilterBackend از django-filter فقط با فیلدهای خواندنی فیلتر می‌کند، روی viewای با ProtectedSerializerMixin، همان‌طور که فیلترهای خود DRF می‌کنند.
  • راهنما می‌گوید یک پردازه‌ی طولانی با آنچه روی کاربر نگه داشته شده چه کند: کاربر را دوباره بخواند، یا forget_grants(user).

  • در بررسی اصلاح‌های بالا پیدا شد، و حالا برقرار است:

  • فهرست قابل‌ویرایش حساب‌ها حسابی را که بیش از شخص دارد دست نمی‌زند - روی Django 5.2 ایمیل یک مدیر را عوض می‌کرد، که راهی به حساب اوست؛
  • هیچ کس روی حساب خودش فیلدی را نمی‌نویسد که یک دامنه‌ی قانونی آن را به‌عنوان مالِ شخص می‌خواند (region، برای «منطقه‌ی شخص») - در پنل ادمین، فرم محافظت‌شده یا API: این همان ردیف‌هایی است که به آن‌ها می‌رسد؛
  • تغییر: حسابی که همان دامنه را فقط به نام دارد، وقتی بالاتر حساب نمی‌شود که آن دامنه برای هر کسی به همان ردیف‌ها برسد. دو نفر که «شعبه‌ی خودش» را دارند هر کدام شعبه‌ی خودشان را دارند؛ تغییر حساب دیگری آن مجوز را در all می‌خواهد؛
  • تغییر: حذف، از هر مدلی که حذف آبشاری به آن می‌رسد و مجوز delete دارد می‌پرسد، نه فقط مدل‌هایی که دامنه یا مجوز تعریف‌شده دارند. حذف یک وام، delete روی صورت‌حساب‌هایش را هم می‌خواهد.

و آنچه بررسی این‌ها پیدا کرد:

  • inline و فهرست قابل‌ویرایش hook خود policy را برای هر ردیف می‌پرسند - مثلاً «سفارش تحویل‌شده ویرایش نمی‌شود». پیش‌تر فقط دامنه را می‌پرسیدند، پس hookی که یک ردیف را رد می‌کرد دور زده می‌شد: در inline روی همه‌ی نسخه‌های Django، و در فهرست قابل‌ویرایش پیش از 6.1.
  • PERSON_FIELDS فیلدهایی از کاربر را نام می‌برد که یک دامنه‌ی کد می‌خواند - ('branch',) برای Q(branch=user.branch_id) - و این فیلدها روی حساب خود شخص قفل می‌شوند، همان‌طور که فیلدهایی که یک دامنه‌ی قانونی می‌خواند. کتابخانه نمی‌تواند دامنه‌ای را که در کد نوشته شده بخواند.
  • CASCADE_UNASKED_APPS اپ‌هایی را نام می‌برد که حذف، ردیف‌هایشان را بدون پرسش با خود می‌برد - اپی که برای هر حساب یک توکن یا نشانی نگه می‌دارد، و delete آن را به کسی نمی‌دهند. مدلی که کتابخانه از آن محافظت می‌کند در هر حال پرسیده می‌شود.

0.1.0 - نخستین انتشار

Alpha: همه‌ی بخش‌ها ساخته و تست شده‌اند، ولی هنوز در پروژه‌ای با مدل‌های واقعی به کار نرفته‌اند، پس API ممکن است پیش از 1.0 عوض شود.

  • بسته‌ها - همان گروه‌های Django، که هر مجوز در آن‌ها با یک دامنه‌ی ردیف داده می‌شود؛ گرفتن یک مجوز از یک شخص، با دلیلش.
  • دامنه‌های ردیف - در کد روی یک ScopedQuerySet، یا ساخته‌شده از قانون‌ها در پنل ادمین؛ visible_to(user, action) آن‌ها را با تعداد ثابتی کوئری می‌خواند.
  • فیلدهای محافظت‌شده - با یک مجوز خوانده و با مجوزی دیگر نوشته می‌شوند، در کد یا در پنل ادمین، و از هر serializer، فرم، قالب، صفحه‌ی ادمین و قانونی کنار گذاشته می‌شوند.
  • اکشن‌ها - اکشن‌های وضعیت که فقط خودشان مقدارهایشان را می‌نویسند؛ اکشن‌هایی که در viewها، viewsetها و اکشن‌های ادمین پیدا می‌شوند، با مجوزهایی که migrate می‌سازد.
  • Policyها - BasePolicy برای قانون‌هایی که هیچ مجوزی نمی‌تواند بگوید، و ModelPolicy برای هر مدل دیگر.
  • viewهای Django و DRF - PolicyRequiredMixin، ScopedQuerySetMixin، ProtectedFieldsViewMixin، policy_required؛ ModelPolicyPermission، PolicyPermission، ScopedModelViewSet، و رد کردن نوشتن، فیلد به فیلد.
  • پنل ادمین - هر ModelAdmin از policyها می‌پرسد، و صفحه‌های دسترسی: نمای کلی، بسته‌ها به شکل جدول، افراد، مجوزها، سازنده‌ی قانون، فیلدهای محافظت‌شده، اکشن‌ها؛ به فارسی راست به چپ.
  • قانون‌های ایمنی - هیچ کس به خودش بیشتر نمی‌دهد، چیزی را که ندارد نمی‌دهد، و کاری نمی‌کند که هیچ کس نتواند دسترسی را مدیریت کند.
  • دسترسی به صورت کد - فایل ACCESS و clearance_sync، با --dry-run، --check و SYNC_ON_MIGRATE.
  • clearance.roles - نقش‌ها با بسته‌های آغازین و یک سقف.
  • clearance.translations - اسم مجوزها، بسته‌ها و نقش‌ها در همه‌ی زبان‌ها.
  • بررسی - system checkها موقع بالا آمدن پروژه، و clearance_check برای چیزهایی که فقط پایگاه داده می‌تواند بگوید.
  • انگلیسی و فارسی در همه جا؛ Python 3.11 تا 3.14، Django 5.2، 6.0 و 6.1، DRF 3.16 و بالاتر.