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

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

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

אימות

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

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to access documents in the "cities" collection
    // only if they are authenticated.
    match /cities/{city} {
      allow read, write: if request.auth != null;
    }
  }
}

דפוס נפוץ נוסף הוא לוודא שהמשתמשים יכולים לקרוא ולכתוב רק את הנתונים שלהם:

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure the uid of the requesting user matches name of the user
    // document. The wildcard expression {userId} makes the userId variable
    // available in rules.
    match /users/{userId} {
      allow read, update, delete: if request.auth != null && request.auth.uid == userId;
      allow create: if request.auth != null;
    }
  }
}

אם האפליקציה שלכם משתמשת באימות ב-Firebase או ב-Google Cloud Identity Platform, המשתנה request.auth מכיל את פרטי האימות של הלקוח שמבקש את הנתונים. מידע נוסף על request.auth זמין במסמכי העזרה.

אימות נתונים

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

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

המשתנה resource מתייחס למסמך המבוקש, ו-resource.data הוא מפה של כל השדות והערכים שמאוחסנים במסמך. למידע נוסף על המשתנה resource, עיינו במסמכי העזרה.

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

service cloud.firestore {
  match /databases/{database}/documents {
    // Make sure all cities have a positive population and
    // the name is not changed
    match /cities/{city} {
      allow update: if request.resource.data.population > 0
                    && request.resource.data.name == resource.data.name;
    }
  }
}

גישה למסמכים אחרים

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

בדוגמה הבאה, המשתנה database מתועד על ידי ביטוי ההתאמה match /databases/{database}/documents ומשמשים ליצירת הנתיב:

service cloud.firestore {
  match /databases/{database}/documents {
    match /cities/{city} {
      // Make sure a 'users' document exists for the requesting user before
      // allowing any writes to the 'cities' collection
      allow create: if request.auth != null && exists(/databases/$(database)/documents/users/$(request.auth.uid));

      // Allow the user to delete cities if their user document has the
      // 'admin' field set to 'true'
      allow delete: if request.auth != null && get(/databases/$(database)/documents/users/$(request.auth.uid)).data.admin == true;
    }
  }
}

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

הגבלות על שיחות גישה

יש מגבלה על מספר הקריאות לגישה למסמך לכל הערכה של קבוצת כללים:

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

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

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

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

שיחות גישה ותמחור

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

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

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

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

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

service cloud.firestore {
  match /databases/{database}/documents {
    // 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 /cities/{city} {
      allow read, write: if signedInOrPublic();
    }

    match /users/{user} {
      allow read, write: if signedInOrPublic();
    }
  }
}

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

כללים הם לא מסננים

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

לדוגמה, ניקח את כלל האבטחה הבא:

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to read data if the document has the 'visibility'
    // field set to 'public'
    match /cities/{city} {
      allow read: if resource.data.visibility == 'public';
    }
  }
}

נדחה: הכלל הזה דוחה את השאילתה הבאה כי קבוצת התוצאות יכולה לכלול מסמכים שבהם הערך של visibility הוא לא public:

אינטרנט
db.collection("cities").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
    });
});

מותר: הכלל הזה מאפשר את השאילתה הבאה כי התנאי where("visibility", "==", "public") מבטיח שקבוצת התוצאות עומדת בתנאי הכלל:

אינטרנט
db.collection("cities").where("visibility", "==", "public").get()
    .then(function(querySnapshot) {
        querySnapshot.forEach(function(doc) {
            console.log(doc.id, " => ", doc.data());
        });
    });

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

השלבים הבאים