שליטה בגישה לשדות ספציפיים

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

יכול להיות שתרצו לשלוט בשינויים במסמך לא ברמת המסמך אלא ברמת השדה.

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

כדי לקבל עזרה מ-AI, מעתיקים את ההנחיה שלמטה ומדביקים אותה בסוכן ה-AI לתכנות (כמו Gemini ב-Android Studio,‏ Antigravity,‏ Claude Code,‏ Codex ו-Cursor). אתם יכולים להתאים אישית את ההנחיה ולשפר את הסוכן. מידע מפורט על ההתקנה זמין במאמר סקילים אג'נטיים ב-Firebase.

Help me use Firebase in my app.
1. Make sure that **Firebase agent skills** are installed and updated. For Claude Code, Codex, or Cursor agents, help me install the Firebase plugin. For other agents, run `npx skills add firebase/agent-skills`. Always try to use **Firebase agent skills** for Firebase-related tasks.
2. Help me write, validate, and deploy Firebase Security Rules to protect my app's Firestore data.

מתן הרשאת קריאה רק לשדות ספציפיים

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

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

‎/employees/{emp_id}

  name: "Alice Hamilton",
  department: 461,
  start_date: <timestamp>

/employees/{emp_id}/private/finances

    salary: 80000,
    bonus_mult: 1.25,
    perf_review: 4.2

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

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow any logged in user to view the public employee data
    match /employees/{emp_id} {
      allow read: if request.resource.auth != null
      // Allow only users with the custom auth claim of "Finance" to view
      // the employee's financial data
      match /private/finances {
        allow read: if request.resource.auth &&
          request.resource.auth.token.role == 'Finance'
      }
    }
  }
}

הגבלת שדות ביצירת מסמכים

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

כדי ליצור את הכללים האלה, אפשר לבדוק את השיטה keys של האובייקט request.resource.data. זו רשימה של כל השדות שהלקוח מנסה לכתוב במסמך החדש הזה. אם משלבים את קבוצת השדות הזו עם פונקציות כמו hasOnly() או hasAny(), אפשר להוסיף לוגיקה שמגבילה את סוגי המסמכים שמשתמש יכול להוסיף לכללים.

דרישה למלא שדות ספציפיים במסמכים חדשים

נניח שאתם רוצים לוודא שכל המסמכים שנוצרו באוסף restaurant מכילים לפחות את השדות name,‏ location ו-city. כדי לעשות את זה, אפשר להפעיל את hasAll() ברשימת המפתחות במסמך החדש.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document contains a name
    // location, and city field
    match /restaurant/{restId} {
      allow create: if request.resource.data.keys().hasAll(['name', 'location', 'city']);
    }
  }
}

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

איסור על שדות ספציפיים במסמכים חדשים

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

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

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document does *not*
    // contain an average_score or rating_count field.
    match /restaurant/{restId} {
      allow create: if (!request.resource.data.keys().hasAny(
        ['average_score', 'rating_count']));
    }
  }
}

יצירת רשימת היתרים של שדות למסמכים חדשים

במקום לאסור על שדות מסוימים במסמכים חדשים, אפשר ליצור רשימה של השדות שמותרים במפורש במסמכים חדשים. לאחר מכן תוכלו להשתמש בפונקציה hasOnly() כדי לוודא שכל המסמכים החדשים שנוצרו מכילים רק את השדות האלה (או קבוצת משנה של השדות האלה) ולא שדות אחרים.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document doesn't contain
    // any fields besides the ones listed below.
    match /restaurant/{restId} {
      allow create: if (request.resource.data.keys().hasOnly(
        ['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

שילוב של שדות חובה ושדות אופציונליים

אפשר לשלב את הפעולות hasAll ו-hasOnly בכללי האבטחה כדי לדרוש שדות מסוימים ולאפשר שדות אחרים. לדוגמה, בדוגמה הזו נדרש שכל המסמכים החדשים יכילו את השדות name, location ו-city, ומאפשרת באופן אופציונלי את השדות address, hours ו-cuisine.

service cloud.firestore {
  match /databases/{database}/documents {
    // Allow the user to create a document only if that document has a name,
    // location, and city field, and optionally address, hours, or cuisine field
    match /restaurant/{restId} {
      allow create: if (request.resource.data.keys().hasAll(['name', 'location', 'city'])) &&
       (request.resource.data.keys().hasOnly(
           ['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

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

service cloud.firestore {
  match /databases/{database}/documents {
    function verifyFields(required, optional) {
      let allAllowedFields = required.concat(optional);
      return request.resource.data.keys().hasAll(required) &&
        request.resource.data.keys().hasOnly(allAllowedFields);
    }
    match /restaurant/{restId} {
      allow create: if verifyFields(['name', 'location', 'city'],
        ['address', 'hours', 'cuisine']);
    }
  }
}

הגבלת שדות בעדכון

שיטת אבטחה נפוצה היא לאפשר ללקוחות לערוך רק חלק מהשדות. אי אפשר לעשות את זה רק על סמך הרשימה request.resource.data.keys() שמתוארת בקטע הקודם, כי הרשימה הזו מייצגת את המסמך המלא כמו שהוא ייראה אחרי העדכון, ולכן היא תכלול שדות שהלקוח לא שינה.

עם זאת, אם משתמשים בפונקציה diff(), אפשר להשוות בין request.resource.data לבין האובייקט resource.data, שמייצג את המסמך במסד הנתונים לפני העדכון. כך נוצר אובייקט mapDiff, שמכיל את כל השינויים בין שתי מפות שונות.

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

מניעת שינויים בשדות מסוימים

באמצעות השיטה hasAny() בקבוצה שנוצרה על ידי affectedKeys(), ואז שלילת התוצאה, תוכלו לדחות כל בקשת לקוח שמנסה לשנות שדות שאתם לא רוצים לשנות.

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

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
      // Allow the client to update a document only if that document doesn't
      // change the average_score or rating_count fields
      allow update: if (!request.resource.data.diff(resource.data).affectedKeys()
        .hasAny(['average_score', 'rating_count']));
    }
  }
}

לאפשר שינויים רק בשדות מסוימים

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

לדוגמה, במקום לאסור על שינוי השדות average_score ו-rating_count, אפשר ליצור כללי אבטחה שמאפשרים ללקוחות לשנות רק את השדות name,‏ location,‏ city,‏ address,‏ hours ו-cuisine.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
    // Allow a client to update only these 6 fields in a document
      allow update: if (request.resource.data.diff(resource.data).affectedKeys()
        .hasOnly(['name', 'location', 'city', 'address', 'hours', 'cuisine']));
    }
  }
}

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

אכיפת סוגי שדות

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

לדוגמה, כלל האבטחה הבא אוכף שהשדה score של הביקורת חייב להיות מספר שלם, השדות headline,‏ content ו-author_name הם מחרוזות, והשדה review_date הוא חותמת זמן.

service cloud.firestore {
  match /databases/{database}/documents {
    match /restaurant/{restId} {
      // Restaurant rules go here...
      match /review/{reviewId} {
        allow create: if (request.resource.data.score is int &&
          request.resource.data.headline is string &&
          request.resource.data.content is string &&
          request.resource.data.author_name is string &&
          request.resource.data.review_date is timestamp
        );
      }
    }
  }
}

סוגי הנתונים החוקיים לאופרטור is הם bool,‏ bytes,‏ float,‏ int,‏ list,‏ latlng,‏ number,‏ path,‏ map,‏ string ו-timestamp. האופרטור is תומך גם בסוגי הנתונים constraint,‏ duration,‏ set ו-map_diff, אבל מכיוון שהם נוצרים על ידי שפת כללי האבטחה עצמה ולא על ידי לקוחות, כמעט ולא משתמשים בהם ברוב האפליקציות המעשיות.

סוגי הנתונים list ו-map לא תומכים בגנריות או בארגומנטים של סוגים. במילים אחרות, אתם יכולים להשתמש בכללי אבטחה כדי לאכוף ששדה מסוים יכיל רשימה או מפה, אבל אתם לא יכולים לאכוף ששדה יכיל רשימה של כל המספרים השלמים או כל המחרוזות.

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

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

service cloud.firestore {
  match /databases/{database}/documents {
  match /orders/{orderId} {
    allow create: if request.resource.data.tags is list &&
      request.resource.data.tags[0] is string &&
      request.resource.data.product is map &&
      request.resource.data.product.name is string &&
      request.resource.data.product.quantity is int
      }
    }
  }
}

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

service cloud.firestore {
  match /databases/{database}/documents {

  function reviewFieldsAreValidTypes(docData) {
     return docData.score is int &&
          docData.headline is string &&
          docData.content is string &&
          docData.author_name is string &&
          docData.review_date is timestamp;
  }

   match /restaurant/{restId} {
      // Restaurant rules go here...
      match /review/{reviewId} {
        allow create: if reviewFieldsAreValidTypes(request.resource.data) &&
          // Other rules may go here
        allow update: if reviewFieldsAreValidTypes(request.resource.data) &&
          // Other rules may go here
      }
    }
  }
}

אכיפת סוגים לשדות אופציונליים

חשוב לזכור שאם קוראים ל-request.resource.data.foo במסמך שבו foo לא קיים, תתקבל שגיאה, ולכן כל כלל אבטחה שמבצע את הקריאה הזו ידחה את הבקשה. כדי לטפל במצב הזה, אפשר להשתמש ב-method‏ get ב-request.resource.data. השיטה get מאפשרת לספק ארגומנט ברירת מחדל לשדה שאתם מאחזרים ממפה, אם השדה הזה לא קיים.

לדוגמה, אם מסמכי הביקורת מכילים גם שדה אופציונלי photo_url ושדה אופציונלי tags שאתם רוצים לאמת שהם מחרוזות ורשימות בהתאמה, אתם יכולים לעשות זאת על ידי כתיבה מחדש של הפונקציה reviewFieldsAreValidTypes למשהו כמו:

  function reviewFieldsAreValidTypes(docData) {
     return docData.score is int &&
          docData.headline is string &&
          docData.content is string &&
          docData.author_name is string &&
          docData.review_date is timestamp &&
          docData.get('photo_url', '') is string &&
          docData.get('tags', []) is list;
  }

הכלל הזה דוחה מסמכים שבהם tags קיים אבל הוא לא רשימה, ועדיין מאפשר מסמכים שלא מכילים את השדה tags (או photo_url).

אף פעם אי אפשר לבצע כתיבה חלקית

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