Kontrolowanie dostępu do określonych pól

Na tej stronie omawiamy pojęcia z zakresu Tworzenie struktury reguł zabezpieczeń Pisanie warunków reguł zabezpieczeń wyjaśniających, jak możesz używać reguł zabezpieczeń Cloud Firestore do tworzenia reguł, które pozwolą klientom wykonywać tylko na niektórych polach w dokumencie.

Może się zdarzyć, że konieczne będzie kontrolowanie zmian w dokumencie, który nie jest w domenie na poziomie dokumentu, ale też pola.

Możesz na przykład zezwolić klientowi na tworzenie lub modyfikowanie dokumentu, ale uniemożliwić im edytowanie niektórych pól w tym dokumencie. Możesz też wymuszać, aby każdy dokument tworzony przez klienta zawsze zawierał określony zestaw . Z tego przewodnika dowiesz się, jak wykonać niektóre z tych zadań przy użyciu Reguły zabezpieczeń Cloud Firestore.

Zezwalanie na dostęp tylko do odczytu w przypadku określonych pól

Odczyty w Cloud Firestore są wykonywane na poziomie dokumentu. Ty albo możesz pobrać cały dokument lub niczego nie pobrać. Nie można odzyskać tylko części dokumentu. Użycie samych reguł zabezpieczeń do uniemożliwiać użytkownikom odczytywanie określonych pól w dokumencie.

Jeśli w dokumencie są pola, które nie mają być ukryte dla niektórych użytkowników, najlepszym sposobem będzie umieszczenie ich w oddzielnym dokumencie. Dla: możesz utworzyć dokument w kolekcji podrzędnej private np.:

/pracownicy/{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

Następnie możesz dodać reguły zabezpieczeń z różnymi poziomami dostępu dwie kolekcje. W tym przykładzie używamy niestandardowych deklaracji uwierzytelniania aby to zrobić, tylko użytkownicy z niestandardowym żądaniem uwierzytelniania role równym Finance mogą wyświetlanie informacji finansowych pracownika.

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'
      }
    }
  }
}

Ograniczanie pól podczas tworzenia dokumentu

Cloud Firestore działa bez schematów, co oznacza, że nie ma ograniczeń na poziomie bazy danych pól, które zawiera dokument. Choć w tym roku elastyczność może ułatwić programowanie, ale i tak nadejdzie moment, dzięki któremu klienci mogą tworzyć tylko dokumenty zawierające określone pola, lub nie zawierają innych pól.

Aby utworzyć te reguły, zapoznaj się z metodą keys funkcji request.resource.data obiektu. To jest lista wszystkich pól, które klient Użytkownik próbuje pisać w tym nowym dokumencie. Łącząc ten zestaw pól za pomocą funkcji takich jak hasOnly() lub hasAny(), możesz dodać logikę ograniczającą typy dokumentów, do których użytkownik może dodawać treści. i Cloud Firestore.

Wymaganie stosowania określonych pól w nowych dokumentach

Załóżmy, że chcesz się upewnić, że wszystkie dokumenty utworzone w restaurant zawierało co najmniej pole name, location i city. Możesz Aby to zrobić, zadzwoń pod numer hasAll() na liście kluczy w nowym dokumencie.

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']);
    }
  }
}

Pozwala to również tworzyć restauracje z innymi polami, ale zapewnia aby wszystkie dokumenty utworzone przez klienta zawierały co najmniej te 3 pola.

Blokowanie określonych pól w nowych dokumentach

Można też uniemożliwić klientom tworzenie dokumentów zawierających określonych pól za pomocą funkcji hasAny() z listą zabronionych pól. Ta metoda zwraca wartość prawda, jeśli dokument zawiera dowolne z tych pól, więc prawdopodobnie chcesz wykluczyć w celu uniemożliwienia wyświetlania określonych pól.

W przykładzie poniżej klienty nie mogą tworzyć dokument, który zawiera pole average_score lub rating_count, ponieważ te wartości są dodawane w późniejszym czasie przez wywołanie serwera.

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']));
    }
  }
}

Tworzenie listy dozwolonych pól dla nowych dokumentów

Zamiast blokować niektóre pola w nowych dokumentach, możesz utworzyć listę tylko tych pól, które są wyraźnie dozwolone w nowych dokumentach. Potem możesz użyć funkcji hasOnly() , aby upewnić się, że każdy nowo utworzony dokument będzie zawierał tylko te pola, (lub podzbiór tych pól), a nie żadne inne.

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']));
    }
  }
}

Łączenie wymaganych i opcjonalnych pól

Możesz połączyć operacje hasAll i hasOnly ze względów bezpieczeństwa wymaga tylko niektórych pól i zezwala na korzystanie z innych. Na przykład w tym przykładzie wymaga, aby wszystkie nowe dokumenty zawierały name, location oraz city i opcjonalnie zezwala na pola address, hours i 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']));
    }
  }
}

W rzeczywistości można przenieść tę logikę do funkcji pomocniczej aby uniknąć duplikowania kodu i łatwiej połączyć opcjonalne i na jedną listę wymaganych pól, na przykład:

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']);
    }
  }
}

Ograniczam pola przy aktualizacji

Typową praktyką w zakresie bezpieczeństwa jest zezwalanie klientom na edytowanie tylko niektórych pól, a nie reszta. Nie możesz tego osiągnąć, patrząc tylko na request.resource.data.keys() listy omówionej w poprzedniej sekcji, ponieważ pokazuje cały dokument tak, jakby wyglądałby po aktualizacji, oraz zawiera więc pola, których klient nie zmienił.

Jeśli jednak używasz strony diff() możesz porównać request.resource.data z Obiekt resource.data, który reprezentuje dokument w bazie danych przed i zainstalować aktualizację. Spowoduje to utworzenie mapDiff. czyli obiekt zawierający wszystkie zmiany między dwoma różnymi map.

Wywołując metodę affectedKeys() na tym obiekcie mapDiff, możesz wymyślić zestaw pól, które zostały zmienione w zmianie. Możesz potem użyć funkcji takich jak hasOnly() lub hasAny() , by upewnić się, że zestaw zawiera określone elementy.

Uniemożliwienie zmiany niektórych pól

Za pomocą funkcji hasAny() w zbiorze wygenerowanym przez: affectedKeys() a następnie negując ten wynik, możesz odrzucić wszystkie żądania klienta, które próbują zmienić pola, których nie chcesz zmieniać.

Możesz na przykład zezwolić klientom na aktualizowanie informacji o bez zmiany średniej oceny ani liczby opinii.

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']));
    }
  }
}

Możliwość zmiany tylko niektórych pól

Zamiast określać pola, których nie chcesz zmieniać, możesz również użyć funkcji hasOnly() aby określić listę pól, które chcesz zmienić. Jest to zasadniczo uważane za bezpieczniejsze, ponieważ zapisy w nowych polach dokumentu są domyślnie niedozwolone, dopóki nie zezwolisz na nie w swoich regułach zabezpieczeń.

Na przykład zamiast blokować zasady average_score i rating_count możesz utworzyć reguły zabezpieczeń, które umożliwią klientom tylko zmianę Pola name, location, city, address, hours i 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']));
    }
  }
}

Oznacza to, że jeśli w przyszłej iteracji aplikacji dokumenty w restauracji uwzględnić pole telephone, próby jego edytowania zakończą się niepowodzeniem dopóki nie wrócisz i nie dodasz tego pola do listy hasOnly() w ustawieniach zabezpieczeń reguł.

Wymuszanie typów pól

Innym efektem braku schematu w Cloud Firestore jest to, że nie ma egzekwowania zasad na poziomie bazy danych w odniesieniu do typów danych, które mogą być przechowywane określonych pól. Możesz to egzekwować w regułach zabezpieczeń, za pomocą operatora is.

Na przykład ta reguła zabezpieczeń wymusza, aby atrybut score opinii pole musi być liczbą całkowitą, a pola headline, content i author_name są ciągami tekstowymi, a review_date to sygnatura czasowa.

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
        );
      }
    }
  }
}

Prawidłowe typy danych operatora is to bool, bytes, float, int, list, latlng, number, path, map, string i timestamp. is obsługuje również dane constraint, duration, set i map_diff ale ponieważ są one generowane przez język reguł zabezpieczeń, nie są generowane przez klientów, dlatego rzadko używają ich w najbardziej praktycznym kontekście. aplikacji.

Typy danych list i map nie obsługują wyrażeń ogólnych ani argumentów typu. Innymi słowy, możesz użyć reguł zabezpieczeń, aby wymusić stosowanie określonego pola zawiera listę lub mapę, ale nie możesz wymusić, aby pole zawierało listę. wszystkich liczb całkowitych lub wszystkich ciągów.

Podobnie możesz używać reguł zabezpieczeń do egzekwowania wartości typów w przypadku określonych wpisy na liście lub mapie (przy użyciu odpowiednio notacji Brakets lub nazw kluczy), ale nie ma skrótu, które pozwalałoby wymusić stosowanie typów danych przez wszystkich użytkowników na mapie. lub całą listę naraz.

Na przykład poniższe reguły powodują, że pole tags w dokumencie zawiera listę, a pierwszy wpis jest ciągiem. Zapewnia to też pole product zawiera mapę, która z kolei zawiera nazwę produktu, która jest ciągiem i liczbą całkowitą.

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
      }
    }
  }
}

Typy pól muszą być wymuszane podczas tworzenia i aktualizowania dokumentu. Dlatego warto rozważyć utworzenie funkcji pomocniczej, w sekcjach tworzenia i aktualizowania reguł zabezpieczeń.

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
      }
    }
  }
}

Typy wymuszania w przypadku pól opcjonalnych

Pamiętaj, że dzwoniąc pod numer request.resource.data.foo dokument, w którym nie ma elementu foo, powoduje wystąpienie błędu, i w związku z tym reguła bezpieczeństwa wykonująca to wywołanie spowoduje odrzucenie żądania. Dasz radę przy użyciu funkcji get request.resource.data. Metoda get umożliwia podanie parametru domyślny argument dla pola, które pobierasz z mapy, jeśli to pole nie istnieje.

Jeśli na przykład dokumenty do weryfikacji zawierają też opcjonalne pole photo_url i opcjonalne pole tags, które chcesz zweryfikować, są ciągami znaków i listami. możesz to zrobić, przepisując kod reviewFieldsAreValidTypes na przykład do tego:

  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;
  }

Spowoduje to odrzucenie dokumentów, w których przypadku tags istnieje, ale nie jest listą, zezwalanie na dokumenty, które nie zawierają pola tags (lub photo_url).

Częściowe zapisy nigdy nie są dozwolone

Ostatnia uwaga na temat reguł zabezpieczeń Cloud Firestore: to, że zezwalają na klient chce wprowadzić zmianę w dokumencie, albo odrzuca całą zmianę. Nie możesz tworzyć reguł zabezpieczeń, które akceptują zapisy w niektórych polach w dokument, jednocześnie odrzucając inne w tej samej operacji.