1. قبل از شروع
Cloud Firestore، Cloud Storage برای Firebase، و پایگاه داده بیدرنگ به فایلهای پیکربندی که شما مینویسید برای اعطای دسترسی خواندن و نوشتن متکی هستند. این پیکربندی که قوانین امنیتی نام دارد، همچنین می تواند به عنوان نوعی طرحواره برای برنامه شما عمل کند. این یکی از مهمترین بخش های توسعه برنامه شما است. و این کد لبه شما را از طریق آن راهنمایی می کند.
پیش نیازها
- یک ویرایشگر ساده مانند Visual Studio Code، Atom یا Sublime Text
- Node.js 8.6.0 یا بالاتر (برای نصب Node.js، از nvm استفاده کنید ؛ برای بررسی نسخه خود،
node --version
اجرا کنید) - جاوا 7 یا بالاتر (برای نصب جاوا از این دستورالعمل ها استفاده کنید ؛ برای بررسی نسخه خود،
java -version
اجرا کنید)
کاری که خواهی کرد
در این کد لبه، شما یک پلت فرم وبلاگ ساده ساخته شده بر روی Firestore را ایمن خواهید کرد. شما از شبیه ساز Firestore برای اجرای تست های واحد در برابر قوانین امنیتی استفاده خواهید کرد و اطمینان حاصل کنید که قوانین دسترسی شما را مجاز می دانند و آن را ممنوع می کنند.
شما یاد خواهید گرفت که چگونه:
- مجوزهای گرانول اعطا کنید
- اعتبارسنجی داده ها و نوع را اعمال کنید
- اجرای کنترل دسترسی مبتنی بر ویژگی
- اعطای دسترسی بر اساس روش احراز هویت
- ایجاد توابع سفارشی
- قوانین امنیتی مبتنی بر زمان ایجاد کنید
- پیاده سازی لیست رد و حذف نرم
- درک زمان غیرعادی کردن داده ها برای برآورده کردن الگوهای دسترسی چندگانه
2. راه اندازی کنید
این یک برنامه وبلاگ نویسی است. در اینجا خلاصه سطح بالایی از عملکرد برنامه آمده است:
پیش نویس پست های وبلاگ:
- کاربران می توانند پیش نویس پست های وبلاگ را ایجاد کنند که در مجموعه
drafts
وجود دارد. - نویسنده می تواند تا زمانی که پیش نویس آماده انتشار شود، به به روز رسانی آن ادامه دهد.
- هنگامی که آماده انتشار است، یک تابع Firebase فعال می شود که یک سند جدید در مجموعه
published
ایجاد می کند. - پیش نویس ها را می توان توسط نویسنده یا مدیران سایت حذف کرد
پست های وبلاگ منتشر شده:
- پست های منتشر شده را نمی توان توسط کاربران ایجاد کرد، فقط از طریق یک تابع.
- آنها را فقط می توان به صورت نرم حذف کرد که یک ویژگی
visible
را به false به روز می کند.
نظرات
- پستهای منتشر شده اجازه نظرات را میدهند، که مجموعهای فرعی در هر پست منتشر شده است.
- برای کاهش سوء استفاده، کاربران باید یک آدرس ایمیل تایید شده داشته باشند و برای گذاشتن نظر در یک انکار کننده نباشند.
- نظرات فقط ظرف یک ساعت پس از ارسال آن می توانند به روز شوند.
- نظرات می تواند توسط نویسنده نظر، نویسنده پست اصلی، یا توسط مدیران حذف شود.
علاوه بر قوانین دسترسی، قوانین امنیتی ایجاد خواهید کرد که فیلدهای مورد نیاز و اعتبارسنجی داده ها را اعمال می کند.
همه چیز به صورت محلی و با استفاده از Firebase Emulator Suite اتفاق می افتد.
کد منبع را دریافت کنید
در این نرمافزار، شما با آزمایشهایی برای قوانین امنیتی شروع میکنید، اما خود قوانین امنیتی را تقلیل میدهید، بنابراین اولین کاری که باید انجام دهید این است که منبع را برای اجرای آزمایشها کلون کنید:
$ git clone https://github.com/FirebaseExtended/codelab-rules.git
سپس به دایرکتوری حالت اولیه بروید، جایی که برای بقیه این کد لبه کار خواهید کرد:
$ cd codelab-rules/initial-state
اکنون، وابستگی ها را نصب کنید تا بتوانید تست ها را اجرا کنید. اگر اتصال اینترنت شما کندتر است، ممکن است یک یا دو دقیقه طول بکشد:
# Move into the functions directory, install dependencies, jump out. $ cd functions && npm install && cd -
Firebase CLI را دریافت کنید
مجموعه Emulator که برای اجرای آزمایشها استفاده میکنید بخشی از Firebase CLI (رابط خط فرمان) است که میتواند با دستور زیر بر روی دستگاه شما نصب شود:
$ npm install -g firebase-tools
سپس، تأیید کنید که آخرین نسخه CLI را دارید. این کد لبه باید با نسخه 8.4.0 یا بالاتر کار کند، اما نسخه های بعدی دارای رفع اشکال بیشتری هستند.
$ firebase --version 9.10.2
3. تست ها را اجرا کنید
در این بخش، تست ها را به صورت محلی اجرا می کنید. این بدان معناست که زمان راهاندازی Emulator Suite فرا رسیده است.
شبیه سازها را راه اندازی کنید
برنامهای که با آن کار خواهید کرد دارای سه مجموعه اصلی Firestore است: drafts
حاوی پستهای وبلاگی هستند که در حال انجام هستند، مجموعه published
حاوی پستهای وبلاگ منتشر شده است و comments
یک مجموعه فرعی در پستهای منتشر شده است. مخزن با تستهای واحد برای قوانین امنیتی ارائه میشود که ویژگیهای کاربر و سایر شرایط مورد نیاز کاربر برای ایجاد، خواندن، بهروزرسانی و حذف اسناد در drafts
، مجموعههای published
و comments
را تعریف میکند. شما قوانین امنیتی را می نویسید تا آن تست ها با موفقیت انجام شود.
برای شروع، پایگاه داده شما قفل می شود: خواندن و نوشتن در پایگاه داده به طور کلی رد می شود و همه آزمایش ها با شکست مواجه می شوند. همانطور که قوانین امنیتی را می نویسید، تست ها با موفقیت پشت سر می گذرد. برای دیدن تست ها، functions/test.js
در ویرایشگر خود باز کنید.
در خط فرمان، شبیه سازها را با استفاده از emulators:exec
راه اندازی کنید و تست ها را اجرا کنید:
$ firebase emulators:exec --project=codelab --import=.seed "cd functions; npm test"
به بالای خروجی بروید:
$ firebase emulators:exec --project=codelab --import=.seed "cd functions; npm test" i emulators: Starting emulators: functions, firestore, hosting ⚠ functions: The following emulators are not running, calls to these services from the Functions emulator will affect production: auth, database, pubsub ⚠ functions: Unable to fetch project Admin SDK configuration, Admin SDK behavior in Cloud Functions emulator may be incorrect. i firestore: Importing data from /Users/user/src/firebase/rules-codelab/initial-state/.seed/firestore_export/firestore_export.overall_export_metadata i firestore: Firestore Emulator logging to firestore-debug.log ⚠ hosting: Authentication error when trying to fetch your current web app configuration, have you run firebase login? ⚠ hosting: Could not fetch web app configuration and there is no cached configuration on this machine. Check your internet connection and make sure you are authenticated. To continue, you must call firebase.initializeApp({...}) in your code before using Firebase. i hosting: Serving hosting files from: public ✔ hosting: Local server: http://localhost:5000 i functions: Watching "/Users/user/src/firebase/rules-codelab/initial-state/functions" for Cloud Functions... ✔ functions[publishPost]: http function initialized (http://localhost:5001/codelab/us-central1/publishPost). ✔ functions[softDelete]: http function initialized (http://localhost:5001/codelab/us-central1/softDelete). i Running script: pushd functions; npm test ~/src/firebase/rules-codelab/initial-state/functions ~/src/firebase/rules-codelab/initial-state > functions@ test /Users/user/src/firebase/rules-codelab/initial-state/functions > mocha (node:76619) ExperimentalWarning: Conditional exports is an experimental feature. This feature could change at any time Draft blog posts 1) can be created with required fields by the author 2) can be updated by author if immutable fields are unchanged 3) can be read by the author and moderator Published blog posts 4) can be read by everyone; created or deleted by no one 5) can be updated by author or moderator Comments on published blog posts 6) can be read by anyone with a permanent account 7) can be created if email is verfied and not blocked 8) can be updated by author for 1 hour after creation 9) can be deleted by an author or moderator 0 passing (848ms) 9 failing ...
در حال حاضر 9 شکست وجود دارد. همانطور که فایل قوانین را میسازید، میتوانید پیشرفت را با تماشای آزمونهای بیشتری اندازهگیری کنید.
4. پیش نویس پست وبلاگ ایجاد کنید.
از آنجایی که دسترسی به پیش نویس پست های وبلاگ بسیار متفاوت از دسترسی برای پست های وبلاگ منتشر شده است، این برنامه وبلاگ نویسی پیش نویس پست های وبلاگ را در مجموعه ای جداگانه ذخیره می کند، /drafts
. پیشنویسها فقط توسط نویسنده یا ناظر قابل دسترسی است و دارای اعتبارسنجی برای فیلدهای ضروری و غیرقابل تغییر است.
با باز کردن فایل firestore.rules
، یک فایل قوانین پیش فرض پیدا خواهید کرد:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if false;
}
}
}
عبارت تطابق، match /{document=**}
، از دستور **
برای اعمال بازگشتی به همه اسناد موجود در زیر مجموعهها استفاده میکند. و از آنجایی که در سطح بالایی قرار دارد، در حال حاضر همان قانون کلی برای همه درخواستها اعمال میشود، مهم نیست چه کسی درخواست را ارائه میکند یا چه دادههایی را میخواهد بخواند یا بنویسد.
با حذف عبارت inner-most match و جایگزینی آن با match /drafts/{draftID}
شروع کنید. (نظرات مربوط به ساختار اسناد می تواند در قوانین مفید باشد و در این کدها گنجانده می شود؛ آنها همیشه اختیاری هستند.)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
}
}
}
اولین قانونی که برای پیش نویس ها می نویسید، کنترل می کند که چه کسی می تواند اسناد را ایجاد کند. در این برنامه، پیشنویسها فقط توسط شخصی که بهعنوان نویسنده فهرست شده است میتواند ایجاد شود. بررسی کنید که UID شخصی که درخواست می کند همان UID ذکر شده در سند باشد.
شرط اول ایجاد این خواهد بود:
request.resource.data.authorUID == request.auth.uid
در مرحله بعد، اسناد تنها در صورتی می توانند ایجاد شوند که شامل سه فیلد الزامی، authorUID
، createdAt
و title
باشند. (کاربر فیلد createdAt
را ارائه نمیکند؛ این باعث میشود که برنامه باید قبل از ایجاد سند آن را اضافه کند.) از آنجایی که فقط باید بررسی کنید که آیا ویژگیها ایجاد شدهاند، میتوانید بررسی کنید که request.resource
همه چیز را دارد. آن کلیدها:
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
])
شرط نهایی برای ایجاد یک پست وبلاگ این است که عنوان نباید بیش از 50 کاراکتر باشد:
request.resource.data.title.size() < 50
از آنجایی که همه این شرایط باید درست باشند، اینها را با عملگر AND منطقی و &&
به هم بچسبانید. قانون اول می شود:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
allow create: if
// User creating document is draft author
request.auth.uid == request.resource.data.authorUID &&
// Must include title, author, and url fields
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
}
}
}
در ترمینال، تست ها را مجدداً اجرا کنید و تأیید کنید که اولین آزمایش موفق شده است.
5. پیش نویس پست های وبلاگ را به روز کنید.
در مرحله بعد، همانطور که نویسندگان پیش نویس پست های وبلاگ خود را اصلاح می کنند، اسناد پیش نویس را ویرایش می کنند. یک قانون برای شرایطی که یک پست می تواند به روز شود ایجاد کنید. اول، فقط نویسنده می تواند پیش نویس های خود را به روز کند. توجه داشته باشید که در اینجا UID که قبلاً نوشته شده است، resource.data.authorUID
را بررسی کنید:
resource.data.authorUID == request.auth.uid
شرط دوم برای به روز رسانی این است که دو ویژگی authorUID
و createdAt
نباید تغییر کنند:
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]);
و در نهایت، عنوان باید 50 کاراکتر یا کمتر باشد:
request.resource.data.title.size() < 50;
از آنجایی که همه این شرایط باید رعایت شوند، آنها را با &&
به هم بچسبانید:
allow update: if
// User is the author, and
resource.data.authorUID == request.auth.uid &&
// `authorUID` and `createdAt` are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
قوانین کامل تبدیل می شوند:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
allow create: if
// User creating document is draft author
request.auth.uid == request.resource.data.authorUID &&
// Must include title, author, and url fields
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
allow update: if
// User is the author, and
resource.data.authorUID == request.auth.uid &&
// `authorUID` and `createdAt` are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
}
}
}
تست های خود را مجدداً اجرا کنید و تأیید کنید که آزمون دیگری موفق شده است.
6. حذف و خواندن پیش نویس ها: کنترل دسترسی مبتنی بر ویژگی
همانطور که نویسندگان می توانند پیش نویس ها را ایجاد و به روز کنند، می توانند پیش نویس ها را نیز حذف کنند.
resource.data.authorUID == request.auth.uid
علاوه بر این، نویسندگانی که دارای ویژگی isModerator
در نشانه تأیید اعتبار خود هستند، مجاز به حذف پیشنویسها هستند:
request.auth.token.isModerator == true
از آنجایی که هر یک از این شرایط برای حذف کافی است، آنها را با یک عملگر منطقی OR، ||
:
allow delete: if resource.data.authorUID == request.auth.uid || request.auth.token.isModerator == true
شرایط مشابهی برای خواندن اعمال می شود، بنابراین می توان مجوز را به قانون اضافه کرد:
allow read, delete: if resource.data.authorUID == request.auth.uid || request.auth.token.isModerator == true
قوانین کامل اکنون عبارتند از:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
allow create: if
// User creating document is draft author
request.auth.uid == request.resource.data.authorUID &&
// Must include title, author, and url fields
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
allow update: if
// User is the author, and
resource.data.authorUID == request.auth.uid &&
// `authorUID` and `createdAt` are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
allow read, delete: if
// User is draft author
resource.data.authorUID == request.auth.uid ||
// User is a moderator
request.auth.token.isModerator == true;
}
}
}
تست های خود را مجدداً اجرا کنید و تأیید کنید که آزمون دیگری اکنون قبول شده است.
7. خواندن، ایجاد، و حذف برای پست های منتشر شده: غیر عادی برای الگوهای دسترسی مختلف
از آنجا که الگوهای دسترسی برای پست های منتشر شده و پست های پیش نویس بسیار متفاوت است، این برنامه پست ها را به مجموعه های draft
و published
جداگانه تبدیل می کند. به عنوان مثال، پستهای منتشر شده را میتوان هر کسی خواند، اما نمیتوان آن را به سختی حذف کرد، در حالی که پیشنویسها را میتوان حذف کرد، اما فقط توسط نویسنده و ناظران قابل خواندن است. در این برنامه، زمانی که کاربر می خواهد یک پیش نویس پست وبلاگ را منتشر کند، تابعی فعال می شود که پست منتشر شده جدید را ایجاد می کند.
در مرحله بعد، قوانین پست های منتشر شده را می نویسید. سادهترین قوانین برای نوشتن این است که پستهای منتشر شده توسط هر کسی قابل خواندن است و هیچکس نمیتواند ایجاد یا حذف کند. این قوانین را اضافه کنید:
match /published/{postID} {
// `authorUID`: string, required
// `content`: string, required
// `publishedAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, required
// `visible`: boolean, required
// Can be read by everyone
allow read: if true;
// Published posts are created only via functions, never by users
// No hard deletes; soft deletes update `visible` field.
allow create, delete: if false;
}
با افزودن اینها به قوانین موجود، کل فایل قوانین به صورت زیر در می آید:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
allow create: if
// User creating document is draft author
request.auth.uid == request.resource.data.authorUID &&
// Must include title, author, and url fields
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
allow update: if
// User is the author, and
resource.data.authorUID == request.auth.uid &&
// `authorUID` and `createdAt` are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]) &&
// Title must be < 50 characters long
request.resource.data.title.size() < 50;
allow read, delete: if
// User is draft author
resource.data.authorUID == request.auth.uid ||
// User is a moderator
request.auth.token.isModerator == true;
}
match /published/{postID} {
// `authorUID`: string, required
// `content`: string, required
// `publishedAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, required
// `visible`: boolean, required
// Can be read by everyone
allow read: if true;
// Published posts are created only via functions, never by users
// No hard deletes; soft deletes update `visible` field.
allow create, delete: if false;
}
}
}
تست ها را مجدداً اجرا کنید و تأیید کنید که آزمون دیگری موفق شده است.
8. به روز رسانی پست های منتشر شده: توابع سفارشی و متغیرهای محلی
شرایط به روز رسانی پست منتشر شده عبارتند از:
- آن را فقط نویسنده یا ناظر می تواند انجام دهد و
- باید شامل تمام فیلدهای مورد نیاز باشد.
از آنجایی که قبلاً شرایطی را برای نویسنده یا ناظر بودن نوشتهاید، میتوانید شرایط را کپی و جایگذاری کنید، اما به مرور زمان خواندن و حفظ آن دشوار میشود. در عوض، یک تابع سفارشی ایجاد میکنید که منطق نویسنده یا ناظم بودن را در بر میگیرد. سپس، شما آن را از شرایط چندگانه فراخوانی خواهید کرد.
یک تابع سفارشی ایجاد کنید
در بالای دستور تطبیق برای پیش نویس ها، یک تابع جدید به نام isAuthorOrModerator
ایجاد کنید که یک سند پست را به عنوان آرگومان می گیرد (این برای پیش نویس ها یا پست های منتشر شده کار می کند) و شی تأیید اعتبار کاربر:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Returns true if user is post author or a moderator
function isAuthorOrModerator(post, auth) {
}
match /drafts/{postID} {
allow create: ...
allow update: ...
...
}
match /published/{postID} {
allow read: ...
allow create, delete: ...
}
}
}
از متغیرهای محلی استفاده کنید
در داخل تابع، از کلمه کلیدی let
برای تنظیم متغیرهای isAuthor
و isModerator
استفاده کنید. همه توابع باید با یک عبارت return خاتمه پیدا کنند و تابع ما یک بولی برمی گرداند که نشان می دهد هر کدام از متغیرها درست است یا خیر:
function isAuthorOrModerator(post, auth) {
let isAuthor = auth.uid == post.authorUID;
let isModerator = auth.token.isModerator == true;
return isAuthor || isModerator;
}
تابع را فراخوانی کنید
اکنون قانون پیشنویسها را برای فراخوانی آن تابع بهروزرسانی میکنید، و مراقب باشید که به عنوان اولین آرگومان در resource.data
ارسال کنید:
// Draft blog posts
match /drafts/{draftID} {
...
// Can be deleted by author or moderator
allow read, delete: if isAuthorOrModerator(resource.data, request.auth);
}
اکنون می توانید شرطی برای به روز رسانی پست های منتشر شده بنویسید که از تابع جدید نیز استفاده می کند:
allow update: if isAuthorOrModerator(resource.data, request.auth);
اعتبارسنجی ها را اضافه کنید
برخی از فیلدهای یک پست منتشر شده نباید تغییر کنند، به ویژه فیلدهای url
، authorUID
و publishedAt
غیر قابل تغییر هستند. دو قسمت دیگر، title
و content
، و visible
باید پس از بهروزرسانی همچنان وجود داشته باشند. برای اعمال این الزامات برای بهروزرسانی پستهای منتشر شده، شرایطی را اضافه کنید:
// Immutable fields are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"publishedAt",
"url"
]) &&
// Required fields are present
request.resource.data.keys().hasAll([
"content",
"title",
"visible"
])
یک تابع سفارشی به تنهایی ایجاد کنید
و در نهایت یک شرط اضافه کنید که عنوان زیر 50 کاراکتر باشد. از آنجایی که این یک منطق استفاده مجدد است، می توانید این کار را با ایجاد یک تابع جدید، titleIsUnder50Chars
انجام دهید. با عملکرد جدید، شرط به روز رسانی یک پست منتشر شده به صورت زیر می شود:
allow update: if
isAuthorOrModerator(resource.data, request.auth) &&
// Immutable fields are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"publishedAt",
"url"
]) &&
// Required fields are present
request.resource.data.keys().hasAll([
"content",
"title",
"visible"
]) &&
titleIsUnder50Chars(request.resource.data);
و فایل قانون کامل این است:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Returns true if user is post author or a moderator
function isAuthorOrModerator(post, auth) {
let isAuthor = auth.uid == post.authorUID;
let isModerator = auth.token.isModerator == true;
return isAuthor || isModerator;
}
function titleIsUnder50Chars(post) {
return post.title.size() < 50;
}
// Draft blog posts
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
allow create: if
// User creating document is draft author
request.auth.uid == request.resource.data.authorUID &&
// Must include title, author, and url fields
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
]) &&
titleIsUnder50Chars(request.resource.data);
allow update: if
// User is the author, and
resource.data.authorUID == request.auth.uid &&
// `authorUID` and `createdAt` are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]) &&
titleIsUnder50Chars(request.resource.data);
// Can be read or deleted by author or moderator
allow read, delete: if isAuthorOrModerator(resource.data, request.auth);
}
// Published blog posts are denormalized from drafts
match /published/{postID} {
// `authorUID`: string, required
// `content`: string, required
// `publishedAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, required
// `visible`: boolean, required
// Can be read by everyone
allow read: if true;
// Published posts are created only via functions, never by users
// No hard deletes; soft deletes update `visible` field.
allow create, delete: if false;
allow update: if
isAuthorOrModerator(resource.data, request.auth) &&
// Immutable fields are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"publishedAt",
"url"
]) &&
// Required fields are present
request.resource.data.keys().hasAll([
"content",
"title",
"visible"
]) &&
titleIsUnder50Chars(request.resource.data);
}
}
}
تست ها را دوباره اجرا کنید. در این مرحله، شما باید 5 آزمون قبولی و 4 تست مردود داشته باشید.
9. نظرات: مجموعه های فرعی و مجوزهای ارائه دهنده ورود به سیستم
پستهای منتشر شده اجازه نظرات را میدهند و نظرات در زیر مجموعه پست منتشر شده ( /published/{postID}/comments/{commentID}
) ذخیره میشوند. به طور پیشفرض قوانین یک مجموعه برای زیر مجموعهها اعمال نمیشود. شما نمی خواهید همان قوانینی که در مورد سند اصلی پست منتشر شده اعمال می شود، در نظرات نیز اعمال شود. شما موارد مختلف را می سازید
برای نوشتن قوانین دسترسی به نظرات، با عبارت match شروع کنید:
match /published/{postID}/comments/{commentID} {
// `authorUID`: string, required
// `comment`: string, < 500 characters, required
// `createdAt`: timestamp, required
// `editedAt`: timestamp, optional
خواندن نظرات: نمی توان ناشناس بود
برای این برنامه، فقط کاربرانی که یک حساب دائمی ایجاد کرده اند، نه یک حساب ناشناس، می توانند نظرات را بخوانند. برای اجرای این قانون، ویژگی sign_in_provider
را که در هر شی auth.token
وجود دارد، جستجو کنید:
allow read: if request.auth.token.firebase.sign_in_provider != "anonymous";
تست های خود را مجدداً اجرا کنید و تأیید کنید که یک آزمون دیگر نیز موفق شده است.
ایجاد نظرات: بررسی لیست رد کردن
سه شرط برای ایجاد نظر وجود دارد:
- یک کاربر باید یک ایمیل تایید شده داشته باشد
- نظر باید کمتر از 500 کاراکتر باشد و
- آنها نمی توانند در لیستی از کاربران ممنوعه قرار بگیرند، که در firestore در مجموعه
bannedUsers
ذخیره می شود. در نظر گرفتن این شرایط یکی یکی:
request.auth.token.email_verified == true
request.resource.data.comment.size() < 500
!exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));
قانون نهایی برای ایجاد نظرات این است:
allow create: if
// User has verified email
(request.auth.token.email_verified == true) &&
// UID is not on bannedUsers list
!(exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));
کل فایل قوانین اکنون این است:
For bottom of step 9
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Returns true if user is post author or a moderator
function isAuthorOrModerator(post, auth) {
let isAuthor = auth.uid == post.authorUID;
let isModerator = auth.token.isModerator == true;
return isAuthor || isModerator;
}
function titleIsUnder50Chars(post) {
return post.title.size() < 50;
}
// Draft blog posts
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
allow create: if
// User is author
request.auth.uid == request.resource.data.authorUID &&
// Must include title, author, and createdAt fields
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
]) &&
titleIsUnder50Chars(request.resource.data);
allow update: if
// User is author
resource.data.authorUID == request.auth.uid &&
// `authorUID` and `createdAt` are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]) &&
titleIsUnder50Chars(request.resource.data);
// Can be read or deleted by author or moderator
allow read, delete: if isAuthorOrModerator(resource.data, request.auth);
}
// Published blog posts are denormalized from drafts
match /published/{postID} {
// `authorUID`: string, required
// `content`: string, required
// `publishedAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, required
// `visible`: boolean, required
// Can be read by everyone
allow read: if true;
// Published posts are created only via functions, never by users
// No hard deletes; soft deletes update `visible` field.
allow create, delete: if false;
allow update: if
isAuthorOrModerator(resource.data, request.auth) &&
// Immutable fields are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"publishedAt",
"url"
]) &&
// Required fields are present
request.resource.data.keys().hasAll([
"content",
"title",
"visible"
]) &&
titleIsUnder50Chars(request.resource.data);
}
match /published/{postID}/comments/{commentID} {
// `authorUID`: string, required
// `createdAt`: timestamp, required
// `editedAt`: timestamp, optional
// `comment`: string, < 500 characters, required
// Must have permanent account to read comments
allow read: if !(request.auth.token.firebase.sign_in_provider == "anonymous");
allow create: if
// User has verified email
request.auth.token.email_verified == true &&
// Comment is under 500 characters
request.resource.data.comment.size() < 500 &&
// UID is not on the block list
!exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));
}
}
}
تست ها را مجدداً اجرا کنید و مطمئن شوید که یک آزمون دیگر نیز قبول شده است.
10. به روز رسانی نظرات: قوانین مبتنی بر زمان
منطق تجاری برای نظرات این است که آنها می توانند توسط نویسنده نظر برای یک ساعت پس از ایجاد ویرایش شوند. برای پیاده سازی این، از timestamp createdAt
استفاده کنید.
ابتدا، برای تعیین اینکه کاربر نویسنده است:
request.auth.uid == resource.data.authorUID
بعد، اینکه نظر در یک ساعت گذشته ایجاد شده است:
(request.time - resource.data.createdAt) < duration.value(1, 'h');
با ترکیب این ها با عملگر AND منطقی، قانون به روز رسانی نظرات به صورت زیر در می آید:
allow update: if
// is author
request.auth.uid == resource.data.authorUID &&
// within an hour of comment creation
(request.time - resource.data.createdAt) < duration.value(1, 'h');
تست ها را مجدداً اجرا کنید و مطمئن شوید که یک آزمون دیگر نیز قبول شده است.
11. حذف نظرات: بررسی مالکیت والدین
نظرات می تواند توسط نویسنده نظر، مدیر یا نویسنده پست وبلاگ حذف شود.
اول، چون تابع کمکی که قبلا اضافه کردید، فیلد authorUID
را بررسی میکند که میتواند در یک پست یا یک نظر وجود داشته باشد، میتوانید برای بررسی اینکه آیا کاربر نویسنده یا ناظر است، از تابع کمکی دوباره استفاده کنید:
isAuthorOrModerator(resource.data, request.auth)
برای بررسی اینکه آیا کاربر نویسنده پست وبلاگ است، از یک get
برای جستجوی پست در Firestore استفاده کنید:
request.auth.uid == get(/databases/$(database)/documents/published/$(postID)).data.authorUID
از آنجا که هر یک از این شرایط کافی است، از یک عملگر OR منطقی بین آنها استفاده کنید:
allow delete: if
// is comment author or moderator
isAuthorOrModerator(resource.data, request.auth) ||
// is blog post author
request.auth.uid == get(/databases/$(database)/documents/published/$(postID)).data.authorUID;
تست ها را مجدداً اجرا کنید و مطمئن شوید که یک آزمون دیگر نیز قبول شده است.
و کل فایل قوانین این است:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Returns true if user is post author or a moderator
function isAuthorOrModerator(post, auth) {
let isAuthor = auth.uid == post.authorUID;
let isModerator = auth.token.isModerator == true;
return isAuthor || isModerator;
}
function titleIsUnder50Chars(post) {
return post.title.size() < 50;
}
// Draft blog posts
match /drafts/{draftID} {
// `authorUID`: string, required
// `content`: string, optional
// `createdAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, optional
allow create: if
// User is author
request.auth.uid == request.resource.data.authorUID &&
// Must include title, author, and createdAt fields
request.resource.data.keys().hasAll([
"authorUID",
"createdAt",
"title"
]) &&
titleIsUnder50Chars(request.resource.data);
allow update: if
// User is author
resource.data.authorUID == request.auth.uid &&
// `authorUID` and `createdAt` are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"createdAt"
]) &&
titleIsUnder50Chars(request.resource.data);
// Can be read or deleted by author or moderator
allow read, delete: if isAuthorOrModerator(resource.data, request.auth);
}
// Published blog posts are denormalized from drafts
match /published/{postID} {
// `authorUID`: string, required
// `content`: string, required
// `publishedAt`: timestamp, required
// `title`: string, < 50 characters, required
// `url`: string, required
// `visible`: boolean, required
// Can be read by everyone
allow read: if true;
// Published posts are created only via functions, never by users
// No hard deletes; soft deletes update `visible` field.
allow create, delete: if false;
allow update: if
isAuthorOrModerator(resource.data, request.auth) &&
// Immutable fields are unchanged
request.resource.data.diff(resource.data).unchangedKeys().hasAll([
"authorUID",
"publishedAt",
"url"
]) &&
// Required fields are present
request.resource.data.keys().hasAll([
"content",
"title",
"visible"
]) &&
titleIsUnder50Chars(request.resource.data);
}
match /published/{postID}/comments/{commentID} {
// `authorUID`: string, required
// `createdAt`: timestamp, required
// `editedAt`: timestamp, optional
// `comment`: string, < 500 characters, required
// Must have permanent account to read comments
allow read: if !(request.auth.token.firebase.sign_in_provider == "anonymous");
allow create: if
// User has verified email
request.auth.token.email_verified == true &&
// Comment is under 500 characters
request.resource.data.comment.size() < 500 &&
// UID is not on the block list
!exists(/databases/$(database)/documents/bannedUsers/$(request.auth.uid));
allow update: if
// is author
request.auth.uid == resource.data.authorUID &&
// within an hour of comment creation
(request.time - resource.data.createdAt) < duration.value(1, 'h');
allow delete: if
// is comment author or moderator
isAuthorOrModerator(resource.data, request.auth) ||
// is blog post author
request.auth.uid == get(/databases/$(database)/documents/published/$(postID)).data.authorUID;
}
}
}
12. مراحل بعدی
تبریک می گویم! شما قوانین امنیتی را نوشتهاید که باعث میشود همه آزمونها قبول شوند و برنامه را ایمن کنند!
در اینجا چند موضوع مرتبط وجود دارد که در ادامه میتوانید به آنها بپردازید: