ทดสอบกฎความปลอดภัยของ Cloud Firestore

ขณะสร้างแอป คุณอาจต้องการล็อกการเข้าถึงฐานข้อมูล Cloud Firestore อย่างไรก็ตาม ก่อนเปิดตัว คุณจะต้องมีCloud Firestore Security Rulesที่ละเอียดยิ่งขึ้น เมื่อใช้โปรแกรมจำลอง Cloud Firestore นอกจากการสร้างต้นแบบและการทดสอบฟีเจอร์และลักษณะการทำงานทั่วไปของแอปแล้ว คุณยังสามารถเขียน Unit Test ที่ตรวจสอบลักษณะการทำงานของ Cloud Firestore Security Rules ได้ด้วย

คู่มือเริ่มใช้งานฉบับย่อ

สำหรับกรอบการทดสอบพื้นฐานบางรายการที่มีกฎง่ายๆ ให้ลองใช้ตัวอย่างการเริ่มต้นใช้งานด่วน

ทำความเข้าใจCloud Firestore Security Rules

ใช้ Firebase Authentication และ Cloud Firestore Security Rules สำหรับการตรวจสอบสิทธิ์ การให้สิทธิ์ และการตรวจสอบข้อมูลแบบ Serverless เมื่อคุณใช้ไลบรารีของไคลเอ็นต์บนอุปกรณ์เคลื่อนที่และเว็บ

Cloud Firestore Security Rules ประกอบด้วย 2 ส่วน ได้แก่

  1. คำสั่ง match ที่ระบุเอกสารในฐานข้อมูล
  2. นิพจน์ allow ที่ควบคุมการเข้าถึงเอกสารเหล่านั้น

Firebase Authentication จะยืนยันข้อมูลเข้าสู่ระบบของผู้ใช้และเป็นรากฐานสำหรับระบบการเข้าถึงตามฐานผู้ใช้และตามบทบาท

คำขอฐานข้อมูลทุกรายการจากไลบรารีของไคลเอ็นต์บนอุปกรณ์เคลื่อนที่/เว็บบนมือถือของ Rules จะได้รับการประเมินเทียบกับกฎความปลอดภัยก่อนที่จะอ่านหรือเขียนข้อมูลใดๆ หากกฎปฏิเสธการเข้าถึงเส้นทางเอกสารที่ระบุ คำขอทั้งหมดจะล้มเหลว

ดูข้อมูลเพิ่มเติมเกี่ยวกับ Cloud Firestore Security Rules ในเริ่มต้นใช้งาน Cloud Firestore Security Rules

ติดตั้งโปรแกรมจำลอง

หากต้องการติดตั้งโปรแกรมจำลอง Cloud Firestore ให้ใช้ Firebase CLI และเรียกใช้คำสั่งด้านล่าง

firebase setup:emulators:firestore

เรียกใช้โปรแกรมจำลอง

เริ่มต้นด้วยการเริ่มต้นโปรเจ็กต์ Firebase ในไดเรกทอรีการทำงาน ซึ่งเป็นขั้นตอนแรกที่พบบ่อยเมื่อใช้ Firebase CLI

firebase init

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

firebase emulators:start --only firestore

ในหลายกรณี คุณอาจต้องการเริ่มโปรแกรมจำลอง เรียกใช้ชุดโปรแกรมทดสอบ แล้วปิดโปรแกรมจำลองหลังจากการทดสอบทำงาน คุณสามารถทำได้ง่ายๆ โดยใช้คำสั่ง emulators:exec ดังนี้

firebase emulators:exec --only firestore "./my-test-script.sh"

เมื่อเริ่มต้น โปรแกรมจำลองจะพยายามทำงานในพอร์ตเริ่มต้น (8080) คุณเปลี่ยนพอร์ตโปรแกรมจำลองได้โดยแก้ไขส่วน "emulators" ของไฟล์ firebase.json ดังนี้

{
  // ...
  "emulators": {
    "firestore": {
      "port": "YOUR_PORT"
    }
  }
}

ก่อนเรียกใช้โปรแกรมจำลอง

โปรดคำนึงถึงสิ่งต่อไปนี้ก่อนเริ่มใช้โปรแกรมจำลอง

  • ในตอนแรก โปรแกรมจำลองจะโหลดกฎที่ระบุไว้ในช่อง firestore.rules ของไฟล์ firebase.json โดยจะคาดหวังชื่อไฟล์ในเครื่องที่มี Cloud Firestore Security Rules ของคุณ และใช้กฎเหล่านั้นกับโปรเจ็กต์ทั้งหมด หากคุณไม่ได้ระบุเส้นทางไฟล์ในเครื่องหรือใช้เมธอด loadFirestoreRules ตามที่อธิบายไว้ด้านล่าง โปรแกรมจำลองจะถือว่าโปรเจ็กต์ทั้งหมดมีกฎแบบเปิด
  • แม้ว่า Firebase SDK ส่วนใหญ่จะทำงานร่วมกับโปรแกรมจำลองได้โดยตรง แต่มีเพียงไลบรารี @firebase/rules-unit-testing เท่านั้นที่รองรับการจำลอง auth ในกฎความปลอดภัย ซึ่งช่วยให้การทำ Unit Test ง่ายขึ้นมาก นอกจากนี้ ไลบรารียังรองรับฟีเจอร์เฉพาะของโปรแกรมจำลองบางอย่าง เช่น การล้างข้อมูลทั้งหมด ดังที่แสดงด้านล่าง
  • นอกจากนี้ โปรแกรมจำลองยังยอมรับโทเค็น Firebase Auth เวอร์ชันที่ใช้งานจริงซึ่งได้จาก SDK ของไคลเอ็นต์และประเมินกฎตามนั้น ซึ่งช่วยให้คุณเชื่อมต่อแอปพลิเคชันกับโปรแกรมจำลองได้โดยตรงในการทดสอบการผสานรวมและการทดสอบด้วยตนเอง

เรียกใช้ Unit Test ในเครื่อง

เรียกใช้การทำ Unit Test ในเครื่องด้วย JavaScript SDK เวอร์ชัน 9

Firebase เผยแพร่ไลบรารีการทำ Unit Test ของกฎความปลอดภัยทั้งใน JavaScript SDK เวอร์ชัน 9 และ SDK เวอร์ชัน 8 API ของไลบรารีจะแตกต่างกันอย่างมาก เราขอแนะนำให้ใช้ไลบรารีการทดสอบ v9 ซึ่งมีประสิทธิภาพมากกว่าและต้องมีการตั้งค่าน้อยกว่าเพื่อเชื่อมต่อกับโปรแกรมจำลอง จึงหลีกเลี่ยงการใช้ทรัพยากรเวอร์ชันที่ใช้งานจริงโดยไม่ตั้งใจได้อย่างปลอดภัย เรายังคงให้บริการไลบรารีการทดสอบ v8 เพื่อให้เข้ากันได้แบบย้อนหลัง

ใช้โมดูล @firebase/rules-unit-testing เพื่อโต้ตอบกับโปรแกรมจำลองที่ทำงานในเครื่อง หากคุณได้รับข้อผิดพลาดเกี่ยวกับระยะหมดเวลาหรือข้อผิดพลาด ECONNREFUSED ให้ตรวจสอบอีกครั้งว่าโปรแกรมจำลองทำงานอยู่จริง

เราขอแนะนำอย่างยิ่งให้ใช้ Node.js เวอร์ชันล่าสุดเพื่อให้คุณใช้สัญกรณ์ async/await ได้ ลักษณะการทำงานเกือบทั้งหมดที่คุณอาจต้องการทดสอบเกี่ยวข้องกับฟังก์ชันแบบอะซิงโครนัส และโมดูลการทดสอบได้รับการออกแบบมาให้ทำงานกับโค้ดที่อิงตาม Promise

ไลบรารีการทำ Unit Test ของกฎ v9 จะรับรู้ถึงโปรแกรมจำลองเสมอและจะไม่แตะทรัพยากรเวอร์ชันที่ใช้งานจริง

คุณนำเข้าไลบรารีโดยใช้คำสั่งนำเข้าแบบโมดูลาร์ v9 เช่น

import {
  assertFails,
  assertSucceeds,
  initializeTestEnvironment
} from "@firebase/rules-unit-testing"

// Use `const { … } = require("@firebase/rules-unit-testing")` if imports are not supported
// Or we suggest `const testing = require("@firebase/rules-unit-testing")` if necessary.

เมื่อนำเข้าแล้ว การใช้ Unit Test จะเกี่ยวข้องกับสิ่งต่อไปนี้

  • การสร้างและกำหนดค่า RulesTestEnvironment ด้วยการเรียกใช้ initializeTestEnvironment
  • การตั้งค่าข้อมูลทดสอบโดยไม่เรียกใช้กฎ โดยใช้วิธีที่สะดวกซึ่งช่วยให้คุณข้ามกฎเหล่านั้นได้ชั่วคราว RulesTestEnvironment.withSecurityRulesDisabled
  • การตั้งค่าชุดโปรแกรมทดสอบและฮุกก่อน/หลังการทดสอบแต่ละครั้งด้วยการเรียกใช้เพื่อล้างข้อมูลและสภาพแวดล้อมการทดสอบ เช่น RulesTestEnvironment.cleanup() หรือ RulesTestEnvironment.clearFirestore()
  • การใช้กรอบการทดสอบที่เลียนแบบสถานะการตรวจสอบสิทธิ์โดยใช้ RulesTestEnvironment.authenticatedContext และ RulesTestEnvironment.unauthenticatedContext

เมธอดและฟังก์ชันยูทิลิตีทั่วไป

โปรดดูวิธีการทดสอบเฉพาะโปรแกรมจำลองใน v9 SDK ด้วย

initializeTestEnvironment() => RulesTestEnvironment

ฟังก์ชันนี้จะเริ่มต้นสภาพแวดล้อมการทดสอบสำหรับการทำ Unit Test ของกฎ เรียกใช้ฟังก์ชันนี้ก่อนเพื่อตั้งค่าการทดสอบ การดำเนินการที่สำเร็จต้องใช้โปรแกรมจำลองที่ทำงานอยู่

ฟังก์ชันนี้ยอมรับออบเจ็กต์ที่ไม่บังคับซึ่งกำหนด TestEnvironmentConfig ซึ่งอาจประกอบด้วยรหัสโปรเจ็กต์และการตั้งค่าการกำหนดค่าโปรแกรมจำลอง

let testEnv = await initializeTestEnvironment({
  projectId: "demo-project-1234",
  firestore: {
    rules: fs.readFileSync("firestore.rules", "utf8"),
  },
});

RulesTestEnvironment.authenticatedContext({ user_id: string, tokenOptions?: TokenOptions }) => RulesTestContext

เมธอดนี้จะสร้าง RulesTestContext ซึ่งทำงานเหมือนกับผู้ใช้ที่ผ่านการตรวจสอบสิทธิ์แล้ว คำขอที่สร้างผ่านบริบทที่แสดงผลจะมีโทเค็นการตรวจสอบสิทธิ์จำลองแนบมาด้วย คุณสามารถส่งออบเจ็กต์ที่กำหนดการอ้างสิทธิ์ที่กำหนดเองหรือการลบล้างสำหรับเพย์โหลดโทเค็นการตรวจสอบสิทธิ์ได้ (ไม่บังคับ)

ใช้ออบเจ็กต์บริบทการทดสอบที่แสดงผลในการทดสอบเพื่อเข้าถึงอินสแตนซ์โปรแกรมจำลองที่กำหนดค่าไว้ รวมถึงอินสแตนซ์ที่กำหนดค่าด้วย initializeTestEnvironment

// Assuming a Firestore app and the Firestore emulator for this example
import { setDoc } from "firebase/firestore";

const alice = testEnv.authenticatedContext("alice", { … });
// Use the Firestore instance associated with this context
await assertSucceeds(setDoc(alice.firestore().doc('/users/alice'), { ... });

RulesTestEnvironment.unauthenticatedContext() => RulesTestContext

วิธีนี้จะสร้าง RulesTestContext ซึ่งทำงานเหมือนกับไคลเอ็นต์ที่ไม่ได้เข้าสู่ระบบผ่านการตรวจสอบสิทธิ์ คำขอที่สร้างผ่านบริบทที่แสดงผลจะไม่มีโทเค็น Firebase Auth แนบมาด้วย

ใช้ออบเจ็กต์บริบทการทดสอบที่แสดงผลในการทดสอบเพื่อเข้าถึงอินสแตนซ์โปรแกรมจำลองที่กำหนดค่าไว้ รวมถึงอินสแตนซ์ที่กำหนดค่าด้วย initializeTestEnvironment

// Assuming a Cloud Storage app and the Storage emulator for this example
import { getStorage, ref, deleteObject } from "firebase/storage";

const alice = testEnv.unauthenticatedContext();

// Use the Cloud Storage instance associated with this context
const desertRef = ref(alice.storage(), 'images/desert.jpg');
await assertSucceeds(deleteObject(desertRef));

RulesTestEnvironment.withSecurityRulesDisabled()

เรียกใช้ฟังก์ชันการตั้งค่าการทดสอบด้วยบริบทที่ทำงานเสมือนว่ากฎความปลอดภัยถูกปิดใช้อยู่

เมธอดนี้ใช้ฟังก์ชัน Callback ซึ่งใช้บริบทการข้ามกฎความปลอดภัยและแสดงผล Promise บริบทจะถูกทำลายเมื่อ Promise ได้รับการแก้ไข / ปฏิเสธ

RulesTestEnvironment.cleanup()

วิธีนี้จะทำลาย RulesTestContexts ทั้งหมดที่สร้างขึ้นในสภาพแวดล้อมการทดสอบและล้างทรัพยากรที่เกี่ยวข้อง เพื่อให้สามารถออกได้อย่างสมบูรณ์

วิธีนี้จะไม่เปลี่ยนสถานะของโปรแกรมจำลองแต่อย่างใด หากต้องการรีเซ็ตข้อมูลระหว่างการทดสอบ ให้ใช้วิธีล้างข้อมูลเฉพาะของโปรแกรมจำลองแอปพลิเคชัน

assertSucceeds(pr: Promise<any>)) => Promise<any>

นี่คือฟังก์ชันยูทิลิตีของกรอบการทดสอบ

ฟังก์ชันนี้ยืนยันว่า Promise ที่ระบุซึ่งรวมการดำเนินการของโปรแกรมจำลองจะได้รับการแก้ไขโดยไม่มีการละเมิดกฎความปลอดภัย

await assertSucceeds(setDoc(alice.firestore(), '/users/alice'), { ... });

assertFails(pr: Promise<any>)) => Promise<any>

นี่คือฟังก์ชันยูทิลิตีของกรอบการทดสอบ

ฟังก์ชันนี้ยืนยันว่า Promise ที่ระบุซึ่งรวมการดำเนินการของโปรแกรมจำลองจะถูกปฏิเสธเนื่องจากละเมิดกฎความปลอดภัย

await assertFails(setDoc(alice.firestore(), '/users/bob'), { ... });

วิธีการเฉพาะสำหรับโปรแกรมจำลอง

นอกจากนี้ โปรดดูวิธีการทดสอบและฟังก์ชันยูทิลิตีทั่วไปใน v9 SDK

RulesTestEnvironment.clearFirestore() => Promise<void>

วิธีนี้จะล้างข้อมูลในฐานข้อมูล Firestore ที่เป็นของ projectId ซึ่งกำหนดค่าไว้สำหรับโปรแกรมจำลอง Firestore

RulesTestContext.firestore(settings?: Firestore.FirestoreSettings) => Firestore;

เมธอดนี้จะรับอินสแตนซ์ Firestore สำหรับบริบทการทดสอบนี้ อินสแตนซ์ Firebase JS Client SDK ที่แสดงผลสามารถใช้กับ API ของ SDK ของไคลเอ็นต์ (v9 แบบแยกส่วนหรือ v9 compat)

แสดงภาพการประเมินกฎ

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

เปิดแท็บ Firestore > คำขอเพื่อดูลำดับการประเมินโดยละเอียดสำหรับคำขอแต่ละรายการ

เครื่องมือตรวจสอบคำขอของโปรแกรมจำลอง Firestore ที่แสดงการประเมินกฎความปลอดภัย

สร้างรายงานการทดสอบ

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

หากต้องการรับรายงาน ให้ค้นหาปลายทางที่แสดงในโปรแกรมจำลองขณะที่ทำงานอยู่ หากต้องการเวอร์ชันที่เหมาะกับเบราว์เซอร์ ให้ใช้ URL ต่อไปนี้

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage.html

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

http://localhost:8080/emulator/v1/projects/<project_id>:ruleCoverage

ความแตกต่างระหว่างโปรแกรมจำลองและเวอร์ชันที่ใช้งานจริง

  1. คุณไม่จำเป็นต้องสร้างโปรเจ็กต์ Cloud Firestore อย่างชัดเจน โปรแกรมจำลองจะสร้างอินสแตนซ์ที่เข้าถึงโดยอัตโนมัติ
  2. โปรแกรมจำลอง Cloud Firestore ไม่ทำงานกับขั้นตอน Firebase Authentication ปกติ แต่ใน Firebase Test SDK เราได้จัดเตรียมเมธอด initializeTestApp() ไว้ในไลบรารี rules-unit-testing ซึ่งใช้ฟิลด์ auth แฮนเดิล Firebase ที่สร้างขึ้นโดยใช้วิธีนี้จะทำงานเสมือนว่าได้รับการตรวจสอบสิทธิ์เรียบร้อยแล้วในฐานะเอนทิตีใดก็ตามที่คุณระบุ หากคุณส่ง null ระบบจะทำงานเหมือนเป็นผู้ใช้ที่ไม่ได้รับการตรวจสอบสิทธิ์ (เช่น กฎ auth != null จะไม่สำเร็จ)

แก้ปัญหาที่ทราบแล้ว

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

ลักษณะการทดสอบไม่สอดคล้องกัน

หากการทดสอบผ่านและไม่ผ่านเป็นครั้งคราว แม้ว่าจะไม่มีการเปลี่ยนแปลงใดๆ กับการทดสอบ คุณอาจต้องตรวจสอบว่าการทดสอบมีลำดับที่ถูกต้อง การโต้ตอบส่วนใหญ่กับโปรแกรมจำลองจะเป็นแบบอะซิงโครนัส ดังนั้นโปรดตรวจสอบอีกครั้งว่าโค้ดแบบอะซิงโครนัสทั้งหมดได้รับการจัดลำดับอย่างถูกต้อง คุณสามารถแก้ไขการจัดลำดับได้โดยการเชื่อมโยง Promise หรือใช้สัญกรณ์ await อย่างอิสระ

โดยเฉพาะอย่างยิ่ง ให้ตรวจสอบการดำเนินการแบบไม่พร้อมกันต่อไปนี้

  • การตั้งค่ากฎความปลอดภัย เช่น initializeTestEnvironment
  • การอ่านและเขียนข้อมูล เช่น ด้วย db.collection("users").doc("alice").get()
  • การยืนยันด้านการปฏิบัติงาน ซึ่งรวมถึง assertSucceeds และ assertFails

การทดสอบจะผ่านเฉพาะครั้งแรกที่คุณโหลดโปรแกรมจำลองเท่านั้น

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

  • ใช้รหัสโปรเจ็กต์ที่ไม่ซ้ำกันสำหรับการทดสอบแต่ละครั้ง โปรดทราบว่าหากเลือกดำเนินการนี้ คุณจะต้องเรียกใช้ initializeTestEnvironment เป็นส่วนหนึ่งของการทดสอบแต่ละรายการ ระบบจะโหลดกฎโดยอัตโนมัติสำหรับรหัสโปรเจ็กต์เริ่มต้นเท่านั้น
  • ปรับโครงสร้างการทดสอบเพื่อไม่ให้โต้ตอบกับข้อมูลที่เขียนไว้ก่อนหน้านี้ (เช่น ใช้คอลเล็กชันอื่นสำหรับการทดสอบแต่ละครั้ง)
  • ลบข้อมูลทั้งหมดที่เขียนระหว่างการทดสอบ

การตั้งค่าการทดสอบซับซ้อนมาก

เมื่อตั้งค่าการทดสอบ คุณอาจต้องการแก้ไขข้อมูลในลักษณะที่ Cloud Firestore Security Rules ไม่อนุญาตจริงๆ หากกฎทำให้การตั้งค่าการทดสอบซับซ้อน ให้ลองใช้ RulesTestEnvironment.withSecurityRulesDisabled ในขั้นตอนการตั้งค่า เพื่อไม่ให้การอ่านและการเขียนทริกเกอร์ข้อผิดพลาด PERMISSION_DENIED

หลังจากนั้น การทดสอบจะดำเนินการในฐานะผู้ใช้ที่ผ่านการตรวจสอบสิทธิ์หรือไม่ผ่านการตรวจสอบสิทธิ์ได้โดยใช้ RulesTestEnvironment.authenticatedContext และ unauthenticatedContext ตามลำดับ ซึ่งจะช่วยให้คุณตรวจสอบได้ว่า Cloud Firestore Security Rules อนุญาต / ปฏิเสธเคสต่างๆ อย่างถูกต้องหรือไม่