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

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

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

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

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

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

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

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

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

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

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

נניח שרצית לוודא שכל המסמכים שנוצרו באוסף 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() בכללי האבטחה שלך.

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

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