ควบคุมการเข้าถึงฟิลด์เฉพาะ

หน้านี้สร้างขึ้นจากแนวคิดใน การจัดโครงสร้างกฎความปลอดภัย และ เงื่อนไขการเขียนสำหรับกฎความปลอดภัย เพื่ออธิบายวิธีที่คุณสามารถใช้กฎความปลอดภัยของ Cloud Firestore เพื่อสร้างกฎที่อนุญาตให้ไคลเอนต์ดำเนินการกับบางฟิลด์ในเอกสาร แต่ไม่ใช่ฟิลด์อื่น

อาจมีบางครั้งที่คุณต้องการควบคุมการเปลี่ยนแปลงในเอกสารที่ไม่ได้อยู่ที่ระดับเอกสาร แต่อยู่ที่ระดับฟิลด์

ตัวอย่างเช่น คุณอาจต้องการอนุญาตให้ไคลเอ็นต์สร้างหรือเปลี่ยนแปลงเอกสาร แต่ไม่อนุญาตให้แก้ไขฟิลด์บางฟิลด์ในเอกสารนั้น หรือคุณอาจต้องการบังคับใช้เอกสารใดๆ ที่ลูกค้าสร้างขึ้นเสมอจะมีชุดฟิลด์บางชุด คู่มือนี้ครอบคลุมถึงวิธีที่คุณสามารถทำงานบางอย่างเหล่านี้ให้สำเร็จโดยใช้กฎความปลอดภัยของ Cloud Firestore

อนุญาตให้เข้าถึงแบบอ่านเฉพาะบางฟิลด์เท่านั้น

การอ่านใน Cloud Firestore ดำเนินการในระดับเอกสาร คุณอาจดึงเอกสารฉบับเต็ม หรือไม่ดึงข้อมูลเลย ไม่มีวิธีเรียกเอกสารบางส่วนกลับมา ไม่สามารถใช้กฎความปลอดภัยเพียงอย่างเดียวเพื่อป้องกันไม่ให้ผู้ใช้อ่านฟิลด์เฉพาะภายในเอกสาร

หากมีฟิลด์บางฟิลด์ในเอกสารที่คุณต้องการซ่อนไม่ให้ผู้ใช้บางรายซ่อน วิธีที่ดีที่สุดคือการใส่ฟิลด์เหล่านั้นในเอกสารแยกต่างหาก ตัวอย่างเช่น คุณอาจพิจารณาสร้างเอกสารในคอลเลกชันย่อย private ดังนี้:

/พนักงาน/{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() กับรายการฟิลด์ที่ต้องห้าม วิธีนี้จะประเมินว่าเป็นจริงหากเอกสารมีฟิลด์ใด ๆ เหล่านี้ ดังนั้นคุณอาจต้องการลบล้างผลลัพธ์เพื่อห้ามบางฟิลด์

ตัวอย่างเช่น ในตัวอย่างต่อไปนี้ ไคลเอ็นต์ไม่ได้รับอนุญาตให้สร้างเอกสารที่มีฟิลด์ 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 คืออนุญาตให้ไคลเอ็นต์ทำการเปลี่ยนแปลงเอกสารหรือปฏิเสธการแก้ไขทั้งหมด คุณไม่สามารถสร้างกฎความปลอดภัยที่ยอมรับการเขียนบางฟิลด์ในเอกสารของคุณในขณะที่ปฏิเสธฟิลด์อื่นในการดำเนินการเดียวกัน