Обходной подход: пользовательские функции
Нельзя использовать сопоставление с внутренними полями, вместо этого необходимо использовать rules.List
, rules.Map
и rules.Set
объектов.
Важно отметить, что правила имеют статус c и не имеют возможности перебирать списки (например, с помощью forEach
, map
и др. c). Этого можно избежать, используя someList.size() <= position
, чтобы проверить, достаточно ли длинен список перед выполнением сравнения элементов. К сожалению, это должно быть жестко запрограммировано, как вы увидите ниже.
Одна из целей этих правил состоит в том, чтобы их можно было комбинировать с другими правилами в том же документе. то есть карта "cars" должна быть ограничена, но вы все равно должны иметь возможность обновлять поля "name" и "address".
Во всем этом разделе переменные будут довольно многословными для простоты понимание (например, включение информации о типе). Переименуйте их, чтобы они соответствовали вашему стилю.
Отказ от ответственности: хотя этот первый набор правил работает, он неопрятен и слишком специфичен c - не рекомендуется для производственного использования.
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"]);
}
}
}
}