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

Trang này xây dựng 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 khách hàng thực hiện các thao tác trên một số trường trong tài liệu chứ không phải 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 tài liệu không phả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 một số trường nhất định trong tài liệu đó. Hoặc bạn có thể muốn thực thi rằng bất kỳ tài liệu nào mà khách hàng luôn tạo đều chứa một tập hợp các trường nhất định. Hướng dẫn này trình bày cách bạn có thể hoàn thành một số tác vụ này bằng cách sử dụng Quy tắc bảo mật của Cloud Firestore.

Chỉ cho phép truy cập đọc đối với các trường cụ thể

Việc đọc trong Cloud Firestore được thực hiện ở cấp độ tài liệu. Bạn có thể truy xuất toàn bộ tài liệu hoặc không truy xuất được gì. Không có cách nào để lấy lại một phần tài liệu. 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 khỏi một số người dùng, cách tốt nhất là đặt chúng vào một tài liệu riêng. Ví dụ: bạn có thể cân nhắc việc tạo một tài liệu trong một bộ sưu tập con private như sau:

/nhân viên/{emp_id}

  name: "Alice Hamilton",
  department: 461,
  start_date: <timestamp>

/nhân viên/{emp_id}/private/tài chính

    salary: 80000,
    bonus_mult: 1.25,
    perf_review: 4.2

Sau đó, bạn có thể thêm các quy tắc bảo mật 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 đang sử dụng xác nhận quyền sở hữu tùy chỉnh để nói rằng chỉ những người dùng có role xác thực tùy chỉnh bằng Finance mới có thể xem thông tin tài chính của 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 khi tạo tài liệu

Cloud Firestore không có sơ đồ, nghĩa là không có hạn chế nào ở cấp cơ sở dữ liệu đối với các trường mà tài liệu chứa. Mặc dù tính linh hoạt này có thể giúp việc phát triển dễ dàng hơn nhưng 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 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 đối tượng request.resource.data . Đây là danh sách tất cả các trường mà khách hàng đang cố gắng viết trong tài liệu mới này. Bằng cách kết hợp tập hợp trường này với các hàm như hasOnly() hoặc hasAny() , bạn có thể thêm 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 bộ sưu tập restaurant đều chứa ít nhất một trường name , locationcity . Bạn có thể làm điều đó bằng cách gọi hasAll() trên danh sách các khóa 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']);
    }
  }
}

Điều này cũng cho phép tạo nhà hàng bằng các trường khác, nhưng nó đảm bảo rằng tất cả tài liệu do khách hàng tạo đều chứa ít nhất ba trường này.

Cấm các trường cụ thể trong tài liệu mới

Tương tự, bạn có thể ngăn khách hàng tạo tài liệu chứa các trường cụ thể bằng cách sử dụng hasAny() đối với danh sách các trường bị cấm. Phương pháp này đánh giá là đúng nếu tài liệu chứa bất kỳ trường nào trong số này, vì vậy bạn có thể muốn phủ định kết quả để cấm một số trường nhất định.

Ví dụ: trong ví dụ sau, máy khách không được phép tạo tài liệu chứa trường average_score hoặc rating_count vì những trường này sẽ được thêm vào bởi lệnh gọi máy chủ sau này.

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 các trường cho phép cho tài liệu mới

Thay vì cấm một số trường nhất định trong tài liệu mới, bạn có thể muốn tạo danh sách chỉ những trường được cho phép rõ ràng trong tài liệu mới. Sau đó, bạn có thể sử 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 tập hợp con của các trường này) và không chứa 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à tùy chọn

Bạn có thể kết hợp các thao tác hasAllhasOnly với nhau trong các quy tắc bảo mật của mình để 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 chứa các trường name , locationcity và tùy ý 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 có thể muốn chuyển logic này vào một hàm trợ giúp để tránh trùng lặp mã và để dễ dàng kết hợp các trường tùy chọn và 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 phương 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 chứ không phải các trường khác. Bạn không thể thực hiện việc này chỉ bằng cách xem danh sách request.resource.data.keys() được mô tả trong phần trước, vì danh sách này thể hiện tài liệu hoàn chỉnh giống như nó sẽ quản lý bản cập nhật và do đó sẽ bao gồm các trường mà máy khách không có thay đổi.

Tuy nhiên, nếu sử dụng hàm 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 khi cập nhật. Điều này tạo ra một đối tượng mapDiff , là một đối tượng chứa tất cả những thay đổi giữa hai bản đồ khác nhau.

Bằng cách gọi phương thức affectedKeys() trên mapDiff này, bạn có thể đưa ra một tập hợp các trường đã được thay đổi trong một bản 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 bộ này có (hoặc không) chứa một số mục nhất định.

Ngăn chặn một số trường bị thay đổi

Bằng cách sử dụng phương thức hasAny() trên tập hợp được tạo bởi bị affectedKeys() rồi phủ nhận 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ề một nhà hàng nhưng không thay đổi điểm trung bình hoặc số lượng đánh giá của họ.

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 các trường mà bạn không muốn thay đổi, bạn cũng có thể sử dụng hàm hasOnly() để chỉ định danh sách các trường mà bạn muốn thay đổi. Điều này thường được coi là an toàn hơn vì việc ghi vào bất kỳ trường tài liệu mới nào đề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 các quy tắc bảo mật của mình.

Ví dụ: thay vì không cho phép trường average_scorerating_count , bạn có thể tạo các 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 trong tương lai, tài liệu nhà hàng bao gồm trường telephone thì các nỗ lực chỉnh sửa trường đó sẽ không thành công cho đến khi bạn quay lại và thêm trường đó vào danh sách hasOnly() trong quy tắc bảo mật của mình.

Thực thi các loại trường

Một tác động khác của Cloud Firestore là không có sơ đồ là không có biện pháp thực thi ở cấp cơ sở dữ liệu đối với loại dữ liệu nào có thể được lưu trữ trong các trường cụ thể. Tuy nhiên, đây là điều bạn có thể thực thi trong các quy tắc bảo mật với toán tử is .

Ví dụ: quy tắc bảo mật sau đây yêu cầu trường điểm của bài đánh score phải là số nguyên, các trường headline , contentauthor_name là 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
        );
      }
    }
  }
}

Các kiểu dữ liệu hợp lệ cho toán tử isbool , bytes , float , int , list , latlng , number , path , map , stringtimestamp . Toán tử is cũng hỗ trợ các kiểu dữ liệu constraint , duration , setmap_diff , nhưng vì chúng được tạo bởi chính ngôn ngữ quy tắc bảo mật chứ không phải do máy khách tạo ra nên bạn hiếm khi sử dụng chúng trong hầu hết các ứng dụng thực tế.

Các kiểu dữ liệu listmap không hỗ trợ các đối số chung hoặc kiểu. Nói cách khác, bạn có thể sử dụng các quy tắc bảo mật để buộc một trường nhất định chứa danh sách hoặc bản đồ, nhưng bạn không thể bắt buộc rằng trường đó chứa danh sách tất cả các số nguyên hoặc tất cả các chuỗi.

Tương tự, bạn có thể sử dụng các quy tắc bảo mật để thực thi các giá trị loại cho các mục cụ thể trong danh sách hoặc bản đồ (sử dụng ký hiệu phanh hoặc tên khóa 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 tại một lần.

Ví dụ: các quy tắc sau đây đảm bảo rằng trường tags trong tài liệu chứa danh sách và mục nhập đầu tiên là một chuỗi. Nó 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à 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
      }
    }
  }
}

Các loại trường cần phải được thực thi khi tạo và cập nhật tài liệu. Do đó, bạn có thể cân nhắc việc tạo một hàm trợ giúp mà bạn có thể gọi trong cả phần tạo và cập nhật các quy tắc bảo mật của mình.

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 tùy chọn

Điều quan trọng cần nhớ là việc gọi request.resource.data.foo trên tài liệu mà 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 nào thực hiện lệnh gọi đó sẽ từ chối yêu cầu. Bạn có thể xử lý tình huống này bằng cách sử dụng phương thức 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 tài liệu đánh giá cũng chứa trường photo_url tùy chọn và trường tags chọn mà bạn muốn xác minh lần lượt là chuỗi và danh sách, thì bạn có thể thực hiện việc này bằng cách viết lại hàm reviewFieldsAreValidTypes thành nội dung 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;
  }

Điều này từ chối các 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 các tài liệu không chứa trường tags (hoặc photo_url ).

Viết một phần không bao giờ được phép

Một lưu ý cuối cùng về Quy tắc bảo mật của Cloud Firestore là chúng cho phép khách hàng thực hiện thay đổi đối với tài liệu hoặc 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 ghi vào một số trường trong tài liệu của mình trong khi từ chối các quy tắc khác trong cùng một thao tác.