解决方法:自定义函数
您不能对内部字段使用匹配,而是必须使用 rules.List、rules.Map 和 rules.Set 对象。
请务必注意,规则是静态的,无法遍历列表(例如使用 forEach、map 等)。这可以通过使用someList.size() <= position 在执行元素比较之前检查列表是否足够长来克服。不幸的是,这必须是硬编码的,如下所示。
这些规则的一个目标是它们应该能够与同一文档上的其他规则结合使用。即“汽车”地图应该受到限制,但您仍然应该能够更新“名称”和“地址”字段。
在本节中,变量将非常冗长以便于理解(例如包括类型信息)。重命名它们以适合您的风格。
免责声明:虽然这第一组规则有效,但它很笨拙且过于具体 - 不建议用于生产环境。
service cloud.firestore {
match /databases/{database}/documents {
// assert no changes or that only "salesComment" was changed
function isCarEditAllowed(afterCarMap, beforeCarMap) {
return afterCarMap.diff(beforeCarMap).affectedKeys().size() == 0
|| afterCarMap.diff(beforeCarMap).affectedKeys().hasOnly(["salesComment"]);
}
// assert that if this car exists that it has allowed changes
function isCarAtPosValid(afterCarsList, beforeCarsList, position) {
return afterCarsList.size() <= position // returns true when car doesn't exist
|| isCarEditAllowed(afterCarsList[position], beforeCarsList[position])
}
function areCarEditsAllowed(afterDataMap, beforeDataMap) {
return afterDataMap.get("cars", false) != false // cars field exists after
&& beforeDataMap.get("cars", false) != false // cars field exists before
&& afterDataMap.cars.size() == beforeDataMap.cars.size() // cars field is same length
&& isCarAtPosValid(afterDataMap.cars, beforeDataMap.cars, 0)
&& isCarAtPosValid(afterDataMap.cars, beforeDataMap.cars, 1)
&& isCarAtPosValid(afterDataMap.cars, beforeDataMap.cars, 2)
&& isCarAtPosValid(afterDataMap.cars, beforeDataMap.cars, 3)
&& isCarAtPosValid(afterDataMap.cars, beforeDataMap.cars, 4)
}
match /carUsers/{userId} {
allow read: if request.auth.uid == userId;
allow write: if request.auth.uid == userId
&& areCarEditsAllowed(request.resource.data, resource.data)
}
}
}
既然上述规则有效,可以通过将步骤抽象为一组可重用的自定义函数来改进它们。
service cloud.firestore {
match /databases/{database}/documents {
/* Custom Functions: Restrict map changes */
function mapHasAllowedChanges(afterMap, beforeMap, setOfWhitelistedKeys) {
return afterMap.diff(beforeMap).affectedKeys().size() == 0 // no changes
|| setOfWhitelistedKeys.hasAll(afterMap.diff(beforeMap).affectedKeys()) // only named keys may be changed
}
function mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, position) {
return afterList.size() <= position // returns true when element doesn't exist
|| mapHasAllowedChanges(afterList[position], beforeList[position], setOfWhitelistedKeys)
}
function listOfMapsHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys) {
return mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, 0)
&& mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, 1)
&& mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, 2)
&& mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, 3)
&& mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, 4)
}
function largeListOfMapsHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys) {
return mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, 0)
&& mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, 1)
&& mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, 2)
&& mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, 3)
&& mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, 4)
&& mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, 5)
&& mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, 6)
&& mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, 7)
&& mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, 8)
&& mapInListHasAllowedChanges(afterList, beforeList, setOfWhitelistedKeys, 9)
}
function namedListWithSameSizeExists(listPath) {
return request.resource.data.get(listPath, false) != false
&& resource.data.get(listPath, false) != false
&& request.resource.data.get(listPath, {}).size() == resource.data.get(listPath, {}).size()
}
function namedListOfMapsWithSameSizeExistsWithAllowedChanges(listPath, setOfWhitelistedKeys) {
return namedListWithSameSizeExists(listPath)
&& listOfMapsHasAllowedChanges(request.resource.data.get(listPath, {}), resource.data.get(listPath, {}), setOfWhitelistedKeys)
}
/* Rules */
match /carUsers/{userId} {
allow read: if request.auth.uid == userId;
allow write: if request.auth.uid == userId
&& namedListOfMapsWithSameSizeExistsWithAllowedChanges("cars", ["salesComment"].toSet())
}
}
}
注意:上述规则并不断言白名单键未被删除。为确保更改后列出的键存在,您需要将 mapHasAllowedChanges 函数替换为:
function mapHasAllowedChanges(afterMap, beforeMap, setOfWhitelistedKeys) {
return afterMap.diff(beforeMap).affectedKeys().size() == 0 // no changes
|| (setOfWhitelistedKeys.hasAll(afterMap.diff(beforeMap).affectedKeys()) // only named keys may be changed
&& afterMap.keys().toSet().hasAll(setOfWhitelistedKeys)) // all named keys must exist
}
推荐的方法:移动到汽车到子集合
上述规则相当复杂,如果您将汽车移至自己的收藏并使用rules.Map#diff,则可以简化。
只有在用户拥有该汽车文档并且仅修改salesComment 键(修改 = 添加/更改/删除)时,以下代码才允许进行写入。
service cloud.firestore {
match /databases/{database}/documents {
match /user/{userId} {
allow read, write: if request.auth.uid == userId;
match /cars/{carId} {
allow read: if request.auth.uid == userId; // Firestore rules don't cascade to subcollections, so this is also needed
allow write: if request.auth.uid == userId
&& request.resource.data.diff(resource.data).affectedKeys().hasOnly(["salesComment"]);
}
}
}
}
如果您要求salesComment 在写入后必须存在(允许添加/更改 - 但不能删除),您还可以使用k in x 确保它仍然存在。
service cloud.firestore {
match /databases/{database}/documents {
match /user/{userId} {
allow read, write: if request.auth.uid == userId;
match /cars/{carId} {
allow read: if request.auth.uid == userId; // Firestore rules don't cascade to subcollections, so this is also needed
allow write: if request.auth.uid == userId
&& "salesComment" in request.resource.data
&& request.resource.data.diff(resource.data).affectedKeys().hasOnly(["salesComment"]);
}
}
}
}