Trang này dựa trên các khái niệm trong Cấu trúc quy tắc bảo mật và Viết điều kiện cho quy tắc bảo mật để giải thích cách bạn có thể sử dụng Cloud Firestore Security Rules để tạo các quy tắc cho phép ứng dụng thực hiện các thao tác trên một số trường trong tài liệu nhưng không cho phép thực hiện trên các trường khác.
Có thể có những lúc bạn muốn kiểm soát các thay đổi đối với một tài liệu không ở cấp tài liệu mà ở cấp trường.
Ví dụ: bạn có thể muốn cho phép ứng dụng tạo hoặc thay đổi một tài liệu, nhưng không cho phép ứng dụng chỉnh sửa một số trường nhất định trong tài liệu đó. Hoặc bạn có thể muốn thực thi để mọi tài liệu mà ứng dụng luôn tạo đều chứa một tập hợp trường nhất định. Hướng dẫn này trình bày cách bạn có thể hoàn thành một số nhiệm vụ này bằng Cloud Firestore Security Rules.
Chỉ cho phép quyền truy cập đọc đối với các trường cụ thể
Các lượt đọc trong Cloud Firestore được thực hiện ở cấp tài liệu. Bạn có thể truy xuất toàn bộ tài liệu hoặc không truy xuất được gì. Bạn không thể truy xuất một phần tài liệu. Bạn không thể chỉ sử dụng quy tắc bảo mật để ngăn người dùng đọc các trường cụ thể trong một tài liệu.
Nếu có một số trường nhất định trong một tài liệu mà bạn muốn ẩn khỏi một số người dùng, thì cách tốt nhất là đặt các trường đó vào một tài liệu riêng. Ví dụ: bạn có thể cân nhắc việc tạo một tài liệu trong một bộ sưu tập con private như sau:
/employees/{emp_id}
name: "Alice Hamilton",
department: 461,
start_date: <timestamp>
/employees/{emp_id}/private/finances
salary: 80000,
bonus_mult: 1.25,
perf_review: 4.2
Sau đó, bạn có thể thêm các quy tắc bảo mật có nhiều cấp truy cập cho hai bộ sưu tập. Trong ví dụ này, chúng tôi đang sử dụng các yêu cầu xác thực tuỳ chỉnh
để cho biết rằng chỉ những người dùng có yêu cầu xác thực tuỳ chỉnh role bằng Finance mới có thể
xem thông tin tài chính của nhân viên.
service cloud.firestore {
match /databases/{database}/documents {
// Allow any logged in user to view the public employee data
match /employees/{emp_id} {
allow read: if request.resource.auth != null
// Allow only users with the custom auth claim of "Finance" to view
// the employee's financial data
match /private/finances {
allow read: if request.resource.auth &&
request.resource.auth.token.role == 'Finance'
}
}
}
}
Hạn chế các trường khi tạo tài liệu
Cloud Firestore không có giản đồ, nghĩa là không có hạn chế ở cấp cơ sở dữ liệu đối với các trường mà một tài liệu chứa. Mặc dù tính linh hoạt này có thể giúp quá trình phát triển dễ dàng hơn, nhưng sẽ có những lúc bạn muốn đảm bảo rằng ứng dụng chỉ có thể tạo các tài liệu chứa các trường cụ thể hoặc không chứa các trường khác.
Bạn có thể tạo các quy tắc này bằng cách kiểm tra phương thức keys của đối tượng
request.resource.data. Đây là danh sách tất cả các trường mà ứng dụng đang cố gắng ghi vào tài liệu mới này. Bằng cách kết hợp tập hợp trường này
với các hàm như hasOnly()
hoặc hasAny(),
bạn có thể thêm logic hạn chế các loại tài liệu mà người dùng có thể thêm vào
Cloud Firestore.
Yêu cầu các trường cụ thể trong tài liệu mới
Giả sử bạn muốn đảm bảo rằng tất cả tài liệu được tạo trong bộ sưu tập restaurant đều chứa ít nhất một trường name, location và city. Bạn có thể
thực hiện việc đó bằng cách gọi hasAll()
trên danh sách khoá trong tài liệu mới.
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document contains a name
// location, and city field
match /restaurant/{restId} {
allow create: if request.resource.data.keys().hasAll(['name', 'location', 'city']);
}
}
}
Điều này cho phép tạo nhà hàng bằng các trường khác, nhưng đảm bảo rằng tất cả tài liệu do ứng dụng tạo đều chứa ít nhất 3 trường này.
Cấm các trường cụ thể trong tài liệu mới
Tương tự, bạn có thể ngăn ứng dụng tạo các tài liệu chứa
các trường cụ thể bằng cách sử dụng hasAny()
đối với danh sách các trường bị cấm. Phương thức này đánh giá là đúng nếu một tài liệu chứa bất kỳ trường nào trong số này, vì vậy, bạn có thể muốn phủ định kết quả để cấm một số trường nhất định.
Ví dụ: trong ví dụ sau, ứng dụng không được phép tạo tài liệu chứa trường average_score hoặc rating_count vì các trường này sẽ được thêm bằng lệnh gọi máy chủ vào thời điểm sau.
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document does *not*
// contain an average_score or rating_count field.
match /restaurant/{restId} {
allow create: if (!request.resource.data.keys().hasAny(
['average_score', 'rating_count']));
}
}
}
Tạo danh sách cho phép các trường cho tài liệu mới
Thay vì cấm một số trường nhất định trong tài liệu mới, bạn có thể muốn tạo danh sách chỉ gồm những trường được cho phép rõ ràng trong tài liệu mới. Sau đó
bạn có thể sử dụng hàm hasOnly()
để đảm bảo rằng mọi tài liệu mới được tạo chỉ chứa các trường này
(hoặc một tập hợp con của các trường này) và không chứa trường nào khác.
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document doesn't contain
// any fields besides the ones listed below.
match /restaurant/{restId} {
allow create: if (request.resource.data.keys().hasOnly(
['name', 'location', 'city', 'address', 'hours', 'cuisine']));
}
}
}
Kết hợp các trường bắt buộc và không bắt buộc
Bạn có thể kết hợp các thao tác hasAll và hasOnly trong quy tắc bảo mật để yêu cầu một số trường và cho phép các trường khác. Ví dụ: ví dụ này yêu cầu tất cả tài liệu mới phải chứa các trường name, location và city, đồng thời cho phép các trường address, hours và cuisine (không bắt buộc).
service cloud.firestore {
match /databases/{database}/documents {
// Allow the user to create a document only if that document has a name,
// location, and city field, and optionally address, hours, or cuisine field
match /restaurant/{restId} {
allow create: if (request.resource.data.keys().hasAll(['name', 'location', 'city'])) &&
(request.resource.data.keys().hasOnly(
['name', 'location', 'city', 'address', 'hours', 'cuisine']));
}
}
}
Trong tình huống thực tế, bạn có thể muốn chuyển logic này vào một hàm trợ giúp để tránh trùng lặp mã và dễ dàng kết hợp các trường không bắt buộc và bắt buộc vào một danh sách duy nhất, như sau:
service cloud.firestore {
match /databases/{database}/documents {
function verifyFields(required, optional) {
let allAllowedFields = required.concat(optional);
return request.resource.data.keys().hasAll(required) &&
request.resource.data.keys().hasOnly(allAllowedFields);
}
match /restaurant/{restId} {
allow create: if verifyFields(['name', 'location', 'city'],
['address', 'hours', 'cuisine']);
}
}
}
Hạn chế các trường khi cập nhật
Một biện pháp bảo mật phổ biến là chỉ cho phép ứng dụng chỉnh sửa một số trường và không cho phép chỉnh sửa các trường khác. Bạn không thể chỉ thực hiện việc này bằng cách xem danh sách request.resource.data.keys() được mô tả trong phần trước, vì danh sách này đại diện cho tài liệu hoàn chỉnh như sau khi cập nhật và do đó sẽ bao gồm các trường mà ứng dụng không thay đổi.
Tuy nhiên, nếu sử dụng diff()
hàm, bạn có thể so sánh request.resource.data với
resource.data đối tượng, đối tượng này đại diện cho tài liệu trong cơ sở dữ liệu trước
khi cập nhật. Thao tác này sẽ tạo đối tượng mapDiff, là đối tượng chứa tất cả các thay đổi giữa hai bản đồ khác nhau.
Bằng cách gọi affectedKeys()
phương thức trên mapDiff này, bạn có thể đưa ra một tập hợp các trường đã được thay đổi
trong quá trình chỉnh sửa. Sau đó, bạn có thể sử dụng các hàm như
hasOnly()
hoặc hasAny()
để đảm bảo rằng tập hợp này có (hoặc không) chứa một số mục nhất định.
Ngăn một số trường bị thay đổi
Bằng cách sử dụng phương thức hasAny()
trên tập hợp do affectedKeys()
tạo và sau đó phủ định kết quả, bạn có thể từ chối mọi yêu cầu của ứng dụng cố gắng
thay đổi các trường mà bạn không muốn thay đổi.
Ví dụ: bạn có thể muốn cho phép ứng dụng cập nhật thông tin về một nhà hàng nhưng không thay đổi điểm trung bình hoặc số lượng bài đánh giá của nhà hàng đó.
service cloud.firestore {
match /databases/{database}/documents {
match /restaurant/{restId} {
// Allow the client to update a document only if that document doesn't
// change the average_score or rating_count fields
allow update: if (!request.resource.data.diff(resource.data).affectedKeys()
.hasAny(['average_score', 'rating_count']));
}
}
}
Chỉ cho phép thay đổi một số trường nhất định
Thay vì chỉ định các trường mà bạn không muốn thay đổi, bạn cũng có thể sử dụng hàm
hasOnly()
để chỉ định danh sách các trường mà bạn muốn thay đổi. Điều này thường được coi là an toàn hơn vì theo mặc định, các lượt ghi vào bất kỳ trường tài liệu mới nào đều bị cấm cho đến khi bạn cho phép rõ ràng trong quy tắc bảo mật.
Ví dụ: thay vì không cho phép trường average_score và rating_count, bạn có thể tạo các quy tắc bảo mật chỉ cho phép ứng dụng thay đổi các trường name, location, city, address, hours và cuisine.
service cloud.firestore {
match /databases/{database}/documents {
match /restaurant/{restId} {
// Allow a client to update only these 6 fields in a document
allow update: if (request.resource.data.diff(resource.data).affectedKeys()
.hasOnly(['name', 'location', 'city', 'address', 'hours', 'cuisine']));
}
}
}
Điều này có nghĩa là nếu trong một số lần lặp lại trong tương lai của ứng dụng, tài liệu về nhà hàng bao gồm trường telephone, thì các lần cố gắng chỉnh sửa trường đó sẽ không thành công cho đến khi bạn quay lại và thêm trường đó vào danh sách hasOnly() trong quy tắc bảo mật.
Thực thi các loại trường
Một tác động khác của việc Cloud Firestore không có giản đồ là không có
quy trình thực thi ở cấp cơ sở dữ liệu đối với các loại dữ liệu có thể được lưu trữ trong
các trường cụ thể. Tuy nhiên, bạn có thể thực thi điều này trong quy tắc bảo mật bằng toán tử is.
Ví dụ: quy tắc bảo mật sau đây thực thi rằng trường score của bài đánh giá phải là số nguyên, các trường headline, content và author_name là chuỗi và review_date là dấu thời gian.
service cloud.firestore {
match /databases/{database}/documents {
match /restaurant/{restId} {
// Restaurant rules go here...
match /review/{reviewId} {
allow create: if (request.resource.data.score is int &&
request.resource.data.headline is string &&
request.resource.data.content is string &&
request.resource.data.author_name is string &&
request.resource.data.review_date is timestamp
);
}
}
}
}
Các loại dữ liệu hợp lệ cho toán tử is là bool, bytes, float, int,
list, latlng, number, path, map, string, và timestamp. Toán tử is cũng hỗ trợ các loại dữ liệu constraint, duration, set và map_diff, nhưng vì các loại dữ liệu này do chính ngôn ngữ quy tắc bảo mật tạo và không do ứng dụng tạo, nên bạn hiếm khi sử dụng chúng trong hầu hết các ứng dụng thực tế.
Các loại dữ liệu list và map không hỗ trợ kiểu chung hoặc đối số kiểu.
Nói cách khác, bạn có thể sử dụng quy tắc bảo mật để thực thi rằng một trường nhất định chứa một danh sách hoặc một bản đồ, nhưng bạn không thể thực thi rằng một trường chứa danh sách tất cả số nguyên hoặc tất cả chuỗi.
Tương tự, bạn có thể sử dụng quy tắc bảo mật để thực thi các giá trị kiểu cho các mục cụ thể trong danh sách hoặc bản đồ (tương ứng bằng cách sử dụng ký hiệu dấu ngoặc hoặc tên khoá), nhưng không có lối tắt để thực thi các loại dữ liệu của tất cả thành viên trong bản đồ hoặc danh sách cùng một lúc.
Ví dụ: các quy tắc sau đây đảm bảo rằng trường tags trong một tài liệu
chứa một danh sách và mục đầu tiên là một chuỗi. Quy tắc này cũng đảm bảo rằng trường product chứa một bản đồ, bản đồ này lần lượt chứa tên sản phẩm là một chuỗi và số lượng là một số nguyên.
service cloud.firestore {
match /databases/{database}/documents {
match /orders/{orderId} {
allow create: if request.resource.data.tags is list &&
request.resource.data.tags[0] is string &&
request.resource.data.product is map &&
request.resource.data.product.name is string &&
request.resource.data.product.quantity is int
}
}
}
}
Bạn cần thực thi các loại trường khi tạo và cập nhật tài liệu. Do đó, bạn có thể cân nhắc việc tạo một hàm trợ giúp mà bạn có thể gọi trong cả phần tạo và cập nhật của quy tắc bảo mật.
service cloud.firestore {
match /databases/{database}/documents {
function reviewFieldsAreValidTypes(docData) {
return docData.score is int &&
docData.headline is string &&
docData.content is string &&
docData.author_name is string &&
docData.review_date is timestamp;
}
match /restaurant/{restId} {
// Restaurant rules go here...
match /review/{reviewId} {
allow create: if reviewFieldsAreValidTypes(request.resource.data) &&
// Other rules may go here
allow update: if reviewFieldsAreValidTypes(request.resource.data) &&
// Other rules may go here
}
}
}
}
Thực thi các loại cho các trường không bắt buộc
Bạn cần lưu ý rằng việc gọi request.resource.data.foo trên một
tài liệu mà foo không tồn tại sẽ dẫn đến lỗi và do đó, mọi
quy tắc bảo mật thực hiện lệnh gọi đó sẽ từ chối yêu cầu. Bạn có thể xử lý tình huống này
bằng cách sử dụng get
phương thức trên request.resource.data. Phương thức get cho phép bạn cung cấp đối số mặc định cho trường mà bạn đang truy xuất từ bản đồ nếu trường đó không tồn tại.
Ví dụ: nếu tài liệu đánh giá cũng chứa trường photo_url không bắt buộc và trường tags không bắt buộc mà bạn muốn xác minh là chuỗi và danh sách tương ứng, thì bạn có thể thực hiện việc này bằng cách viết lại hàm reviewFieldsAreValidTypes thành một hàm như sau:
function reviewFieldsAreValidTypes(docData) {
return docData.score is int &&
docData.headline is string &&
docData.content is string &&
docData.author_name is string &&
docData.review_date is timestamp &&
docData.get('photo_url', '') is string &&
docData.get('tags', []) is list;
}
Thao tác này sẽ từ chối các tài liệu có tags, nhưng không phải là danh sách, đồng thời vẫn cho phép các tài liệu không chứa trường tags (hoặc photo_url).
Không bao giờ cho phép ghi một phần
Lưu ý cuối cùng về Cloud Firestore Security Rules là các quy tắc này cho phép ứng dụng thay đổi một tài liệu hoặc từ chối toàn bộ quá trình chỉnh sửa. Bạn không thể tạo các quy tắc bảo mật chấp nhận các lượt ghi vào một số trường trong tài liệu của mình trong khi từ chối các trường khác trong cùng một thao tác.