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à Điều kiện viết cho quy tắc bảo mật để giải thích cách bạn có thể sử dụng Quy tắc bảo mật của Cloud Firestore để tạo 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 thực hiện trên các trường khác.
Có thể đôi khi bạn muốn kiểm soát các thay đổi đối với một tài liệu không phải tại ở cấp tài liệu mà ở cấp trường.
Ví dụ: bạn có thể muốn cho phép khách hàng tạo hoặc thay đổi tài liệu, nhưng không cho phép họ chỉnh sửa các trường nhất định trong tài liệu đó. Hoặc bạn có thể muốn thực thi rằng mọi tài liệu mà ứng dụng luôn tạo sẽ chứa một tập hợp mới. Hướng dẫn này trình bày cách bạn có thể thực hiện một số thao tác trong số này bằng cách sử dụng Các quy tắc bảo mật của Cloud Firestore.
Chỉ cho phép truy cập đọc cho các trường cụ thể
Hoạt động đọc trong Cloud Firestore được thực hiện ở cấp tài liệu. Bạn truy xuất toàn bộ tài liệu hoặc bạn không truy xuất được gì. Không có cách nào để truy xuất m��t ph��n của tài liệu. Bạn không thể chỉ sử dụng các quy tắc bảo mật để ngăn người dùng đọc các trường cụ thể trong tài liệu.
Nếu có một số trường nhất định trong tài liệu mà bạn muốn ẩn
một số người dùng, cách tốt nhất là đưa họ vào một tài liệu riêng. Cho
Ví dụ: bạn có thể cân nhắc việc tạo tài liệu trong một bộ sưu tập con private
như thế:
/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 quy tắc bảo mật có các cấp truy cập khác nhau cho
hai bộ sưu tập. Trong ví dụ này, chúng tôi sử dụng thông báo xác nhận quyền sở hữu tuỳ chỉnh
để cho biết rằng chỉ những người dùng có xác nhận quyền sở hữu tuỳ chỉnh role
bằng Finance
mới có thể
xem thông tin tài chính của một 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 trong quá trình tạo tài liệu
Cloud Firestore không có giản đồ, nghĩa là không có hạn chế về cấp cơ sở dữ liệu đối với những trường trong tài liệu. Trong khi sự linh hoạt có thể giúp việc phát triển trở nên dễ dàng hơn, sẽ có lúc bạn muốn để đảm bảo rằng khách hàng chỉ có thể tạo tài liệu có 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
request.resource.data
. Đây là danh sách tất cả các trường mà ứng dụng
đang cố viết trong tài liệu mới này. Bằng cách kết hợp nhóm trường này
bằng các hàm như hasOnly()
hoặc hasAny()
,
bạn có thể thêm vào 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 restaurant
tập hợp chứa ít nhất một trường name
, location
và city
. Bạn có thể
làm việc đó bằng cách gọi hasAll()
trên danh sách các 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']);
}
}
}
Thao tác này cũng cho phép tạo nhà hàng bằng các trường khác, nhưng vẫn đảm bảo tất cả tài liệu do ứng dụng tạo đều chứa ít nhất ba trường này.
Đặt giá thầu cho những trường cụ thể trong tài liệu mới
Tương tự, bạn có thể ngăn không cho khách hàng tạo tài liệu có chứa
các trường cụ thể bằng cách sử dụng hàm hasAny()
dựa vào danh sách các trường bị cấm. Phương thức này được đánh giá là đúng nếu một
có chứa bất kỳ trường nào trong số này, nên bạn có thể bỏ qua
để cấm một số trường nhất định.
Ví dụ: trong ví dụ sau, ứng dụng khách không được phép tạo một
tài liệu chứa trường average_score
hoặc rating_count
vì
sẽ được thêm bằng lệnh gọi máy chủ 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 chứa các trường đối với tài liệu mới
Thay vì đặt giá thầu cho một số trường nhất định trong các tài liệu mới, có thể bạn nên tạo
danh sách chỉ có các trường được cho phép rõ ràng trong tài liệu mới. Sau đó
bạn có thể dùng 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 nhóm nhỏ các trường này) và không có 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
với nhau trong phần bảo mật
quy tắc để 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 có chứa name
, location
và city
và tuỳ ý cho phép các trường address
, hours
và cuisine
.
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 nên chuyển logic này vào một hàm trợ giúp để tránh lặp lại mã và để dễ dàng kết hợp các thuộc tính các trường bắt buộc vào một danh sách, 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 khách hàng chỉnh sửa một số trường mà không cho phép
khác. Bạn không thể đạt được điều này nếu chỉ xem xét
Danh sách request.resource.data.keys()
được mô tả trong phần trước, vì đây là
danh sách trình bày toàn bộ tài liệu giống 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 bạn định sử dụng diff()
bạn có thể so sánh request.resource.data
với
Đối tượng resource.data
đại diện cho tài liệu trong cơ sở dữ liệu trước
bản cập nhật. Thao tác này sẽ tạo một mapDiff
là một đối tượng chứa tất cả các thay đổi giữa hai
Maps.
Bằng cách gọi affectedKeys()
trên mapDiff này, bạn có thể tạo một tập hợp các trường đã được thay đổi
trong nội dung 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.
Không cho phép thay đổi một số trường
Bằng cách sử dụng hasAny()
trên tập hợp được tạo bởi affectedKeys()
và sau đó phủ định kết quả, bạn có thể từ chối mọi yêu cầu của khách hà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 khách hàng cập nhật thông tin về nhà hàng nhưng không thay đổi điểm trung bình hoặc số lượng bài đánh giá.
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 những trường mà bạn không muốn thay đổi, bạn cũng có thể sử dụng
hasOnly()
để chỉ định danh sách các trường mà bạn muốn thay đổi. Thông thường,
được coi là an toàn hơn vì việc ghi vào mọi trường tài liệu mới đều
không được phép theo mặc định cho đến khi bạn cho phép chúng một cách rõ ràng trong quy tắc bảo mật của mình.
Ví dụ: thay vì không cho phép average_score
và rating_count
, bạn có thể tạo quy tắc bảo mật cho phép khách hàng chỉ 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 ứng dụng của bạn, tài liệu của nhà hàng
thêm trường telephone
, thì bạn sẽ không chỉnh sửa được trường đó
cho đến khi bạn quay lại và thêm trường đó vào danh sách hasOnly()
trong mục bảo mật
quy tắc.
Thực thi các loại trường
Một hiệu ứng khác của việc Cloud Firestore không có giản đồ là không có
việc thực thi ở cấp cơ sở dữ liệu đối với loại dữ liệu có thể được lưu trữ
các trường cụ thể. Tuy nhiên, bạn có thể thực thi điều này trong các 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 việc score
của bài đánh giá
trường phải là một số nguyên. Các trường headline
, content
và author_name
là các 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
);
}
}
}
}
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
. is
toán tử cũng hỗ trợ dữ liệu constraint
, duration
, set
và map_diff
nhưng vì chúng được tạo bởi chính ngôn ngữ của quy tắc bảo mật và
không phải do khách hàng tạo ra, bạn hiếm khi sử dụng chúng trong
.
Các kiểu dữ liệu list
và map
không hỗ trợ các đối số chung hoặc đối số kiểu.
Nói cách khác, bạn có thể sử dụng các quy tắc bảo mật để thực thi 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 việc một trường đó chứa danh sách
của 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ị loại cho trong một danh sách hoặc một bản đồ (sử dụng ký hiệu phanh hoặc tên khoá tương ứng), nhưng không có lối tắt nào để thực thi các loại dữ liệu của tất cả cá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 nhập đầu tiên là một chuỗi. Quy trình này cũng đảm bảo rằng
trường product
chứa một bản đồ 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 phải thực thi các loại trường khi tạo và cập nhật tài liệu. Do đó, bạn nên cân nhắc tạo một chức năng trợ giúp mà bạn có thể 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
Xin lưu ý rằng việc gọi request.resource.data.foo
trên
tài liệu trong đó foo
không tồn tại sẽ dẫn đến lỗi và do đó bất kỳ
quy tắc bảo mật mà việc thực hiện lệnh gọi đó sẽ từ chối yêu cầu. Bạn có thể xử lý
bằng cách sử dụng hàm get
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 bạn đang truy xuất từ bản đồ nếu trường đó
không tồn tại.
Ví dụ: Nếu các tài liệu đánh giá cũng chứa trường photo_url
(không bắt buộc)
và một trường tags
không bắt buộc mà bạn muốn xác minh là các chuỗi và danh sách
bạn có thể thực hiện điều này bằng cách viết lại
reviewFieldsAreValidTypes
thành một hàm có dạng 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 những tài liệu có tags
tồn tại, nhưng không phải là danh sách trong khi vẫn
cho phép tài liệu không chứa trường tags
(hoặc photo_url
).
Không bao giờ được phép ghi một phần
Một lưu ý cuối cùng về Quy tắc bảo mật của Cloud Firestore là quy tắc này cho phép khách hàng thực hiện thay đổi đối với tài liệu hoặc họ từ chối toàn bộ chỉnh sửa. Bạn không thể tạo các quy tắc bảo mật chấp nhận việc ghi vào một số trường trong trong khi từ chối các tài liệu khác trong cùng thao tác.