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

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

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

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

הסבר על Cloud Firestore Security Rules

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

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 מהסביבה הפרודקטיבית שסופקו דרך Client SDKs, ויעריכו את הכללים בהתאם. כך תוכלו לקשר את האפליקציה ישירות לאמולטורים בבדיקות שילוב ובבדיקות ידניות.

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

הרצת בדיקות יחידה מקומיות באמצעות 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. לבקשות שנוצרו באמצעות ההקשר שהוחזר יצורף טוקן אימות מדומה. אופציונלי: אפשר להעביר אובייקט שמגדיר טענות מותאמות אישית או שינויים בנתוני הטוקנים של האימות.

משתמשים באובייקט של הקשר הבדיקה שמוחזר בבדיקות כדי לגשת לכל מופעי האמולטור שהוגדרו, כולל אלה שהוגדרו באמצעות 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().doc('/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'), { ... });

‫methods ספציפיות לאמולטור

אפשר גם לעיין בשיטות בדיקה נפוצות ופונקציות עזר ב-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האמולטור מאפשר לכם לראות את בקשות הלקוח בממשק המשתמש של כלים לאמולטור, כולל מעקב אחר הערכה של כללי האבטחה של Firebase.

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

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

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

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

כדי לקבל את הדוחות, צריך להריץ שאילתה על נקודת קצה חשופה באמולטור. כדי להשתמש בגרסה שמתאימה לדפדפן, משתמשים בכתובת ה-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.

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

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

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

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

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

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