Kiểm thử Quy tắc bảo mật của Cloud Firestore

Khi xây dựng ứng dụng, bạn nên khoá quyền truy cập vào cơ sở dữ liệu Cloud Firestore. Tuy nhiên, trước khi phát hành, bạn sẽ cần các Quy tắc bảo mật của Cloud Firestore chi tiết hơn. Với trình mô phỏng Cloud Firestore, ngoài việc tạo nguyên mẫu và kiểm thử các tính năng và hành vi chung của ứng dụng, bạn còn có thể viết mã kiểm thử đơn vị để kiểm tra hành vi của các Quy tắc bảo mật Cloud Firestore.

Bắt đầu nhanh

Đối với một số trường hợp kiểm thử cơ bản có quy tắc đơn giản, hãy thử mẫu bắt đầu nhanh.

Tìm hiểu các quy tắc bảo mật của Cloud Firestore

Triển khai tính năng Xác thực FirebaseQuy tắc bảo mật của Cloud Firestore để xác thực, uỷ quyền và xác thực dữ liệu không máy chủ khi bạn sử dụng thư viện ứng dụng khách dành cho web và thiết bị di động.

Quy tắc bảo mật của Cloud Firestore bao gồm 2 phần:

  1. Câu lệnh match xác định các tài liệu trong cơ sở dữ liệu của bạn.
  2. Biểu thức allow kiểm soát quyền truy cập vào các tài liệu đó.

Tính năng Xác thực Firebase xác minh thông tin xác thực của người dùng và cung cấp nền tảng cho các hệ thống truy cập dựa trên người dùng và dựa trên vai trò.

Mọi yêu cầu cơ sở dữ liệu từ thư viện ứng dụng web/dành cho thiết bị di động Cloud Firestore sẽ được đánh giá theo các quy tắc bảo mật của bạn trước khi đọc hoặc ghi bất kỳ dữ liệu nào. Nếu các quy tắc đó từ chối quyền truy cập vào bất kỳ đường dẫn tài liệu được chỉ định nào, thì toàn bộ yêu cầu sẽ không thành công.

Tìm hiểu thêm về Các quy tắc bảo mật của Cloud Firestore trong bài viết Bắt đầu sử dụng Quy tắc bảo mật của Cloud Firestore.

Cài đặt trình mô phỏng

Để cài đặt trình mô phỏng Cloud Firestore, hãy sử dụng Firebase CLI và chạy lệnh bên dưới:

firebase setup:emulators:firestore

Chạy trình mô phỏng

Bắt đầu bằng cách khởi chạy một dự án Firebase trong thư mục đang hoạt động của bạn. Đây là bước đầu tiên phổ biến khi sử dụng Giao diện dòng lệnh (CLI) của Firebase.

firebase init

Khởi động trình mô phỏng bằng lệnh sau. Trình mô phỏng sẽ chạy cho đến khi bạn dừng quy trình này:

firebase emulators:start --only firestore

Trong nhiều trường hợp, bạn muốn khởi động trình mô phỏng, chạy bộ kiểm thử rồi tắt trình mô phỏng sau khi chạy chương trình kiểm thử. Bạn có thể thực hiện việc này dễ dàng bằng cách sử dụng lệnh emulators:exec:

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

Khi khởi động, trình mô phỏng sẽ cố chạy trên cổng mặc định (8080). Bạn có thể thay đổi cổng trình mô phỏng bằng cách sửa đổi phần "emulators" của tệp firebase.json:

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

Trước khi bạn chạy trình mô phỏng

Trước khi bắt đầu sử dụng trình mô phỏng, hãy lưu ý những điều sau:

  • Ban đầu, trình mô phỏng sẽ tải các quy tắc được chỉ định trong trường firestore.rules của tệp firebase.json. Phần phụ thuộc này yêu cầu tên của một tệp cục bộ chứa Quy tắc bảo mật của Cloud Firestore và áp dụng các quy tắc đó cho tất cả dự án. Nếu bạn không cung cấp đường dẫn tệp cục bộ hoặc sử dụng phương thức loadFirestoreRules như mô tả dưới đây, trình mô phỏng sẽ coi tất cả dự án là có các quy tắc mở.
  • Mặc dù hầu hết SDK Firebase đều hoạt động trực tiếp với trình mô phỏng, nhưng chỉ thư viện @firebase/rules-unit-testing mới hỗ trợ mô phỏng auth trong Quy tắc bảo mật, giúp việc kiểm thử đơn vị trở nên dễ dàng hơn nhiều. Ngoài ra, thư viện hỗ trợ một số tính năng dành riêng cho trình mô phỏng như xoá tất cả dữ liệu, như liệt kê dưới đây.
  • Trình mô phỏng cũng sẽ chấp nhận mã thông báo xác thực Firebase chính thức được cung cấp thông qua SDK ứng dụng và đánh giá các quy tắc theo đó, từ đó cho phép kết nối ứng dụng của bạn trực tiếp với trình mô phỏng trong quá trình tích hợp và kiểm thử thủ công.

Chạy kiểm thử đơn vị cục bộ

Chạy kiểm thử đơn vị cục bộ với SDK JavaScript phiên bản 9

Firebase phân phối một thư viện kiểm thử đơn vị Quy tắc bảo mật với cả SDK JavaScript phiên bản 9 và SDK phiên bản 8 của thư viện đó. Các API thư viện có sự khác biệt đáng kể. Bạn nên sử dụng thư viện kiểm thử v9. Thư viện này được sắp xếp hợp lý hơn và đòi hỏi ít thiết lập hơn để kết nối với trình mô phỏng, nhờ đó tránh được việc vô tình sử dụng tài nguyên phát hành chính thức. Để đảm bảo khả năng tương thích ngược, chúng tôi sẽ tiếp tục cung cấp thư viện kiểm thử phiên bản 8.

Sử dụng mô-đun @firebase/rules-unit-testing để tương tác với trình mô phỏng chạy cục bộ. Nếu bạn gặp lỗi hết thời gian chờ hoặc lỗi ECONNREFUSED, hãy kiểm tra kỹ để đảm bảo trình mô phỏng thực sự đang chạy.

Bạn nên sử dụng phiên bản Node.js gần đây để có thể sử dụng ký hiệu async/await. Hầu hết hành vi mà bạn có thể muốn kiểm thử đều liên quan đến các hàm không đồng bộ và mô-đun kiểm thử được thiết kế để hoạt động với mã dựa trên Promise.

Thư viện Kiểm thử đơn vị quy tắc v9 luôn nhận biết được trình mô phỏng và không bao giờ chạm đến tài nguyên phát hành chính thức của bạn.

Bạn nhập thư viện bằng cách sử dụng các câu lệnh nhập mô-đun v9. Ví dụ:

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.

Sau khi nhập, việc triển khai kiểm thử đơn vị bao gồm:

  • Tạo và định cấu hình RulesTestEnvironment bằng lệnh gọi đến initializeTestEnvironment.
  • Thiết lập dữ liệu kiểm thử mà không kích hoạt Quy tắc, sử dụng một phương thức tiện lợi cho phép bạn tạm thời bỏ qua các quy tắc đó, RulesTestEnvironment.withSecurityRulesDisabled.
  • Thiết lập bộ kiểm thử và mỗi hook trước/sau mỗi lần kiểm thử bằng các lệnh gọi để dọn dẹp dữ liệu và môi trường kiểm thử, chẳng hạn như RulesTestEnvironment.cleanup() hoặc RulesTestEnvironment.clearFirestore().
  • Triển khai các trường hợp kiểm thử bắt chước trạng thái xác thực bằng cách sử dụng RulesTestEnvironment.authenticatedContextRulesTestEnvironment.unauthenticatedContext.

Các phương thức và hàm hiệu dụng phổ biến

Ngoài ra, hãy xem các phương thức kiểm thử dành riêng cho trình mô phỏng trong SDK phiên bản 9.

initializeTestEnvironment() => RulesTestEnvironment

Hàm này khởi tạo một môi trường kiểm thử để kiểm thử đơn vị quy tắc. Trước tiên, hãy gọi hàm này để thiết lập kiểm thử. Để thực thi thành công, bạn phải chạy trình mô phỏng.

Hàm này chấp nhận một đối tượng không bắt buộc xác định TestEnvironmentConfig, có thể bao gồm mã dự án và chế độ cài đặt cấu hình trình mô phỏng.

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

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

Phương thức này sẽ tạo một RulesTestContext, hoạt động giống như một Người dùng xác thực đã được xác thực. Các yêu cầu được tạo thông qua ngữ cảnh trả về sẽ đính kèm một mã thông báo xác thực mô phỏng. Bạn có thể chuyển một đối tượng xác định các thông báo xác nhận quyền sở hữu hoặc ghi đè tuỳ chỉnh cho các tải trọng mã thông báo xác thực.

Sử dụng đối tượng ngữ cảnh kiểm thử được trả về trong các kiểm thử của bạn để truy cập vào mọi thực thể trình mô phỏng đã định cấu hình, bao gồm cả những thực thể được định cấu hình bằng 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

Phương thức này tạo một RulesTestContext, hoạt động như một ứng dụng khách không được đăng nhập thông qua tính năng Xác thực. Các yêu cầu được tạo thông qua ngữ cảnh trả về sẽ không đính kèm mã thông báo xác thực Firebase.

Sử dụng đối tượng ngữ cảnh kiểm thử được trả về trong các kiểm thử của bạn để truy cập vào mọi thực thể trình mô phỏng đã định cấu hình, bao gồm cả những thực thể được định cấu hình bằng 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()

Chạy hàm thiết lập kiểm thử với ngữ cảnh hoạt động như thể Quy tắc bảo mật đã bị tắt.

Phương thức này sử dụng một hàm callback, lấy bối cảnh bỏ qua Quy tắc bảo mật và trả về một lời hứa. Ngữ cảnh sẽ bị huỷ sau khi lời hứa được phân giải / từ chối.

RulesTestEnvironment.cleanup()

Phương thức này sẽ huỷ tất cả RulesTestContexts được tạo trong môi trường kiểm thử và dọn dẹp các tài nguyên cơ bản, cho phép thoát sạch.

Phương thức này không thay đổi trạng thái của trình mô phỏng theo bất kỳ cách nào. Để đặt lại dữ liệu giữa các lần kiểm thử, hãy sử dụng phương thức xoá dữ liệu dành riêng cho trình mô phỏng ứng dụng.

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

Đây là một hàm hiệu dụng trường hợp kiểm thử.

Hàm này khẳng định rằng Promise đã cung cấp gói hoạt động của trình mô phỏng sẽ được giải quyết mà không có lỗi vi phạm Quy tắc bảo mật nào.

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

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

Đây là một hàm hiệu dụng trường hợp kiểm thử.

Hàm này xác nhận rằng Promise đã cung cấp gói hoạt động của trình mô phỏng sẽ bị từ chối do vi phạm Quy tắc bảo mật.

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

Phương thức dành riêng cho trình mô phỏng

Ngoài ra, hãy xem các phương pháp kiểm thử và hàm hiệu dụng phổ biến trong SDK phiên bản 9.

RulesTestEnvironment.clearFirestore() => Promise<void>

Phương thức này xoá dữ liệu trong cơ sở dữ liệu Firestore thuộc về projectId đã định cấu hình cho trình mô phỏng Firestore.

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

Phương thức này nhận một thực thể Firestore cho ngữ cảnh kiểm thử này. Bạn có thể sử dụng phiên bản SDK ứng dụng Firebase JS được trả về với các API SDK ứng dụng (mô-đun phiên bản 9 hoặc khả năng tương thích phiên bản 9).

Trực quan hoá việc đánh giá quy tắc

Trình mô phỏng Cloud Firestore cho phép bạn trực quan hoá các yêu cầu của ứng dụng trong giao diện người dùng của Bộ công cụ mô phỏng, bao gồm cả hoạt động theo dõi hoạt động đánh giá cho Quy tắc bảo mật của Firebase.

Mở thẻ Firestore > Yêu cầu để xem trình tự đánh giá chi tiết cho từng yêu cầu.

Màn hình yêu cầu của Trình mô phỏng Firestore cho thấy các kết quả đánh giá về Quy tắc bảo mật

Tạo báo cáo kiểm thử

Sau khi chạy một bộ kiểm thử, bạn có thể truy cập vào báo cáo kiểm thử mức độ phù hợp, cho biết cách đánh giá từng quy tắc bảo mật của bạn.

Để nhận báo cáo, hãy truy vấn một điểm cuối hiển thị trên trình mô phỏng khi trình mô phỏng đang chạy. Đối với phiên bản thân thiện với trình duyệt, hãy sử dụng URL sau:

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

Thao tác này chia các quy tắc của bạn thành các biểu thức và biểu thức phụ mà bạn có thể di chuột qua để biết thêm thông tin, bao gồm cả số lượng đánh giá và giá trị được trả về. Đối với phiên bản JSON thô của dữ liệu này, hãy đưa URL sau vào truy vấn của bạn:

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

Sự khác biệt giữa trình mô phỏng và bản phát hành chính thức

  1. Bạn không cần phải tạo dự án Cloud Firestore một cách rõ ràng. Trình mô phỏng sẽ tự động tạo mọi thực thể được truy cập.
  2. Trình mô phỏng Cloud Firestore không hoạt động với quy trình Xác thực Firebase thông thường. Thay vào đó, trong SDK thử nghiệm Firebase, chúng tôi đã cung cấp phương thức initializeTestApp() trong thư viện rules-unit-testing. Phương thức này nhận trường auth. Tên người dùng Firebase được tạo bằng phương thức này sẽ hoạt động như thể đã được xác thực thành công là bất kỳ thực thể nào mà bạn cung cấp. Nếu bạn truyền vào null, đối tượng đó sẽ hoạt động như một người dùng chưa được xác thực (ví dụ: quy tắc auth != null sẽ không thành công).

Khắc phục các vấn đề đã biết

Khi sử dụng trình mô phỏng Cloud Firestore, bạn có thể gặp phải các vấn đề đã biết sau. Hãy làm theo hướng dẫn bên dưới để khắc phục mọi hành vi bất thường mà bạn đang gặp phải. Các ghi chú này được viết với sự lưu tâm đến thư viện kiểm thử đơn vị Quy tắc bảo mật, nhưng phương pháp chung đều có thể áp dụng cho mọi SDK Firebase.

Hoạt động thử nghiệm không nhất quán

Nếu các chương trình kiểm thử của bạn đôi khi đạt và không thành công, ngay cả khi không có bất kỳ thay đổi nào đối với quy trình kiểm thử, thì bạn có thể cần phải xác minh rằng các chương trình kiểm thử đó được sắp xếp theo trình tự đúng cách. Hầu hết các hoạt động tương tác với trình mô phỏng đều không đồng bộ, vì vậy, hãy kiểm tra kỹ để đảm bảo rằng tất cả mã không đồng bộ đều được sắp xếp theo trình tự đúng cách. Bạn có thể khắc phục trình tự bằng cách tạo chuỗi hứa hẹn hoặc thoải mái sử dụng ký hiệu await.

Cụ thể, hãy xem xét các hoạt động không đồng bộ sau đây:

  • Đặt các quy tắc bảo mật với initializeTestEnvironment chẳng hạn.
  • Đọc và ghi dữ liệu, chẳng hạn như db.collection("users").doc("alice").get().
  • Xác nhận hoạt động, bao gồm assertSucceedsassertFails.

Các lượt kiểm thử chỉ đạt trong lần đầu tiên bạn tải trình mô phỏng

Trình mô phỏng có trạng thái. Nó lưu trữ tất cả dữ liệu được ghi vào bộ nhớ, vì vậy, mọi dữ liệu sẽ bị mất bất cứ khi nào trình mô phỏng tắt. Nếu bạn đang chạy nhiều lượt kiểm thử cho cùng một mã dự án, thì mỗi lượt kiểm thử có thể tạo ra dữ liệu có thể ảnh hưởng đến các lượt kiểm thử tiếp theo. Bạn có thể sử dụng bất kỳ phương thức nào sau đây để bỏ qua hành vi này:

  • Sử dụng mã dự án duy nhất cho mỗi lần kiểm thử. Xin lưu ý rằng nếu chọn phương án này, bạn cần gọi initializeTestEnvironment trong mỗi lần kiểm thử; các quy tắc chỉ được tải tự động cho mã dự án mặc định.
  • Điều chỉnh cấu trúc chương trình kiểm thử để chúng không tương tác với dữ liệu đã ghi trước đó (ví dụ: sử dụng một bộ sưu tập khác cho mỗi kiểm thử).
  • Xoá tất cả dữ liệu đã ghi trong quá trình kiểm thử.

Quá trình thiết lập thử nghiệm rất phức tạp

Khi thiết lập kiểm thử, bạn nên sửa đổi dữ liệu theo cách mà các Quy tắc bảo mật của Cloud Firestore không thực sự cho phép. Nếu các quy tắc của bạn khiến việc thiết lập kiểm thử trở nên phức tạp, hãy thử sử dụng RulesTestEnvironment.withSecurityRulesDisabled trong các bước thiết lập để việc đọc và ghi sẽ không gây ra lỗi PERMISSION_DENIED.

Sau đó, chương trình kiểm thử của bạn có thể thực hiện các thao tác với tư cách là người dùng đã được xác thực hoặc chưa được xác thực bằng cách sử dụng RulesTestEnvironment.authenticatedContextunauthenticatedContext tương ứng. Việc này giúp bạn xác thực rằng các Quy tắc bảo mật của Cloud Firestore cho phép / từ chối nhiều trường hợp một cách chính xác.