این صفحه بر پایه مفاهیم ساختار قوانین امنیتی و شرایط نوشتن برای قوانین امنیتی است تا توضیح دهد که چگونه می توانید Cloud Firestore Security Rules برای ایجاد قوانینی استفاده کنید که به مشتریان اجازه می دهد عملیات را روی برخی از فیلدها در یک سند انجام دهند اما برخی دیگر را نه.
ممکن است مواقعی پیش بیاید که بخواهید تغییرات سند را نه در سطح سند بلکه در سطح زمینه کنترل کنید.
به عنوان مثال، ممکن است بخواهید به یک کلاینت اجازه ایجاد یا تغییر یک سند را بدهید، اما به آنها اجازه ندهید که فیلدهای خاصی را در آن سند ویرایش کنند. یا ممکن است بخواهید اعمال کنید که هر سندی که مشتری همیشه ایجاد می کند شامل مجموعه خاصی از فیلدها باشد. این راهنما نحوه انجام برخی از این وظایف را با استفاده از Cloud Firestore Security Rules پوشش می دهد.
اجازه دسترسی خواندن فقط برای فیلدهای خاص
خواندن در Cloud Firestore در سطح سند انجام می شود. شما یا سند کامل را بازیابی می کنید یا چیزی را بازیابی نمی کنید. هیچ راهی برای بازیابی سند جزئی وجود ندارد. استفاده از قوانین امنیتی به تنهایی برای جلوگیری از خواندن فیلدهای خاص در یک سند غیرممکن است.
اگر فیلدهای خاصی در یک سند وجود دارد که می خواهید آنها را از دید برخی کاربران پنهان نگه دارید، بهترین راه این است که آنها را در یک سند جداگانه قرار دهید. به عنوان مثال، ممکن است ایجاد یک سند در یک مجموعه فرعی private
مانند موارد زیر را در نظر بگیرید:
/employees/{emp_id}
name: "Alice Hamilton",
department: 461,
start_date: <timestamp>
/employees/{emp_id}/private/finances
salary: 80000,
bonus_mult: 1.25,
perf_review: 4.2
سپس می توانید قوانین امنیتی را اضافه کنید که سطوح دسترسی متفاوتی برای دو مجموعه دارند. در این مثال، ما از ادعاهای تأیید اعتبار سفارشی استفاده میکنیم تا بگوییم که فقط کاربرانی که role
ادعای تأیید اعتبار سفارشی برابر با Finance
دارند، میتوانند اطلاعات مالی یک کارمند را مشاهده کنند.
service cloud.firestore {
match /databases/{database}/documents {
// Allow any logged in user to view the public employee data
match /employees/{emp_id} {
allow read: if request.resource.auth != null
// Allow only users with the custom auth claim of "Finance" to view
// the employee's financial data
match /private/finances {
allow read: if request.resource.auth &&
request.resource.auth.token.role == 'Finance'
}
}
}
}
محدود کردن فیلدها در ایجاد سند
Cloud Firestore بدون طرح است، به این معنی که هیچ محدودیتی در سطح پایگاه داده برای فیلدهای یک سند وجود ندارد. در حالی که این انعطافپذیری میتواند توسعه را آسانتر کند، مواقعی وجود خواهد داشت که میخواهید اطمینان حاصل کنید که کلاینتها فقط میتوانند اسنادی ایجاد کنند که حاوی فیلدهای خاص هستند یا حاوی فیلدهای دیگر نیستند.
می توانید این قوانین را با بررسی روش keys
شی request.resource.data
ایجاد کنید. این لیستی از تمام فیلدهایی است که مشتری سعی دارد در این سند جدید بنویسد. با ترکیب این مجموعه از فیلدها با توابعی مانند hasOnly()
یا hasAny()
، می توانید منطقی را اضافه کنید که انواع اسنادی را که کاربر می تواند به Cloud Firestore اضافه کند محدود می کند.
نیاز به فیلدهای خاص در اسناد جدید
فرض کنید میخواستید مطمئن شوید که تمام اسناد ایجاد شده در مجموعه restaurant
حاوی حداقل name
، location
و فیلد city
هستند. می توانید این کار را با فراخوانی hasAll()
در لیست کلیدهای سند جدید انجام دهید.
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document contains a name
// location, and city field
match /restaurant/{restId} {
allow create: if request.resource.data.keys().hasAll(['name', 'location', 'city']);
}
}
}
این اجازه می دهد تا رستوران ها با فیلدهای دیگر نیز ایجاد شوند، اما تضمین می کند که تمام اسناد ایجاد شده توسط مشتری حداقل شامل این سه فیلد باشد.
ممنوعیت فیلدهای خاص در اسناد جدید
به طور مشابه، میتوانید با استفاده از hasAny()
در برابر فهرستی از فیلدهای ممنوعه، از ایجاد اسنادی که حاوی فیلدهای خاص هستند توسط کلاینتها جلوگیری کنید. اگر سندی حاوی هر یک از این فیلدها باشد، این روش به درستی ارزیابی میکند، بنابراین احتمالاً میخواهید نتیجه را نفی کنید تا فیلدهای خاصی را ممنوع کنید.
به عنوان مثال، در مثال زیر، مشتریان مجاز به ایجاد سندی نیستند که حاوی یک فیلد average_score
یا rating_count
باشد، زیرا این فیلدها با فراخوانی سرور در مرحله بعد اضافه خواهند شد.
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document does *not*
// contain an average_score or rating_count field.
match /restaurant/{restId} {
allow create: if (!request.resource.data.keys().hasAny(
['average_score', 'rating_count']));
}
}
}
ایجاد لیست مجاز از فیلدها برای اسناد جدید
به جای ممنوع کردن فیلدهای خاص در اسناد جدید، ممکن است بخواهید فهرستی از آن قسمت هایی ایجاد کنید که به صراحت در اسناد جدید مجاز هستند. سپس میتوانید از تابع hasOnly()
استفاده کنید تا مطمئن شوید که هر سند جدیدی که ایجاد میشود فقط شامل این فیلدها (یا زیرمجموعهای از این فیلدها) باشد و نه دیگری.
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document doesn't contain
// any fields besides the ones listed below.
match /restaurant/{restId} {
allow create: if (request.resource.data.keys().hasOnly(
['name', 'location', 'city', 'address', 'hours', 'cuisine']));
}
}
}
ترکیب فیلدهای ضروری و اختیاری
می توانید عملیات hasAll
و hasOnly
را با هم در قوانین امنیتی خود ترکیب کنید تا به برخی از فیلدها نیاز داشته باشید و برخی دیگر را مجاز کنید. به عنوان مثال، این مثال مستلزم آن است که تمام اسناد جدید حاوی name
، location
و فیلدهای city
باشند و به صورت اختیاری فیلدهای address
، hours
و cuisine
را مجاز کند.
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document has a name,
// location, and city field, and optionally address, hours, or cuisine field
match /restaurant/{restId} {
allow create: if (request.resource.data.keys().hasAll(['name', 'location', 'city'])) &&
(request.resource.data.keys().hasOnly(
['name', 'location', 'city', 'address', 'hours', 'cuisine']));
}
}
}
در یک سناریوی واقعی، ممکن است بخواهید این منطق را به یک تابع کمکی منتقل کنید تا از تکرار کد خود جلوگیری کنید و به راحتی فیلدهای اختیاری و مورد نیاز را در یک لیست واحد ترکیب کنید، مانند:
service cloud.firestore {
match /databases/{database}/documents {
function verifyFields(required, optional) {
let allAllowedFields = required.concat(optional);
return request.resource.data.keys().hasAll(required) &&
request.resource.data.keys().hasOnly(allAllowedFields);
}
match /restaurant/{restId} {
allow create: if verifyFields(['name', 'location', 'city'],
['address', 'hours', 'cuisine']);
}
}
}
محدود کردن فیلدها در به روز رسانی
یک روش امنیتی رایج این است که فقط به مشتریان اجازه میدهد برخی از فیلدها را ویرایش کنند و برخی دیگر را ویرایش نکنند. شما نمی توانید این کار را صرفاً با مشاهده لیست request.resource.data.keys()
که در بخش قبل توضیح داده شد انجام دهید، زیرا این لیست نمایانگر سند کامل است همانطور که بعد از به روز رسانی نگاه می کند و بنابراین شامل فیلدهایی می شود که مشتری آن ها را انجام نداده است. تغییر دهید.
با این حال، اگر میخواهید از تابع diff()
استفاده کنید، میتوانید request.resource.data
با شی resource.data
مقایسه کنید، که سند موجود در پایگاه داده را قبل از بهروزرسانی نشان میدهد. این یک شی mapDiff
ایجاد می کند، که یک شی حاوی تمام تغییرات بین دو نقشه مختلف است.
با فراخوانی متد affectedKeys()
در این mapDiff، میتوانید مجموعهای از فیلدها را که در یک ویرایش تغییر کردهاند به دست آورید. سپس می توانید از توابعی مانند hasOnly()
یا hasAny()
استفاده کنید تا اطمینان حاصل کنید که این مجموعه شامل موارد خاصی است (یا نیست).
جلوگیری از تغییر برخی فیلدها
با استفاده از متد hasAny()
در مجموعه تولید شده توسط affectedKeys()
و سپس نفی نتیجه، میتوانید هر درخواست مشتری را که سعی در تغییر فیلدهایی که نمیخواهید تغییر کند رد کنید.
به عنوان مثال، ممکن است بخواهید به مشتریان اجازه دهید اطلاعات مربوط به یک رستوران را به روز کنند، اما میانگین امتیاز یا تعداد نظرات خود را تغییر ندهید.
service cloud.firestore {
match /databases/{database}/documents {
match /restaurant/{restId} {
// Allow the client to update a document only if that document doesn't
// change the average_score or rating_count fields
allow update: if (!request.resource.data.diff(resource.data).affectedKeys()
.hasAny(['average_score', 'rating_count']));
}
}
}
امکان تغییر فقط فیلدهای خاص
به جای تعیین فیلدهایی که نمی خواهید تغییر کنند، می توانید از تابع hasOnly()
نیز برای تعیین لیستی از فیلدهایی که می خواهید تغییر کنند استفاده کنید. این معمولاً امنتر در نظر گرفته میشود، زیرا نوشتن در هر فیلد سند جدید بهطور پیشفرض مجاز نیست تا زمانی که به صراحت آنها را در قوانین امنیتی خود مجاز نکنید.
به عنوان مثال، به جای غیر مجاز کردن فیلد average_score
و rating_count
، میتوانید قوانین امنیتی ایجاد کنید که به مشتریان اجازه میدهد فقط name
، location
، city
، address
، hours
، و قسمتهای cuisine
را تغییر دهند.
service cloud.firestore {
match /databases/{database}/documents {
match /restaurant/{restId} {
// Allow a client to update only these 6 fields in a document
allow update: if (request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['name', 'location', 'city', 'address', 'hours', 'cuisine']));
}
}
}
این بدان معنی است که اگر در برخی از تکرارهای بعدی برنامه شما، اسناد رستوران شامل یک فیلد telephone
باشد، تلاش برای ویرایش آن فیلد ناموفق خواهد بود تا زمانی که به عقب برگردید و آن فیلد را به لیست hasOnly()
در قوانین امنیتی خود اضافه کنید.
اعمال انواع فیلدها
یکی دیگر از اثرات بدون طرح و طرح Cloud Firestore این است که هیچ گونه اعمالی در سطح پایگاه داده برای اینکه چه نوع داده هایی را می توان در فیلدهای خاص ذخیره کرد وجود ندارد. این چیزی است که می توانید در قوانین امنیتی اعمال کنید، اما با اپراتور is
.
بهعنوان مثال، قانون امنیتی زیر بیان میکند که فیلد score
مرور باید یک عدد صحیح باشد، فیلدهای headline
، content
و author_name
رشتهها هستند و review_date
یک مهر زمانی است.
service cloud.firestore {
match /databases/{database}/documents {
match /restaurant/{restId} {
// Restaurant rules go here...
match /review/{reviewId} {
allow create: if (request.resource.data.score is int &&
request.resource.data.headline is string &&
request.resource.data.content is string &&
request.resource.data.author_name is string &&
request.resource.data.review_date is timestamp
);
}
}
}
}
انواع داده های معتبر برای عملگر is
عبارتند از bool
, bytes
, float
, int
, list
, latlng
, number
, path
, map
, string
و timestamp
. عملگر is
همچنین از انواع دادههای constraint
، duration
، set
و map_diff
پشتیبانی میکند، اما از آنجایی که اینها توسط خود زبان قوانین امنیتی تولید میشوند و توسط کلاینتها تولید نمیشوند، شما به ندرت از آنها در اکثر برنامههای کاربردی استفاده میکنید.
انواع داده های list
و map
از ژنریک ها یا آرگومان های نوع پشتیبانی نمی کنند. به عبارت دیگر، شما می توانید از قوانین امنیتی برای اعمال این موضوع استفاده کنید که یک فیلد خاص حاوی یک لیست یا یک نقشه باشد، اما نمی توانید اعمال کنید که یک فیلد حاوی لیستی از تمام اعداد صحیح یا تمام رشته ها باشد.
به طور مشابه، میتوانید از قوانین امنیتی برای اعمال مقادیر نوع برای ورودیهای خاص در یک فهرست یا نقشه (به ترتیب با استفاده از علامتگذاری براکت یا نام کلید) استفاده کنید، اما هیچ میانبری برای اعمال انواع دادههای همه اعضا در یک نقشه یا یک فهرست وجود ندارد. یک بار
به عنوان مثال، قوانین زیر تضمین می کند که یک فیلد tags
در یک سند حاوی یک لیست است و اولین ورودی یک رشته است. همچنین تضمین می کند که فیلد product
حاوی یک نقشه است که به نوبه خود حاوی نام محصول است که یک رشته و یک مقدار است که یک عدد صحیح است.
service cloud.firestore {
match /databases/{database}/documents {
match /orders/{orderId} {
allow create: if request.resource.data.tags is list &&
request.resource.data.tags[0] is string &&
request.resource.data.product is map &&
request.resource.data.product.name is string &&
request.resource.data.product.quantity is int
}
}
}
}
انواع فیلدها باید هنگام ایجاد و به روز رسانی یک سند اعمال شوند. بنابراین، ممکن است بخواهید ایجاد یک تابع کمکی را در نظر بگیرید که می توانید آن را در بخش ایجاد و به روز رسانی قوانین امنیتی خود فراخوانی کنید.
service cloud.firestore {
match /databases/{database}/documents {
function reviewFieldsAreValidTypes(docData) {
return docData.score is int &&
docData.headline is string &&
docData.content is string &&
docData.author_name is string &&
docData.review_date is timestamp;
}
match /restaurant/{restId} {
// Restaurant rules go here...
match /review/{reviewId} {
allow create: if reviewFieldsAreValidTypes(request.resource.data) &&
// Other rules may go here
allow update: if reviewFieldsAreValidTypes(request.resource.data) &&
// Other rules may go here
}
}
}
}
اعمال انواع برای فیلدهای اختیاری
مهم است که به یاد داشته باشید که فراخوانی request.resource.data.foo
در سندی که foo
وجود ندارد منجر به خطا می شود و بنابراین هر قانون امنیتی که آن تماس را ایجاد کند، درخواست را رد می کند. می توانید با استفاده از روش get
در request.resource.data
این وضعیت را مدیریت کنید. متد get
به شما امکان می دهد در صورتی که آن فیلد وجود نداشته باشد، یک آرگومان پیش فرض برای فیلدی که از نقشه بازیابی می کنید، ارائه دهید.
برای مثال، اگر اسناد بررسی همچنین حاوی یک فیلد photo_url
اختیاری و یک فیلد tags
اختیاری است که میخواهید به ترتیب رشتهها و فهرستها را تأیید کنید، میتوانید با بازنویسی تابع reviewFieldsAreValidTypes
به چیزی شبیه به زیر این کار را انجام دهید:
function reviewFieldsAreValidTypes(docData) {
return docData.score is int &&
docData.headline is string &&
docData.content is string &&
docData.author_name is string &&
docData.review_date is timestamp &&
docData.get('photo_url', '') is string &&
docData.get('tags', []) is list;
}
با این کار اسنادی که tags
وجود دارند را رد میکند، اما فهرستی نیست، در حالی که همچنان به اسنادی اجازه میدهد که حاوی فیلد tags
(یا photo_url
) نیستند.
نوشتن جزئی هرگز مجاز نیست
آخرین نکته در مورد Cloud Firestore Security Rules این است که آنها یا به مشتری اجازه می دهند در یک سند تغییر ایجاد کند یا کل ویرایش را رد می کنند. شما نمی توانید قوانین امنیتی ایجاد کنید که نوشتن برخی از فیلدها را در سند خود بپذیرد و در عین حال برخی دیگر را در همان عملیات رد کنید.