การเขียนเงื่อนไขสำหรับกฎความปลอดภัยของ Cloud Firestore

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

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

การตรวจสอบสิทธิ์

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

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 จะอนุญาตหรือปฏิเสธการเข้าถึงแบบไดนามิกตามข้อมูลเอกสารได้ดังนี้

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)

ในตัวอย่างด้านล่าง คำสั่งจับคู่ match /databases/{database}/documents จะบันทึกตัวแปร database และใช้เพื่อสร้างเส้นทาง

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

เข้าถึงขีดจำกัดการโทร

มีการจำกัดการเรียกการเข้าถึงเอกสารต่อการประเมินชุดกฎ ดังนี้

  • 10 สำหรับคำขอเอกสารและคำขอการค้นหารายการเดียว
  • 20 สำหรับการอ่านเอกสารหลายเอกสาร ธุรกรรม และการเขียนแบบกลุ่ม ขีดจำกัดก่อนหน้านี้ที่ 10 รายการมีผลกับการดำเนินการแต่ละรายการด้วย

    ตัวอย่างเช่น สมมติว่าคุณสร้างคำขอเขียนแบบกลุ่มที่มีการดำเนินการเขียน 3 รายการ และกฎความปลอดภัยใช้การเรียกการเข้าถึงเอกสาร 2 ครั้งเพื่อตรวจสอบการเขียนแต่ละรายการ ในกรณีนี้ การเขียนแต่ละรายการจะใช้การเรียกการเข้าถึง 2 จาก 10 ครั้ง และคำขอเขียนแบบกลุ่มใช้การเรียกการเข้าถึง 6 จาก 20 ครั้ง

หากเกินขีดจำกัดใดขีดจำกัดจะทำให้เกิดข้อผิดพลาดเกี่ยวกับสิทธิ์ถูกปฏิเสธ การเรียกเข้าถึงเอกสารบางรายการอาจถูกแคชไว้ และไม่นับการเรียกในแคชรวมในขีดจำกัด

สำหรับคำอธิบายโดยละเอียดว่าขีดจำกัดเหล่านี้ส่งผลต่อธุรกรรมและการเขียนแบบกลุ่มอย่างไร โปรดดูคู่มือการรักษาความปลอดภัยการดำเนินการระดับอะตอม

เข้าถึงการโทรและราคา

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

ฟังก์ชันที่กำหนดเอง

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

  • ฟังก์ชันจะมีคำสั่ง return ได้เพียงคำสั่งเดียว และไม่สามารถมีตรรกะเพิ่มเติม เช่น ไม่สามารถใช้งานลูปหรือเรียกใช้บริการภายนอกได้
  • ฟังก์ชันจะเข้าถึงฟังก์ชันและตัวแปรจากขอบเขตที่กำหนดได้โดยอัตโนมัติ เช่น ฟังก์ชันที่กำหนดไว้ในขอบเขต service cloud.firestore มีสิทธิ์เข้าถึงตัวแปร resource และฟังก์ชันในตัว เช่น get() และ exists()
  • ฟังก์ชันต่างๆ อาจเรียกใช้ฟังก์ชันอื่นๆ แต่อาจไม่แสดงใหม่ ความลึกของสแต็กการโทรทั้งหมดจำกัดไว้ที่ 10
  • ในกฎเวอร์ชัน v2 ฟังก์ชันจะกำหนดตัวแปรโดยใช้คีย์เวิร์ด let ได้ ฟังก์ชันมีการเชื่อมโยง Let ๆ ได้สูงสุด 10 รายการ แต่ต้องสิ้นสุดด้วยคำสั่งส่งกลับ

ระบบจะกำหนดฟังก์ชันด้วยคีย์เวิร์ด function และไม่มีอาร์กิวเมนต์ใดๆ เช่น คุณอาจต้องการรวมเงื่อนไข 2 ประเภทที่ใช้ในตัวอย่างด้านบนเข้าเป็นฟังก์ชันเดียว

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 จะประเมินการค้นหาแต่ละรายการตามผลลัพธ์ที่อาจเกิดขึ้น และจะดำเนินการคำขอไม่สำเร็จหากการค้นหาสามารถแสดงเอกสารที่ไคลเอ็นต์ไม่มีสิทธิ์อ่าน โดยการค้นหาต้องเป็นไปตามข้อจำกัดที่กำหนดโดยกฎความปลอดภัย ดูข้อมูลเพิ่มเติมเกี่ยวกับกฎความปลอดภัยและการค้นหาได้ที่การค้นหาข้อมูลอย่างปลอดภัย

ขั้นตอนถัดไป