בדיקת כללי האבטחה של Cloud Firestore

במהלך פיתוח האפליקציה, יכול להיות שתרצו להגביל את הגישה למסד הנתונים של Cloud Firestore. עם זאת, לפני ההשקה, תצטרכו להגדיר אסטרטגיה מורכבת יותר Cloud Firestore Security Rules. באמצעות האמולטור Cloud Firestore, בנוסף ליצירת אב טיפוס ולבדיקה של התכונות וההתנהגות הכלליות של האפליקציה, אפשר לכתוב בדיקות יחידה שבודקות את ההתנהגות של Cloud Firestore Security Rules.

מדריך למתחילים

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

הסבר על Cloud Firestore Security Rules

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

Cloud Firestore Security Rules כולל שני חלקים:

  1. match הצהרה שמזהה מסמכים במסד הנתונים.
  2. ביטוי allow ששולט בגישה למסמכים האלה.

Firebase Authentication מאמת את פרטי הכניסה של המשתמשים ומספק את הבסיס למערכות גישה מבוססות-משתמשים ומבוססות-תפקידים.

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

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

התקנת האמולטור

כדי להתקין את האמולטור של Cloud Firestore, משתמשים ב-Firebase CLI ומריצים את הפקודה הבאה:

firebase setup:emulators:firestore

הפעלת האמולטור

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

firebase init

מפעילים את האמולטור באמצעות הפקודה הבאה. האמולטור יפעל עד שתסיימו את התהליך:

firebase emulators:start --only firestore

במקרים רבים, רוצים להפעיל את האמולטור, להריץ חבילת בדיקות ואז לכבות את האמולטור אחרי שהבדיקות רצות. אפשר לעשות את זה בקלות באמצעות הפקודה emulators:exec:

firebase emulators:exec --only firestore "./my-test-script.sh"

כשהאמולטור מופעל, הוא מנסה לפעול ביציאת ברירת מחדל (8080). אפשר לשנות את יציאת האמולטור על ידי שינוי הקטע "emulators" בקובץ firebase.json:

{
  // ...
  "emulators": {
    "firestore": {
      "port": "YOUR_PORT"
    }
  }
}

לפני שמריצים את האמולטור

לפני שמתחילים להשתמש באמולטור, חשוב לזכור את הנקודות הבאות:

  • האמולטור יטען בהתחלה את הכללים שצוינו בשדה firestore.rules בקובץ firebase.json. הוא מצפה לשם של קובץ מקומי שמכיל את Cloud Firestore Security Rules ומחיל את הכללים האלה על כל הפרויקטים. אם לא תספקו את הנתיב של הקובץ המקומי או אם לא תשתמשו בשיטה loadFirestoreRules שמתוארת בהמשך, האמולטור יתייחס לכל הפרויקטים כאילו יש להם כללים פתוחים.
  • רוב ערכות ה-SDK של Firebase פועלות ישירות עם האמולטורים, אבל רק ספריית @firebase/rules-unit-testing תומכת ב-mocking‏ auth בכללי האבטחה, ולכן קל יותר לבצע בדיקות יחידה. בנוסף, הספרייה תומכת בכמה תכונות ספציפיות לאמולטור, כמו מחיקת כל הנתונים, כמו שמופיע בהמשך.
  • האמולטורים יקבלו גם טוקנים של Firebase Auth מהסביבה הפרודקטיבית שסופקו דרך ערכות SDK של לקוח, ויעריכו את הכללים בהתאם. כך תוכלו לקשר את האפליקציה ישירות לאמולטורים בבדיקות שילוב ובבדיקות ידניות.

הרצת בדיקות יחידה מקומיות

הרצת בדיקות יחידה מקומיות באמצעות JavaScript SDK בגרסה 9

‫Firebase מפיצה ספרייה לבדיקת יחידות של כללי אבטחה עם גרסה 9 של JavaScript SDK ועם גרסה 8 של SDK. ממשקי ה-API של הספרייה שונים באופן משמעותי. מומלץ להשתמש בספריית הבדיקות v9, שהיא יעילה יותר ודורשת פחות הגדרות כדי להתחבר לאמולטורים, וכך אפשר להימנע בבטחה משימוש לא מכוון במשאבי ייצור. כדי לשמור על תאימות לאחור, אנחנו ממשיכים להציע את ספריית הבדיקות v8.

משתמשים במודול @firebase/rules-unit-testing כדי ליצור אינטראקציה עם האמולטור שפועל באופן מקומי. אם מופיעות שגיאות של פסק זמן או שגיאות ECONNREFUSED, צריך לוודא שהאמולטור פועל.

מומלץ מאוד להשתמש בגרסה עדכנית של Node.js כדי שתוכלו להשתמש בסימון async/await. כמעט כל ההתנהגויות שתרצו לבדוק כוללות פונקציות אסינכרוניות, ומודול הבדיקה מיועד לעבוד עם קוד מבוסס-Promise.

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

מייבאים את הספרייה באמצעות הצהרות ייבוא מודולריות של גרסה 9. לדוגמה:

import {
  assertFails,
  assertSucceeds,
  initializeTestEnvironment
} from "@firebase/rules-unit-testing"

// Use `const { … } = require("@firebase/rules-unit-testing")` if imports are not supported
// Or we suggest `const testing = require("@firebase/rules-unit-testing")` if necessary.

אחרי הייבוא, כדי להטמיע בדיקות יחידה צריך:

  • יצירה והגדרה של RulesTestEnvironment עם קריאה אל initializeTestEnvironment.
  • הגדרה של נתוני בדיקה בלי להפעיל כללים, באמצעות שיטה נוחה שמאפשרת לעקוף אותם באופן זמני, RulesTestEnvironment.withSecurityRulesDisabled.
  • הגדרה של חבילת בדיקות ושל פעולות לפני ואחרי כל בדיקה עם קריאות לניקוי נתוני הבדיקה והסביבה, כמו RulesTestEnvironment.cleanup() או RulesTestEnvironment.clearFirestore().
  • הטמעה של תרחישי בדיקה שמדמים מצבי אימות באמצעות RulesTestEnvironment.authenticatedContext ו-RulesTestEnvironment.unauthenticatedContext.

שיטות נפוצות ופונקציות בסיסיות

אפשר גם לעיין בשיטות בדיקה ספציפיות לאמולטור ב-SDK בגרסה 9.

initializeTestEnvironment() => RulesTestEnvironment

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

הפונקציה מקבלת אובייקט אופציונלי שמגדיר TestEnvironmentConfig, שיכול לכלול מזהה פרויקט והגדרות תצורה של אמולטור.

let testEnv = await initializeTestEnvironment({
  projectId: "demo-project-1234",
  firestore: {
    rules: fs.readFileSync("firestore.rules", "utf8"),
  },
});

RulesTestEnvironment.authenticatedContext({ user_id: string, tokenOptions?: TokenOptions }) => RulesTestContext

השיטה הזו יוצרת RulesTestContext, שמתנהג כמו משתמש מאומת של Authentication. לבקשות שנוצרו באמצעות ההקשר שהוחזר יצורף טוקן אימות מדומה. אופציונלי: אפשר להעביר אובייקט שמגדיר טענות מותאמות אישית או שינויים במטען הייעודי (payload) של טוקנים לאימות.

משתמשים באובייקט הקשר של הבדיקה שמוחזר בבדיקות כדי לגשת לכל המופעים של האמולטור שהוגדרו, כולל אלה שהוגדרו באמצעות initializeTestEnvironment.

// Assuming a Firestore app and the Firestore emulator for this example
import { setDoc } from "firebase/firestore";

const alice = testEnv.authenticatedContext("alice", {  });
// Use the Firestore instance associated with this context
await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

RulesTestEnvironment.unauthenticatedContext() => RulesTestContext

בשיטה הזו נוצר RulesTestContext, שמתנהג כמו לקוח שלא מחובר באמצעות אימות. לבקשות שנוצרות באמצעות ההקשר שהוחזר לא יצורפו טוקנים של Firebase Auth.

משתמשים באובייקט הקשר של הבדיקה שמוחזר בבדיקות כדי לגשת לכל המופעים של האמולטור שהוגדרו, כולל אלה שהוגדרו באמצעות initializeTestEnvironment.

// Assuming a Cloud Storage app and the Storage emulator for this example
import { getStorage, ref, deleteObject } from "firebase/storage";

const alice = testEnv.unauthenticatedContext();

// Use the Cloud Storage instance associated with this context
const desertRef = ref(alice.storage(), 'images/desert.jpg');
await assertSucceeds(deleteObject(desertRef));

RulesTestEnvironment.withSecurityRulesDisabled()

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

השיטה הזו מקבלת פונקציית קריאה חוזרת, שמקבלת את ההקשר של עקיפת כללי האבטחה ומחזירה אובייקט promise. ההקשר יימחק אחרי שההבטחה תתקבל או תידחה.

RulesTestEnvironment.cleanup()

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

השיטה הזו לא משנה את מצב האמולטורים בשום צורה. כדי לאפס את הנתונים בין הבדיקות, משתמשים בשיטה לניקוי הנתונים שספציפית לאמולטור של האפליקציה.

assertSucceeds(pr: Promise<any>)) => Promise<any>

זוהי פונקציית כלי לבדיקת תרחישים.

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

await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

assertFails(pr: Promise<any>)) => Promise<any>

זוהי פונקציית כלי לבדיקת תרחישים.

הפונקציה קובעת שה-Promise שסופק, שעוטף פעולת אמולטור, יידחה בגלל הפרה של כללי האבטחה.

await assertFails(setDoc(alice.firestore(), '/users/bob'), { ... });

שיטות ספציפיות לאמולטור

אפשר גם לעיין בשיטות נפוצות לבדיקה ופונקציות עזר ב-SDK בגרסה 9.

RulesTestEnvironment.clearFirestore() => Promise<void>

השיטה הזו מוחקת נתונים במסד הנתונים של Firestore ששייכים ל-projectId שהוגדר לאמולטור של Firestore.

RulesTestContext.firestore(settings?: Firestore.FirestoreSettings) => Firestore;

השיטה הזו מאחזרת מופע של Firestore עבור הקשר של הבדיקה הזו. אפשר להשתמש במופע של Firebase JS Client SDK שמוחזר עם ממשקי API של Client SDK (מודולריים בגרסה 9 או תואמים בגרסה 9).

הדמיה של הערכות הכללים

האמולטור Cloud Firestore מאפשר לכם לראות את בקשות הלקוח בממשק המשתמש של Emulator Suite, כולל מעקב אחר הערכות של כללי אבטחה ב-Firebase.

פותחים את הכרטיסייה Firestore > בקשות כדי לראות את רצף ההערכה המפורט של כל בקשה.

מוניטור הבקשות של אמולטור Firestore שמציג הערכות של כללי אבטחה

יצירת דוחות בדיקה

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

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

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage.html

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

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage

ההבדלים בין האמולטור לבין הסביבה הפעילה

  1. לא צריך ליצור פרויקט Cloud Firestore באופן מפורש. האמולטור יוצר באופן אוטומטי כל מופע שאליו ניגשים.
  2. האמולטור Cloud Firestore לא פועל עם התהליך הרגיל של Firebase Authentication. במקום זאת, ב-Firebase Test SDK סיפקנו את ה-method‏ initializeTestApp() בספרייה rules-unit-testing, שמקבלת שדה auth. ה-handle של Firebase שנוצר באמצעות השיטה הזו יתנהג כאילו האימות שלו בוצע בהצלחה כישות שסיפקתם. אם מעבירים את הערך null, המערכת תתייחס למשתמש כאל משתמש לא מאומת (לדוגמה, כללי auth != null ייכשלו).

פתרון בעיות מוכרות

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

התנהגות הבדיקה לא עקבית

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

חשוב במיוחד לעיין בפעולות האסינכרוניות הבאות:

  • הגדרת כללי אבטחה, למשל באמצעות initializeTestEnvironment.
  • קריאה וכתיבה של נתונים, לדוגמה, db.collection("users").doc("alice").get().
  • טענות תפעוליות, כולל assertSucceeds ו-assertFails.

הבדיקות עוברות רק בפעם הראשונה שבה טוענים את האמולטור

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

  • צריך להשתמש במזהי פרויקט ייחודיים לכל בדיקה. שימו לב שאם תבחרו לעשות את זה, תצטרכו לקרוא ל-initializeTestEnvironment כחלק מכל בדיקה. הכללים נטענים אוטומטית רק למזהה פרויקט ברירת המחדל.
  • לשנות את המבנה של הבדיקות כך שלא תהיה אינטראקציה עם נתונים שנכתבו בעבר (לדוגמה, להשתמש באוסף נתונים שונה לכל בדיקה).
  • מחיקה של כל הנתונים שנכתבו במהלך בדיקה.

הגדרת הבדיקה מסובכת מדי

כשמגדירים את הבדיקה, יכול להיות שתרצו לשנות את הנתונים באופן שCloud Firestore Security Rules לא מאפשרים בפועל. אם הכללים שלכם מסבכים את הגדרת הבדיקה, נסו להשתמש ב-RulesTestEnvironment.withSecurityRulesDisabled בשלבי ההגדרה, כדי שפעולות קריאה וכתיבה לא יפעילו שגיאות PERMISSION_DENIED.

לאחר מכן, הבדיקה יכולה לבצע פעולות כמשתמש מאומת או לא מאומת באמצעות RulesTestEnvironment.authenticatedContext ו-unauthenticatedContext בהתאמה. כך תוכלו לוודא שCloud Firestore Security Rules מאפשר או חוסם תרחישים שונים בצורה נכונה.