ทดสอบกฎความปลอดภัยของ 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 จะยืนยันข้อมูลเข้าสู่ระบบของผู้ใช้และเป็นรากฐานสำหรับระบบการเข้าถึงตามฐานผู้ใช้และตามบทบาท

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

ดูข้อมูลเพิ่มเติมเกี่ยวกับ 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 ระบบจะทำลายบริบทเมื่อสัญญาได้รับการแก้ไข/ปฏิเสธ

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 modular หรือ 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 อนุญาต/ปฏิเสธเคสต่างๆ อย่างถูกต้องหรือไม่