Kiểm soát quyền truy cập vào các trường cụ thể

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Đ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, locationcity. 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 hasAllhasOnly 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, locationcity và tuỳ ý cho phép các trường address, hourscuisine.

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_scorerating_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, hourscuisine.

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, contentauthor_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ử isbool, bytes, float, int, list, latlng, number, path, map, stringtimestamp. is toán tử cũng hỗ trợ dữ liệu constraint, duration, setmap_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 listmap 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.