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

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

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

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

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

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

Cloud Firestore Security Rules มี 2 ส่วน ดังนี้

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

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

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

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

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

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

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

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

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

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

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

ดูวิธีการทดสอบเฉพาะอีมูเลเตอร์ใน SDK v9 ด้วย

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 ซึ่งใช้บริบท Security-Rules-bypassing และแสดงผล 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'), { ... });

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

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

RulesTestEnvironment.clearFirestore() => Promise<void>

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

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

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

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

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