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

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

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 תומכת לעג של auth ב'כללי אבטחה', כך שקל יותר לבצע בדיקות יחידה. In addition, הספרייה תומכת בכמה תכונות ספציפיות לאמולטור כמו ניקוי כל הנתונים, כפי שמפורט בהמשך.
  • האמולטורים יקבלו גם אסימוני Auth Auth שסופקו בסביבת הייצור של Firebase באמצעות ערכות SDK של לקוחות ולהעריך כללים בהתאם, כדי לאפשר חיבור את האפליקציה שלכם ישירות לאמולטורים בשילוב ובבדיקות ידניות.

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

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

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

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

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

ספריית v9 של 'בדיקת היחידה' תמיד מודעת לאמולטורים ואף פעם לא נוגע במשאבי ההפקה שלך.

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

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
  • הגדרת חבילת בדיקות וכל בדיקה לפני/אחרי הוקים (hooks) עם קריאות אל לנקות את נתוני הבדיקה ואת הסביבה, כמו RulesTestEnvironment.cleanup() או RulesTestEnvironment.clearFirestore().
  • הטמעת תרחישי בדיקה שמחקים מצבי אימות באמצעות RulesTestEnvironment.authenticatedContext ו-RulesTestEnvironment.unauthenticatedContext.

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

בנוסף, כדאי לעיין בשיטות בדיקה ספציפיות לאמולטור ב-v9 SDK.

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

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

משתמשים באובייקט ההקשר לבדיקה שמוחזר בבדיקות כדי לגשת לכל אמולטור למכונות שהוגדרו, כולל אלה שהוגדרו ב-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()

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

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

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'), { ... });

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

כדאי לעיין גם בשיטות בדיקה נפוצות ופונקציות שימושיות ב-v9 SDK.

RulesTestEnvironment.clearFirestore() => Promise<void>

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

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

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

הדמיה של כללים

בעזרת המהדרר 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, הוספנו את השיטה initializeTestApp() ב- הספרייה rules-unit-testing, שמכילה את השדה auth. הכינוי שנוצר ב-Firebase שימוש בשיטה זו יפעל כאילו הוא אומת בהצלחה כל ישות שמציינים. אם עוברים בכתובת null, אופן הפעולה יהיה משתמש לא מאומת (למשל, auth != null כללים ייכשלו).

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

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

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

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