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

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

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

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

ทำความเข้าใจกฎความปลอดภัยของ Cloud Firestore

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

กฎความปลอดภัยของ Cloud Firestore มี 2 ข้อดังนี้

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

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

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

ดูข้อมูลเพิ่มเติมเกี่ยวกับกฎความปลอดภัยของ Cloud Firestore ในหัวข้อเริ่มต้นใช้งานกฎความปลอดภัยของ Cloud Firestore

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

หากต้องการติดตั้งโปรแกรมจำลอง 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 และใช้กฎเหล่านั้นกับโปรเจ็กต์ทั้งหมด หากไม่ได้ระบุเส้นทางไฟล์ในเครื่องหรือใช้วิธีการ loadFirestoreRules ตามที่อธิบายไว้ด้านล่าง โปรแกรมจำลองจะถือว่าโปรเจ็กต์ทั้งหมดมีกฎแบบเปิด
  • แม้ว่า Firebase SDK ส่วนใหญ่จะทำงานร่วมกับโปรแกรมจำลองโดยตรง แต่มีแค่ไลบรารี @firebase/rules-unit-testing เท่านั้นที่รองรับการจำลอง auth ในกฎการรักษาความปลอดภัย ซึ่งทำให้การทดสอบ 1 หน่วยง่ายขึ้นมาก นอกจากนี้ ไลบรารียังสนับสนุนคุณลักษณะเฉพาะโปรแกรมจำลองบางอย่าง เช่น การล้างข้อมูลทั้งหมด ตามที่แสดงด้านล่าง
  • นอกจากนี้ โปรแกรมจำลองจะยอมรับโทเค็นการตรวจสอบสิทธิ์ Firebase ที่ใช้งานจริงที่ให้บริการผ่าน SDK ของไคลเอ็นต์ และประเมินกฎตามนั้น ซึ่งช่วยให้เชื่อมต่อแอปพลิเคชันกับโปรแกรมจำลองได้โดยตรงในการผสานรวมและการทดสอบด้วยตนเอง

เรียกใช้การทดสอบหน่วยในเครื่อง

เรียกใช้การทดสอบหน่วยในเครื่องด้วย JavaScript SDK v9

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

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

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

ไลบรารีการทดสอบหน่วยกฎ 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.

เมื่อนำเข้าแล้ว การใช้การทดสอบ 1 หน่วยเกี่ยวข้องกับสิ่งต่อไปนี้

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

วิธีการทั่วไปและฟังก์ชันยูทิลิตี

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

initializeTestEnvironment() => RulesTestEnvironment

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

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

RulesTestEnvironment.cleanup()

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

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

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

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

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

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

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

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

ฟังก์ชันนี้จะยืนยันว่าการดำเนินการ "ห่อคำสัญญา" ที่ระบุจะถูกปฏิเสธเนื่องจากมีการละเมิดกฎการรักษาความปลอดภัย

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

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

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

RulesTestEnvironment.clearFirestore() => Promise<void>

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

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

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

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

โปรแกรมจำลอง Cloud Firestore ช่วยให้คุณเห็นภาพคำขอของไคลเอ็นต์ใน UI ของ Emulator Suite รวมถึงการติดตามกฎการรักษาความปลอดภัยของ 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 ปกติ แต่เรามีเมธอด initializeTestApp() ในไลบรารี rules-unit-testing ซึ่งมีฟิลด์ auth อยู่ใน Firebase Test SDK แฮนเดิล Firebase ที่สร้างขึ้นโดยใช้เมธอดนี้จะทำงานเสมือนว่าได้ตรวจสอบสิทธิ์ว่าเป็นเอนทิตีใดก็ตามที่คุณระบุไว้เรียบร้อยแล้ว หากคุณผ่านใน null ระบบจะทำหน้าที่เป็นผู้ใช้ที่ไม่ได้รับการตรวจสอบสิทธิ์ (เช่น กฎ auth != null รายการก็จะล้มเหลว)

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

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

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

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

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

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

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

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

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

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

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

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