אבטחה יכולה להיות אחד החלקים המורכבים ביותר בפאזל פיתוח האפליקציה. ברוב היישומים, מפתחים חייבים לבנות ולהפעיל שרת שמטפל באימות (מי זה משתמש) והרשאה (מה משתמש יכול לעשות).
כללי האבטחה של Firebase מסירים את השכבה האמצעית (שרת) ומאפשרים לך לציין הרשאות מבוססות נתיב עבור לקוחות שמתחברים לנתונים שלך ישירות. השתמש במדריך זה כדי ללמוד עוד על אופן החלת הכללים על בקשות נכנסות.
בחר מוצר כדי ללמוד עוד על הכללים שלו.
Cloud Firestore
מבנה בסיסי
כללי האבטחה של Firebase ב-Cloud Firestore ו-Cloud Storage משתמשים במבנה ובתחביר הבאים:
service <<name>> {
// Match the resource path.
match <<path>> {
// Allow the request if the following conditions are true.
allow <<methods>> : if <<condition>>
}
}
חשוב להבין את מושגי המפתח הבאים בעת בניית הכללים:
- בקשה: השיטה או השיטות שהופעלו בהצהרת
allow
. אלו שיטות שאתה מאפשר להפעיל. השיטות הסטנדרטיות הן:get
,list
,create
,update
delete
. שיטות נוחותread
write
מאפשרות גישת קריאה וכתיבה רחבה במסד הנתונים או נתיב האחסון שצוין. - נתיב: מסד הנתונים או מיקום האחסון, המיוצג כנתיב URI.
- כלל: הצהרת
allow
, הכוללת תנאי שמתיר בקשה אם היא מוערכת כאמת.
כללי אבטחה גרסה 2
החל ממאי 2019, גרסה 2 של כללי האבטחה של Firebase זמינה כעת. גרסה 2 של הכללים משנה את ההתנהגות של תווים כלליים רקורסיביים {name=**}
. עליך להשתמש בגרסה 2 אם אתה מתכנן להשתמש בשאילתות קבוצת אוסף . עליך להצטרף לגרסה 2 על ידי הפיכת rules_version = '2';
השורה הראשונה בכללי האבטחה שלך:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
נתיבים תואמים
כל הצהרות ההתאמה צריכות להצביע על מסמכים, לא על אוספים. הצהרת התאמה יכולה להצביע על מסמך ספציפי, כמו ב- match /cities/SF
או להשתמש בתווים כלליים כדי להצביע על כל מסמך בנתיב שצוין, כמו ב- match /cities/{city}
.
בדוגמה שלמעלה, הצהרת ההתאמה משתמשת בתחביר התווים הכלליים {city}
. המשמעות היא שהכלל חל על כל מסמך באוסף cities
, כגון /cities/SF
או /cities/NYC
. כאשר ביטויי allow
בהצהרת ההתאמה מוערכים, משתנה city
יעבור לשם מסמך העיר, כגון SF
או NYC
.
אוספי משנה תואמים
הנתונים ב-Cloud Firestore מאורגנים באוספים של מסמכים, וכל מסמך עשוי להרחיב את ההיררכיה באמצעות תת-אוספים. חשוב להבין כיצד כללי אבטחה מתקשרים עם נתונים היררכיים.
קחו בחשבון את המצב שבו כל מסמך באוסף cities
מכיל תת-אוסף landmarks
. כללי האבטחה חלים רק בנתיב המותאם, כך שבקרות הגישה המוגדרות באוסף cities
אינן חלות על אוסף landmarks
. במקום זאת, כתוב כללים מפורשים כדי לשלוט בגישה לאוספי משנה:
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
allow read, write: if <condition>;
// Explicitly define rules for the 'landmarks' subcollection
match /landmarks/{landmark} {
allow read, write: if <condition>;
}
}
}
}
בעת קינון הצהרות match
, הנתיב של הצהרת match
הפנימית תמיד ביחס לנתיב של הצהרת match
החיצונית. לכן ערכי הכללים הבאים שווים:
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city} {
match /landmarks/{landmark} {
allow read, write: if <condition>;
}
}
}
}
service cloud.firestore {
match /databases/{database}/documents {
match /cities/{city}/landmarks/{landmark} {
allow read, write: if <condition>;
}
}
}
תווים כלליים רקורסיביים
אם אתה רוצה שהכללים יחולו על היררכיה עמוקה באופן שרירותי, השתמש בתחביר התווים הכלליים הרקורסיבים, {name=**}
:
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the cities collection as well as any document
// in a subcollection.
match /cities/{document=**} {
allow read, write: if <condition>;
}
}
}
בעת שימוש בתחביר התווים הכלליים הרקורסיבים, משתנה התווים הכלליים יכיל את כל מקטע הנתיב התואם, גם אם המסמך ממוקם בתת-אוסף מקונן עמוק. לדוגמה, הכללים המפורטים למעלה יתאימו למסמך הממוקם ב- /cities/SF/landmarks/coit_tower
, והערך של משתנה document
יהיה SF/landmarks/coit_tower
.
שים לב, עם זאת, ההתנהגות של תווים כלליים רקורסיביים תלויה בגרסת הכללים.
גרסה 1
כללי אבטחה משתמשים בגרסה 1 כברירת מחדל. בגרסה 1, תווים כלליים רקורסיביים תואמים פריט נתיב אחד או יותר. הם אינם תואמים נתיב ריק, כך ש- match /cities/{city}/{document=**}
תואם מסמכים בתתי-אוספים אך לא באוסף cities
, ואילו match /cities/{document=**}
מתאים לשני המסמכים ב- אוסף cities
ואוספים משנה.
תווים כלליים רקורסיביים חייבים להגיע בסוף הצהרת התאמה.
גרסה 2
בגרסה 2 של כללי האבטחה, תווים כלליים רקורסיביים תואמים לאפס פריטי נתיב או יותר. match/cities/{city}/{document=**}
תואם מסמכים בכל תת-אוספים כמו גם מסמכים באוסף cities
.
עליך להצטרף לגרסה 2 על ידי הוספת rules_version = '2';
בראש כללי האבטחה שלך:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the cities collection as well as any document
// in a subcollection.
match /cities/{city}/{document=**} {
allow read, write: if <condition>;
}
}
}
אתה יכול לקבל לכל היותר תו כללי רקורסיבי אחד לכל הצהרת התאמה, אבל בגרסה 2, אתה יכול למקם את התו הכללי הזה בכל מקום בהצהרת ההתאמה. לדוגמה:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the songs collection group
match /{path=**}/songs/{song} {
allow read, write: if <condition>;
}
}
}
אם אתה משתמש בשאילתות קבוצת איסוף , עליך להשתמש בגרסה 2, ראה אבטחת שאילתות קבוצת איסוף .
הצהרות התאמה חופפות
ייתכן שמסמך יתאים ליותר מהצהרת match
אחת. במקרה שבו מספר ביטויי allow
תואמים בקשה, הגישה מותרת אם אחד מהתנאים true
:
service cloud.firestore {
match /databases/{database}/documents {
// Matches any document in the 'cities' collection.
match /cities/{city} {
allow read, write: if false;
}
// Matches any document in the 'cities' collection or subcollections.
match /cities/{document=**} {
allow read, write: if true;
}
}
}
בדוגמה שלמעלה, כל הקריאה והכתיבה לאוסף cities
יתאפשרו מכיוון שהכלל השני תמיד true
, למרות שהכלל הראשון תמיד false
.
מגבלות כללי אבטחה
כשאתה עובד עם כללי אבטחה, שים לב למגבלות הבאות:
לְהַגבִּיל | פרטים |
---|---|
המספר המרבי של exists() , get() ו- getAfter() לכל בקשה |
חריגה משתי הגבלות גורמת לשגיאה שנדחתה. חלק משיחות הגישה למסמכים עשויות להיות מאוחסנות במטמון, ושיחות במטמון אינן נחשבות למגבלות. |
עומק מקסימלי של הצהרת match מקוננת | 10 |
אורך נתיב מרבי, בקטעי נתיב, מותר בתוך קבוצה של הצהרות match מקוננות | 100 |
המספר המרבי של משתני לכידת נתיב המותר בתוך קבוצה של הצהרות match מקוננות | 20 |
עומק קריאת פונקציה מקסימלי | 20 |
מספר מקסימלי של ארגומנטים של פונקציה | 7 |
המספר המרבי של כריכות משתנה let לכל פונקציה | 10 |
המספר המרבי של קריאות פונקציות רקורסיביות או מחזוריות | 0 (לא מותר) |
מספר מקסימלי של ביטויים שהוערך לכל בקשה | 1,000 |
גודל מקסימלי של ערכת כללים | על ערכות הכללים לציית לשתי מגבלות גודל:
|
אחסון בענן
מבנה בסיסי
כללי האבטחה של Firebase ב-Cloud Firestore ו-Cloud Storage משתמשים במבנה ובתחביר הבאים:
service <<name>> {
// Match the resource path.
match <<path>> {
// Allow the request if the following conditions are true.
allow <<methods>> : if <<condition>>
}
}
חשוב להבין את מושגי המפתח הבאים בעת בניית הכללים:
- בקשה: השיטה או השיטות שהופעלו בהצהרת
allow
. אלו הן שיטות שאתה מאפשר להפעיל. השיטות הסטנדרטיות הן:get
,list
,create
,update
delete
. שיטות נוחותread
write
מאפשרות גישת קריאה וכתיבה רחבה במסד הנתונים או נתיב האחסון שצוין. - נתיב: מסד הנתונים או מיקום האחסון, המיוצג כנתיב URI.
- כלל: הצהרת
allow
, הכוללת תנאי שמתיר בקשה אם היא מוערכת כאמת.
נתיבים תואמים
כללי האבטחה של Cloud Storage match
לנתיבי הקבצים המשמשים לגישה לקבצים ב-Cloud Storage. כללים יכולים match
לנתיבים מדויקים או לנתיבי תווים כלליים, וניתן גם לקנן כללים. אם אין כלל התאמה מאפשר שיטת בקשה, או שהתנאי מוערך ל- false
, הבקשה נדחית.
התאמות מדויקות
// Exact match for "images/profilePhoto.png" match /images/profilePhoto.png { allow write: if <condition>; } // Exact match for "images/croppedProfilePhoto.png" match /images/croppedProfilePhoto.png { allow write: if <other_condition>; }
גפרורים מקוננים
// Partial match for files that start with "images" match /images { // Exact match for "images/profilePhoto.png" match /profilePhoto.png { allow write: if <condition>; } // Exact match for "images/croppedProfilePhoto.png" match /croppedProfilePhoto.png { allow write: if <other_condition>; } }
התאמות עם תו כללי
ניתן להשתמש בכללים גם כדי match
דפוס באמצעות תווים כלליים. תו כללי הוא משתנה בעל שם המייצג מחרוזת בודדת כגון profilePhoto.png
, או מקטעי נתיב מרובים, כגון images/profilePhoto.png
.
תו כללי נוצר על ידי הוספת סוגרים מסולסלים סביב שם התווים הכלליים, כמו {string}
. ניתן להכריז על תו כללי של מקטע מרובה על ידי הוספת =**
לשם התווים הכלליים, כמו {path=**}
:
// Partial match for files that start with "images" match /images { // Exact match for "images/*" // e.g. images/profilePhoto.png is matched match /{imageId} { // This rule only matches a single path segment (*) // imageId is a string that contains the specific segment matched allow read: if <condition>; } // Exact match for "images/**" // e.g. images/users/user:12345/profilePhoto.png is matched // images/profilePhoto.png is also matched! match /{allImages=**} { // This rule matches one or more path segments (**) // allImages is a path that contains all segments matched allow read: if <other_condition>; } }
אם כללים מרובים תואמים לקובץ, התוצאה היא ה- OR
של התוצאה של כל הערכות הכללים. כלומר, אם כלל כלשהו שהקובץ תואם evalutes ל- true
, התוצאה היא true
.
בכללים שלמעלה, ניתן לקרוא את הקובץ "images/profilePhoto.png" אם condition
או other_condition
מוערכים כ-true, בעוד שהקובץ "images/users/user:12345/profilePhoto.png" כפוף רק לתוצאה של other_condition
.
ניתן להפנות למשתנה תווים כלליים מתוך match
לספק שם קובץ או הרשאת נתיב:
// Another way to restrict the name of a file match /images/{imageId} { allow read: if imageId == "profilePhoto.png"; }
כללי האבטחה של Cloud Storage אינם משתפכים, וכללים מוערכים רק כאשר נתיב הבקשה תואם נתיב עם כללים שצוינו.
בקש הערכה
העלאות, הורדות, שינויים במטא נתונים ומחיקות מוערכים באמצעות request
הנשלחת ל-Cloud Storage. משתנה request
מכיל את נתיב הקובץ שבו מתבצעת הבקשה, את השעה שבה הבקשה מתקבלת וערך resource
החדש אם הבקשה היא כתיבה. כותרות HTTP ומצב אימות כלולים גם כן.
אובייקט request
מכיל גם את המזהה הייחודי של המשתמש ואת מטען האימות של Firebase באובייקט request.auth
, שיוסבר עוד בסעיף האימות של המסמכים.
רשימה מלאה של מאפיינים באובייקט request
זמינה להלן:
תכונה | סוּג | תיאור |
---|---|---|
auth | map<string, string> | כאשר משתמש מחובר, מספק uid , המזהה הייחודי token של המשתמש, טוענת מפה של Firebase Authentication JWT. אחרת, הוא יהיה null . |
params | map<string, string> | מפה המכילה את פרמטרי השאילתה של הבקשה. |
path | נָתִיב | path המייצג את הנתיב שבו מבוצעת הבקשה. |
resource | map<string, string> | ערך המשאב החדש, קיים רק בבקשות write . |
time | חותמת זמן | חותמת זמן המייצגת את זמן השרת שבו הבקשה מוערכת. |
הערכת משאבים
בעת הערכת כללים, ייתכן שתרצה גם להעריך את המטא נתונים של הקובץ המועלה, הורדה, שינוי או מחיקה. זה מאפשר לך ליצור כללים מורכבים וחזקים שעושים דברים כמו לאפשר רק להעלות קבצים עם סוגי תוכן מסוימים, או למחוק רק קבצים גדולים מגודל מסוים.
כללי האבטחה של Firebase עבור Cloud Storage מספקים מטא נתונים של קבצים באובייקט resource
, שמכילים זוגות מפתח/ערך של המטא נתונים המופיעים באובייקט Cloud Storage. ניתן לבדוק מאפיינים אלה בבקשות read
או write
כדי להבטיח שלמות הנתונים.
בבקשות write
(כגון העלאות, עדכוני מטא-נתונים ומחיקות), בנוסף לאובייקט resource
, המכיל מטא-נתונים של קובץ עבור הקובץ הקיים כעת בנתיב הבקשה, יש לך גם את היכולת להשתמש באובייקט request.resource
, המכיל תת-קבוצה של מטא-נתונים של הקובץ שייכתב אם הכתיבה מותרת. אתה יכול להשתמש בשני ערכים אלה כדי להבטיח שלמות הנתונים או לאכוף אילוצי יישום כגון סוג או גודל קובץ.
רשימה מלאה של מאפיינים באובייקט resource
זמינה להלן:
תכונה | סוּג | תיאור |
---|---|---|
name | חוּט | השם המלא של האובייקט |
bucket | חוּט | שם הדלי שבו שוכן האובייקט הזה. |
generation | int | יצירת אובייקט Google Cloud Storage של אובייקט זה. |
metageneration | int | מטא-גנרציה של אובייקט Google Cloud Storage של אובייקט זה. |
size | int | גודל האובייקט בבתים. |
timeCreated | חותמת זמן | חותמת זמן המייצגת את הזמן שבו נוצר אובייקט. |
updated | חותמת זמן | חותמת זמן המייצגת את הזמן שבו אובייקט עודכן לאחרונה. |
md5Hash | חוּט | Hash MD5 של האובייקט. |
crc32c | חוּט | hash crc32c של האובייקט. |
etag | חוּט | ה-Etag המשויך לאובייקט זה. |
contentDisposition | חוּט | צורת התוכן המשויכת לאובייקט זה. |
contentEncoding | חוּט | קידוד התוכן המשויך לאובייקט זה. |
contentLanguage | חוּט | שפת התוכן המשויכת לאובייקט זה. |
contentType | חוּט | סוג התוכן המשויך לאובייקט זה. |
metadata | map<string, string> | צמדי מפתח/ערך של מטא נתונים נוספים בהתאמה אישית שצוינו על ידי מפתח. |
request.resource
מכיל את כל אלה למעט generation
, metageneration
, etag
, timeCreated
updated
.
מגבלות כללי אבטחה
כשאתה עובד עם כללי אבטחה, שים לב למגבלות הבאות:
לְהַגבִּיל | פרטים |
---|---|
המספר המרבי של firestore.exists() ו- firestore.get() לכל בקשה | 2 עבור בקשות למסמך בודד ובקשות שאילתות. חריגה ממגבלה זו גורמת לשגיאה שנדחתה. שיחות גישה לאותם מסמכים עשויות להיות מאוחסנות במטמון, ושיחות במטמון אינן נחשבות למגבלות. |
דוגמה מלאה
אם תחבר את הכל ביחד, תוכל ליצור דוגמה מלאה לכללים לפתרון אחסון תמונות:
service firebase.storage { match /b/{bucket}/o { match /images { // Cascade read to any image type at any path match /{allImages=**} { allow read; } // Allow write files to the path "images/*", subject to the constraints: // 1) File is less than 5MB // 2) Content type is an image // 3) Uploaded content type matches existing content type // 4) File name (stored in imageId wildcard variable) is less than 32 characters match /{imageId} { allow write: if request.resource.size < 5 * 1024 * 1024 && request.resource.contentType.matches('image/.*') && request.resource.contentType == resource.contentType && imageId.size() < 32 } } } }
מסד נתונים בזמן אמת
מבנה בסיסי
במסד נתונים בזמן אמת, כללי האבטחה של Firebase מורכבים מביטויים דמויי JavaScript הכלולים במסמך JSON.
הם משתמשים בתחביר הבא:
{
"rules": {
"<<path>>": {
// Allow the request if the condition for each method is true.
".read": <<condition>>,
".write": <<condition>>,
".validate": <<condition>>
}
}
}
ישנם שלושה מרכיבים בסיסיים בכלל:
- נתיב: מיקום מסד הנתונים. זה משקף את מבנה ה-JSON של מסד הנתונים שלך.
- בקשה: אלו הן השיטות שהכלל משתמש בהן כדי להעניק גישה. כללי
read
write
מעניקים גישת קריאה וכתיבה רחבה, בעוד שכלליvalidate
פועלים כאימות משני להענקת גישה על סמך נתונים נכנסים או קיימים. - תנאי: התנאי שמתיר בקשה אם היא מוערכת כנכונה.
כיצד חלים כללים על שבילים
במסד נתונים בזמן אמת, הכללים חלים בצורה אטומית, כלומר כללים בצמתי אב ברמה גבוהה יותר עוקפים כללים בצמתים צאצאים פרטניים יותר וכללים בצומת עמוק יותר אינם יכולים להעניק גישה לנתיב אב. אינך יכול לחדד או לבטל גישה בנתיב עמוק יותר במבנה מסד הנתונים שלך אם כבר הענקת זאת עבור אחד מנתיב האב.
שקול את הכללים הבאים:
{ "rules": { "foo": { // allows read to /foo/* ".read": "data.child('baz').val() === true", "bar": { // ignored, since read was allowed already ".read": false } } } }
מבנה אבטחה זה מאפשר לקרוא את /bar/
בכל פעם ש- /foo/
מכיל baz
ילד עם הערך true
. הכלל ".read": false
תחת /foo/bar/
אינו משפיע כאן, מכיוון שלא ניתן לבטל את הגישה על ידי נתיב צאצא.
למרות שזה אולי לא נראה אינטואיטיבי באופן מיידי, זהו חלק רב עוצמה משפת הכללים ומאפשר ליישם הרשאות גישה מורכבות מאוד במינימום מאמץ. זה שימושי במיוחד עבור אבטחה מבוססת משתמש .
עם זאת, כללי .validate
אינם משתפכים. יש לעמוד בכל כללי האימות בכל רמות ההיררכיה כדי שתתאפשר כתיבה.
בנוסף, מכיוון שכללים אינם חלים בחזרה על נתיב אב, פעולת הקריאה או הכתיבה נכשלת אם אין כלל במיקום המבוקש או במיקום אב המעניק גישה. גם אם כל נתיב ילד מושפע נגיש, הקריאה במיקום האב תיכשל לחלוטין. שקול את המבנה הזה:
{ "rules": { "records": { "rec1": { ".read": true }, "rec2": { ".read": false } } } }
בלי להבין שהכללים מוערכים בצורה אטומית, זה אולי נראה כאילו שליפת הנתיב /records/
תחזיר rec1
אבל לא rec2
. התוצאה בפועל, לעומת זאת, היא שגיאה:
JavaScript
var db = firebase.database(); db.ref("records").once("value", function(snap) { // success method is not called }, function(err) { // error callback triggered with PERMISSION_DENIED });
Objective-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[_ref child:@"records"] observeSingleEventOfType:FIRDataEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // success block is not called } withCancelBlock:^(NSError * _Nonnull error) { // cancel block triggered with PERMISSION_DENIED }];
מָהִיר
var ref = FIRDatabase.database().reference() ref.child("records").observeSingleEventOfType(.Value, withBlock: { snapshot in // success block is not called }, withCancelBlock: { error in // cancel block triggered with PERMISSION_DENIED })
Java
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("records"); ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // success method is not called } @Override public void onCancelled(FirebaseError firebaseError) { // error callback triggered with PERMISSION_DENIED }); });
מנוחה
curl https://docs-examples.firebaseio.com/rest/records/ # response returns a PERMISSION_DENIED error
מכיוון שפעולת הקריאה ב- /records/
היא אטומית, ואין כלל קריאה המעניק גישה לכל הנתונים תחת /records/
, הדבר יביא שגיאה PERMISSION_DENIED
. אם אנו מעריכים את הכלל הזה בסימולטור האבטחה במסוף Firebase שלנו, נוכל לראות שפעולת הקריאה נדחתה:
Attempt to read /records with auth=Success(null) / /records No .read rule allowed the operation. Read was denied.
הפעולה נדחתה מכיוון שאף כלל קריאה לא איפשר גישה לנתיב /records/
, אך שים לב שהכלל עבור rec1
מעולם לא הוערך מכיוון שהוא לא היה בנתיב שביקשנו. כדי להביא rec1
, נצטרך לגשת אליו ישירות:
JavaScript
var db = firebase.database(); db.ref("records/rec1").once("value", function(snap) { // SUCCESS! }, function(err) { // error callback is not called });
Objective-C
FIRDatabaseReference *ref = [[FIRDatabase database] reference]; [[ref child:@"records/rec1"] observeSingleEventOfType:FEventTypeValue withBlock:^(FIRDataSnapshot *snapshot) { // SUCCESS! }];
מָהִיר
var ref = FIRDatabase.database().reference() ref.child("records/rec1").observeSingleEventOfType(.Value, withBlock: { snapshot in // SUCCESS! })
Java
FirebaseDatabase database = FirebaseDatabase.getInstance(); DatabaseReference ref = database.getReference("records/rec1"); ref.addListenerForSingleValueEvent(new ValueEventListener() { @Override public void onDataChange(DataSnapshot snapshot) { // SUCCESS! } @Override public void onCancelled(FirebaseError firebaseError) { // error callback is not called } });
מנוחה
curl https://docs-examples.firebaseio.com/rest/records/rec1 # SUCCESS!
משתנה מיקום
כללי מסד נתונים בזמן אמת תומכים במשתנה $location
כדי להתאים לפלחי נתיב. השתמש בקידומת $
לפני קטע הנתיב שלך כדי להתאים את הכלל שלך לצמתי צאצא לאורך הנתיב.
{
"rules": {
"rooms": {
// This rule applies to any child of /rooms/, the key for each room id
// is stored inside $room_id variable for reference
"$room_id": {
"topic": {
// The room's topic can be changed if the room id has "public" in it
".write": "$room_id.contains('public')"
}
}
}
}
}
אתה יכול גם להשתמש $variable
במקביל לשמות נתיבים קבועים.
{
"rules": {
"widget": {
// a widget can have a title or color attribute
"title": { ".validate": true },
"color": { ".validate": true },
// but no other child paths are allowed
// in this case, $other means any key excluding "title" and "color"
"$other": { ".validate": false }
}
}
}