Правило безопасности Firestore соответствует пути к полю в словаре - PullRequest
2 голосов
/ 01 апреля 2020

Допустим, у меня есть объект Firestore ниже пользовательского объекта с полями name, address и cars (обратите внимание, что только пользователь является коллекцией).

user {
      name: "John Smith"
      address: '123 Firebase Road, Firestore CA, 10000"
      cars: {
               asfdfsd811r9UAdfasdf1: {
                       name: "Ford Explorer"
                       carSold: false,
                       salesComment: "This is the best SUV in the world"
               },
               12342342ADSfas! :{
                        name:" Testla Modal X"
                        carPrice:false,
                       salesComment: "This is the best electric car in the world"
               }
      }
}

Я хочу установить правило безопасности для принудительного применения клиентских библиотек могу только редактировать salesComment, но ничего больше в этом объекте пользовательской коллекции, как мне это сделать? Я установил соответствующий путь, как показано ниже, но он не работает :(. Можете ли вы установить путь для сопоставления словаря полей, как в данном случае для автомобилей? Шаблоны match и variableId применяются только только к коллекциям.

service cloud.firestore {
  match /databases/{database}/documents {
    match /user/{userId} {
              match /cars/{carId}/salesComment {
                    allow write: if request.auth.uid == userId;
              }
               allow read: if request.auth.uid == userId;
        }

1 Ответ

1 голос
/ 12 апреля 2020

Обходной подход: пользовательские функции

Нельзя использовать сопоставление с внутренними полями, вместо этого необходимо использовать 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"]);
      }
    }
  }
}
...