ควบคุมการเข้าถึงช่องที่ต้องการ

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

จากนั้นคุณจะเพิ่มกฎความปลอดภัยที่มีระดับการเข้าถึงที่แตกต่างกันสำหรับ 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 อย่างน้อย 1 ฟิลด์ คุณสามารถ โดยโทรไปที่ 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']);
    }
  }
}

การทำเช่นนี้ช่วยให้สามารถสร้างร้านอาหารโดยใช้ฟิลด์อื่นๆ ได้เช่นกัน แต่จะทำให้ เอกสารทั้งหมดที่ไคลเอ็นต์สร้างขึ้นมีช่องอย่างน้อย 3 ช่องนี้

การห้ามการเสนอราคาในช่องที่เฉพาะเจาะจงในเอกสารใหม่

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