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

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

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

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.

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

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

RulesTestEnvironment.clearFirestore() => Promise<void>

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

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

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

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

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

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

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

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

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