שימוש בתנאים בכללי אבטחה של Cloud Storage ב-Firebase

המדריך הזה מבוסס על לומדים את התחביר הבסיסי של המדריך לשפת Firebase Security Rules כדי להראות איך להוסיף תנאים ל-Firebase Security Rules עבור Cloud Storage.

אבן הבניין הראשית של Cloud Storage Security Rules הוא התנאי. א' הוא ביטוי בוליאני שקובע אם פעולה מסוימת מותר או לדחות. לכללים בסיסיים, יש להשתמש בליטרל true ובליטרל false כי התנאים פועלים בצורה מושלמת. אבל Firebase Security Rules של Cloud Storage שפה מספקת דרכים לכתוב תנאים מורכבים יותר שיכולים:

  • בדיקת אימות המשתמש
  • אימות הנתונים הנכנסים

אימות

Firebase Security Rules ל-Cloud Storage משתלב עם Firebase Authentication כדי לספק אימות מבוסס-משתמשים רב עוצמה ל-Cloud Storage. כך אפשר לבצע בקרת גישה מפורטת על סמך הצהרות של אסימון Firebase Authentication.

כשמשתמש מאומת שולח בקשה נגד Cloud Storage, המשתנה request.auth מאוכלס ב-uid של המשתמש (request.auth.uid) וכן להצהרות של JWT Firebase Authentication (request.auth.token).

בנוסף, כשמשתמשים באימות מותאם אישית, יוצגו תלונות נוספות בשדה request.auth.token.

כשמשתמש לא מאומת מבצע בקשה, המשתנה request.auth הוא null.

באמצעות הנתונים האלה, יש כמה דרכים נפוצות להשתמש באימות כדי קבצים:

  • ציבורי: התעלמות מ-request.auth
  • פרטי מאומת: יש לוודא ש-request.auth אינו null
  • משתמש פרטי: בודקים ש-request.auth.uid שווה לנתיב uid
  • פרטי קבוצה: בודקים אם הצהרות האסימון המותאם אישית תואמות להצהרה שנבחרה, או קוראים את המטא-נתונים של הקובץ כדי לראות אם קיים שדה מטא-נתונים

גלוי לכולם

כל כלל שלא מתייחס להקשר של request.auth יכול להיחשב public, כי הוא לא מביא בחשבון את הקשר האימות של המשתמש. הכללים האלה יכולים להיות שימושיים להצגת נתונים ציבוריים, כמו נכסי משחקים, צלילים קבצים או תוכן סטטי אחר.

// Anyone to read a public image if the file is less than 100kB
// Anyone can upload a public file ending in '.txt'
match /public/{imageId} {
  allow read: if resource.size < 100 * 1024;
  allow write: if imageId.matches(".*\\.txt");
}

פרטי מאומת,

במקרים מסוימים, ייתכן שתרצו שהנתונים יהיו גלויים לכל המשתמשים המאומתים של את האפליקציה שלך, אבל לא על ידי משתמשים לא מאומתים. מאז request.auth המשתנה הוא null עבור כל המשתמשים הלא מאומתים, כל מה שצריך לעשות הוא לבדוק המשתנה request.auth קיים כדי לדרוש אימות:

// Require authentication on all internal image reads
match /internal/{imageId} {
  allow read: if request.auth != null;
}

פרטי למשתמש

עד עכשיו, התרחיש לדוגמה הנפוץ ביותר של request.auth הוא מתן משתמשים עם הרשאות מפורטות לקבצים שלהם: מהעלאת תמונות פרופיל לקריאת מסמכים פרטיים.

לקבצים ב-Cloud Storage יש 'נתיב' מלא לקובץ, כל מה שצריך ליצירת קובץ שנשלט על ידי משתמש הוא קטע מידע בקידומת שם הקובץ (למשל uid של המשתמש) שיכול להיות כשמתבצעת הערכה של הכלל:

// Only a user can upload their profile picture, but anyone can view it
match /users/{userId}/profilePicture.png {
  allow read;
  allow write: if request.auth.uid == userId;
}

קבוצה פרטית

תרחיש נוסף נפוץ לא פחות הוא מתן הרשאות לקבוצה באובייקט, למשל, לאפשר לכמה חברי צוות לעבוד ביחד על מסמך משותף. יש יש כמה גישות לעשות זאת:

אחרי שהנתונים האלה מאוחסנים באסימון או במטא-נתונים של הקובץ, אפשר להפנות אליהם מתוך כלל:

// Allow reads if the group ID in your token matches the file metadata's `owner` property
// Allow writes if the group ID is in the user's custom token
match /files/{groupId}/{fileName} {
  allow read: if resource.metadata.owner == request.auth.token.groupId;
  allow write: if request.auth.token.groupId == groupId;
}

בקשה להערכה

המערכת מבצעת הערכה של העלאות, הורדות, שינויים במטא-נתונים ומחיקות באמצעות הערך של request שנשלח אל Cloud Storage. בנוסף למזהה הייחודי של המשתמש ול המטען הייעודי (payload) של Firebase Authentication באובייקט request.auth כמו שמתואר למעלה, המשתנה request מכיל את נתיב הקובץ שבו הבקשה מועברת בוצעו, השעה שבה הבקשה מתקבלת והערך resource החדש אם הבקשה היא כתיבה.

האובייקט request מכיל גם את המזהה הייחודי של המשתמש ואת עומס העבודה (payload) של Firebase Authentication באובייקט request.auth. מידע נוסף זמין בקטע אבטחה מבוססת-משתמשים במסמכים.

רשימה מלאה של המאפיינים באובייקט request זמינה בהמשך:

נכס סוג תיאור
auth מפה<string, string> כשמשתמש מחובר לחשבון, הוא מספק את uid, המזהה הייחודי של המשתמש, וגם את token, מפה של Firebase Authentication הצהרות JWT. אחרת, null
params map<string, string> מפה שמכילה את פרמטרים השאילתה של הבקשה.
path נתיב path שמייצג את הנתיב שבו מתבצעת הבקשה.
resource מפה<string, string> ערך המשאב החדש, שנמצא רק בבקשות write.
time חותמת זמן חותמת זמן שמייצגת את הזמן שבו מתבצעת בדיקת הבקשה בשרת.

הערכת משאבים

כשבוחנים כללים, מומלץ גם להעריך את המטא-נתונים של הקובץ העלאות, הורדה, שינוי או מחיקה. כך אפשר ליצור הם כללים מורכבים וחזקים שעושים דברים כמו לאפשר רק קבצים עם מאפיינים מסוימים סוגי תוכן להעלאה, או רק קבצים שגדולים מגודל מסוים נמחק.

Firebase Security Rules עבור 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 מחרוזת גיבוב MD5 של האובייקט.
crc32c מחרוזת גיבוב crc32c של האובייקט.
etag מחרוזת ה-etag שמשויך לאובייקט הזה.
contentDisposition מחרוזת צורת התוכן שמשויכת לאובייקט הזה.
contentEncoding מחרוזת קידוד התוכן שמשויך לאובייקט הזה.
contentLanguage מחרוזת שפת התוכן שמשויכת לאובייקט הזה.
contentType מחרוזת סוג התוכן שמשויך לאובייקט הזה.
metadata map<string, string> צמדי מפתח/ערך של מטא-נתונים מותאמים אישית נוספים שהמפתח ציין.

השדה request.resource מכיל את כל הערכים האלה, מלבד generation,‏ metageneration,‏ etag,‏ timeCreated ו-updated.

שיפור באמצעות Cloud Firestore

אפשר לגשת למסמכים ב-Cloud Firestore כדי להעריך קריטריונים אחרים להרשאה.

באמצעות הפונקציות firestore.get() ו-firestore.exists(), האבטחה שלך כללים יכולים להעריך בקשות נכנסות מול מסמכים בCloud Firestore. הפונקציות firestore.get() והפונקציות firestore.exists() מצפות באופן מלא נתיבי מסמך שצוינו. כשמשתמשים במשתנים כדי לבנות נתיבים firestore.get() וגם firestore.exists(), יש לסמן בתו בריחה (escape) באופן מפורש משתנים באמצעות התחביר $(variable).

בדוגמה הבאה מוצג כלל שמגביל את הגישה לקריאת קבצים למשתמשים שהם חברים במועדונים מסוימים.

service firebase.storage {
  match /b/{bucket}/o {
    match /users/{club}/files/{fileId} {
      allow read: if club in
        firestore.get(/databases/(default)/documents/users/$(request.auth.id)).memberships
    }
  }
}
בדוגמה הבאה, רק החברים של המשתמש יכולים לראות את התמונות שלו.
service firebase.storage {
  match /b/{bucket}/o {
    match /users/{userId}/photos/{fileId} {
      allow read: if
        firestore.exists(/databases/(default)/documents/users/$(userId)/friends/$(request.auth.id))
    }
  }
}

אחרי שיוצרים ושומרים את Cloud Storage Security Rules הראשונים שמשתמשים בCloud Firestore האלה הפונקציות, תופיע הודעה במסוף Firebase או ב-Firebase CLI כדי להפעיל את ההרשאות לחיבור בין שני המוצרים.

כדי להשבית את התכונה, מסירים תפקיד IAM, כפי שמתואר בקטע ניהול ופריסה של Firebase Security Rules.

אימות נתונים

אפשר להשתמש ב-Firebase Security Rules ל-Cloud Storage גם לאימות נתונים, כולל אימות שם ונתיב של קובץ ומאפייני מטא נתונים של קבצים, כמו contentType וגם size.

service firebase.storage {
  match /b/{bucket}/o {
    match /images/{imageId} {
      // Only allow uploads of any image file that's less than 5MB
      allow write: if request.resource.size < 5 * 1024 * 1024
                   && request.resource.contentType.matches('image/.*');
    }
  }
}

פונקציות מותאמות אישית

ככל שהFirebase Security Rules יהיה מורכב יותר, כדאי לעטוף קבוצות של בפונקציות שניתן לעשות בהן שימוש חוזר בכל קבוצת הכללים. כללי האבטחה תומכים בפונקציות בהתאמה אישית. התחביר של פונקציות מותאמות אישית דומה קצת ל-JavaScript, אבל הפונקציות של Firebase Security Rules כתובות בשפה ספציפית לדומיין עם כמה מגבלות חשובות:

  • פונקציות יכולות להכיל רק הצהרת return אחת. הם לא יכולים להכיל לוגיקה נוספת. לדוגמה, הם לא יכולים להפעיל לולאות או להתקשר לשירותים חיצוניים.
  • פונקציות יכולות לגשת באופן אוטומטי לפונקציות ולמשתנים מההיקף שבו הן מוגדרות. לדוגמה, לפונקציה שמוגדרת בהיקף service firebase.storage יש גישה למשתנה resource, ולפונקציות מובנות כמו get() ו-exists() רק בהיקף Cloud Firestore.
  • פונקציות יכולות לקרוא לפונקציות אחרות, אבל לא ניתן לחזור עליהן. עומק ה-call stack הכולל מוגבל ל-10.
  • בגרסה rules2, פונקציות יכולות להגדיר משתנים באמצעות מילת המפתח let. פונקציות יכולות לכלול כל מספר קישורי let, אבל הן חייבות להסתיים בהצהרת return.

פונקציה מוגדרת עם מילת המפתח function והיא מקבלת אפס או יותר ארגומנטים. לדוגמה, תוכלו לשלב בין שני סוגי התנאים שבהם אתם משתמשים. בדוגמאות שלמעלה, לפונקציה אחת:

service firebase.storage {
  match /b/{bucket}/o {
    // True if the user is signed in or the requested data is 'public'
    function signedInOrPublic() {
      return request.auth.uid != null || resource.data.visibility == 'public';
    }
    match /images/{imageId} {
      allow read, write: if signedInOrPublic();
    }
    match /mp3s/{mp3Ids} {
      allow read: if signedInOrPublic();
    }
  }
}

השימוש בפונקציות ב-Firebase Security Rules הופך אותן לקלות יותר בתור המורכבות של הכללים שלכם.

השלבים הבאים

אחרי הדיון על התנאים, להבין את הכללים והם מוכנים:

תלמדו איך לטפל בתרחישים עיקריים לדוגמה, ותלמדו את תהליך הפיתוח של של כללים לבדיקה ופריסה: