หน้านี้ต่อยอดจากแนวคิดใน การกำหนดโครงสร้างกฎการรักษาความปลอดภัยและ การเขียนเงื่อนไขสำหรับกฎการรักษาความปลอดภัยเพื่ออธิบายวิธี คุณจะใช้กฎความปลอดภัยของ Cloud Firestore เพื่อสร้างกฎที่ทำให้ไคลเอ็นต์ดำเนินการต่อไปนี้ได้ การดำเนินการในบางฟิลด์ในเอกสาร
บางครั้งคุณอาจต้องการควบคุมการเปลี่ยนแปลงเอกสารที่ไม่มี ที่ระดับเอกสาร แต่ที่ระดับฟิลด์
ตัวอย่างเช่น คุณอาจต้องการอนุญาตให้ไคลเอ็นต์สร้างหรือเปลี่ยนแปลงเอกสาร แต่ไม่อนุญาตให้แก้ไขบางช่องในเอกสารนั้น หรือคุณอาจต้องการ บังคับใช้ว่าเอกสารที่ลูกค้าสร้างทุกครั้งจะมีชุดของ ด้วย คู่มือนี้จะกล่าวถึงวิธีที่คุณสามารถทำงานบางอย่างเหล่านี้ให้เสร็จสมบูรณ์โดยใช้ กฎความปลอดภัยของ Cloud Firestore
อนุญาตสิทธิ์การเข้าถึงระดับอ่านเฉพาะบางฟิลด์
การอ่านใน Cloud Firestore จะดำเนินการในระดับเอกสาร คุณสามารถทำอย่างใดอย่างหนึ่งต่อไปนี้ เรียกดูเอกสารฉบับเต็ม หรือไม่ได้เรียกดูอะไรเลย ไม่มีวิธีดึงข้อมูล เอกสารบางส่วน คุณไม่สามารถใช้กฎความปลอดภัยเพียงอย่างเดียว ป้องกันไม่ให้ผู้ใช้อ่านช่องบางช่องภายในเอกสาร
หากมีบางช่องในเอกสารที่คุณต้องการซ่อน
วิธีที่ดีที่สุดคือใส่ไว้ในเอกสารแยกต่างหาก สำหรับ
ตัวอย่างเช่น คุณอาจพิจารณาสร้างเอกสารในคอลเล็กชันย่อย private
ดังนี้
/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
จากนั้นคุณจะเพิ่มกฎความปลอดภัยที่มีระดับการเข้าถึงที่แตกต่างกันสำหรับ
2 คอลเล็กชัน ในตัวอย่างนี้ เราใช้การอ้างสิทธิ์ในการตรวจสอบสิทธิ์ที่กำหนดเอง
เพื่อบอกว่าเฉพาะผู้ใช้ที่มีการอ้างสิทธิ์ในการตรวจสอบสิทธิ์ที่กำหนดเอง role
เท่ากับ Finance
เท่านั้นที่จะทำได้
ดูข้อมูลด้านการเงินของพนักงาน
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'
}
}
}
}
การจำกัดช่องในการสร้างเอกสาร
Cloud Firestore เป็นแบบไร้สคีมา ซึ่งหมายความว่าไม่มีข้อจำกัด ระดับฐานข้อมูลสำหรับฟิลด์ที่เอกสารมี ขณะที่รายการนี้ ความยืดหยุ่นสามารถทำให้การพัฒนาง่าย��ึ้น ในบางเวลาที่คุณต้องการ เพื่อให้แน่ใจว่าไคลเอ็นต์จะสร้างได้เฉพาะเอกสารที่มีช่องข้อมูลที่เจาะจง หรือไม่มีช่องอื่นๆ
คุณสร้างกฎเหล่านี้ได้โดยตรวจสอบเมธอด keys
ของเมธอด
request.resource.data
ออบเจ็กต์ นี่คือรายการช่องทั้งหมดที่ไคลเอ็นต์
กำลังพยายามเขียนในเอกสารใหม่นี้ การรวมช่องชุดนี้เข้าด้วยกัน
ที่มีฟังก์ชันอย่างเช่น hasOnly()
หรือ hasAny()
คุณสามารถเพิ่มตรรกะที่จำกัดประเภทเอกสารที่ผู้ใช้สามารถเพิ่มได้
Cloud Firestore
กำหนดให้ระบุข้อมูลในช่องใดช่องหนึ่งในเอกสารใหม่
สมมติว่าคุณต้องการตรวจสอบให้แน่ใจว่าเอกสารทั้งหมดที่สร้างใน restaurant
มีฟิลด์ name
, location
และ city
อย่างน้อย 1 ฟิลด์ คุณสามารถ
โดยโทรไปที่ hasAll()
ในรายการคีย์ในเอกสารใหม่
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']);
}
}
}
การทำเช่นนี้ช่วยให้สามารถสร้างร้านอาหารโดยใช้ฟิลด์อื่นๆ ได้เช่นกัน แต่จะทำให้ เอกสารทั้งหมดที่ไคลเอ็นต์สร้างขึ้นมีช่องอย่างน้อย 3 ช่องนี้
การห้ามการเสนอราคาในช่องที่เฉพาะเจาะจงในเอกสารใหม่
ในทำนองเดียวกัน คุณสามารถป้องกันไม่ให้ไคลเอ็นต์สร้างเอกสารที่มี
ฟิลด์ที่ต้องการโดยใช้ hasAny()
ในรายการฟิลด์ที่ไม่ได้รับอนุญาต วิธีนี้จะประเมินเป็น "จริง" หาก
มีช่องเหล่านี้อยู่ คุณอาจต้องลบ
เพื่อไม่อนุญาตบางช่อง
ตัวอย่างเช่น ในตัวอย่างต่อไปนี้ ไคลเอ็นต์ไม่ได้รับอนุญาตให้สร้าง
เอกสารที่มีฟิลด์ average_score
หรือ rating_count
ตั้งแต่
เพิ่มโดยการเรียกใช้เซิร์ฟเวอร์ในภายหลัง
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']));
}
}
}
การสร้างรายการที่อนุญาตสำหรับเอกสารใหม่
แทนที่จะห้ามการเสนอราคาบางช่องในเอกสารใหม่ คุณอาจสร้าง
เฉพาะรายการของช่องที่อนุญาตอย่างชัดแจ้งในเอกสารใหม่ จากนั้น
คุณสามารถใช้hasOnly()
เพื่อให้มั่นใจว่าเอกสารใหม่ที่สร้างขึ้นมีฟิลด์เหล่านี้
(หรือชุดย่อยของฟิลด์เหล่านี้) และใช้กับอย่างอื่นไม่ได้
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']));
}
}
}
รวมช่องที่ต้องกรอกและไม่บังคับ
คุณรวมการดำเนินการ hasAll
และ hasOnly
เข้าด้วยกันในการรักษาความปลอดภัยได้
กำหนดให้มีฟิลด์บางฟิลด์และอนุญาตฟิลด์อื่นๆ เช่น ตัวอย่างนี้
เอกสารใหม่ทั้งหมดต้องมี name
, location
และ city
และอนุญาตช่อง address
, hours
และ 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']));
}
}
}
ในสถานการณ์จริง คุณอาจต้องการย้ายตรรกะนี้ไปยังฟังก์ชันตัวช่วย เพื่อหลีกเลี่ยงการทำซ้ำโค้ดและเพื่อให้รวมตัวเลือกและ ไว้ในรายการเดียว เช่น
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']);
}
}
}
การจำกัดช่องที่อัปเดต
แนวทางปฏิบัติด้านความปลอดภัยโดยทั่วไปคือการอนุญาตให้ลูกค้าแก้ไขบางช่องเท่านั้น
อื่นๆ คุณไม่สามารถทำเช่นนั้นได้ด้วยการดูที่
request.resource.data.keys()
รายการที่อธิบายไว้ในส่วนก่อนหน้า ตั้งแต่นี้เป็นต้นไป
รายการนี้จะแสดงเอกสารที่สมบูรณ์ตามที่จะใช้หลังจากการอัปเดต และ
จะรวมฟิลด์ที่ไคลเอ็นต์ไม่มีการเปลี่ยนแปลง
อย่างไรก็ตาม ��ากคุณจะใช้ diff()
คุณสามารถเปรียบเทียบ request.resource.data
กับ
resource.data
ออบเจ็กต์ ซึ่งเป็นตัวแทนของเอกสารในฐานข้อมูลก่อน
อัปเดต การดำเนินการนี้จะสร้าง mapDiff
ซึ่งเป็นออบเจ็กต์ที่มีการเปลี่ยนแปลงทั้งหมดระหว่าง
แผนที่
ด้วยการเรียกใช้ affectedKeys()
ใน MapDiff นี้ คุณสามารถสร้างชุดฟิลด์ที่มีการเปลี่ยนแปลง
ไว้ในการแก้ไข จากนั้นคุณจะใช้ฟังก์ชันต่างๆ ได้ เช่น
hasOnly()
หรือ hasAny()
เพื่อตรวจสอบว่าชุดนี้มี (หรือไม่มี) บางรายการ
การป้องกันการเปลี่ยนแปลงบางช่อง
โดยใช้hasAny()
ในชุดที่สร้างขึ้นโดย affectedKeys()
แล้วปฏิเสธผลลัพธ์ คุณสามารถปฏิเสธคำขอของลูกค้าใดๆ ที่พยายามจะ
เปลี่ยนฟิลด์ที่ไม่ต้องการให้เปลี่ยนแปลง
เช่น คุณอาจต้องการอนุญาตให้ลูกค้าอัปเดตข้อมูลเกี่ยวกับ แต่ไม่ได้เปลี่ยนคะแนนเฉลี่ยหรือจำนวนรีวิว
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']));
}
}
}
อนุญาตให้เปลี่ยนแปลงบางช่องเท่านั้น
แทนการระบุฟิลด์ที่คุณไม่ต้องการเปลี่ยนแปลง คุณยังสามารถใช้
hasOnly()
เพื่อระบุรายการฟิลด์ที่คุณต้องการเปลี่ยนแปลง โดยทั่วไป
ถือว่าปลอดภัยกว่า เนื่องจากการเขียนในช่องเอกสารใหม่
ไม่อนุญาตโดยค่าเริ่มต้นจนกว่าคุณจะอนุญาตอย่างชัดแจ้งในกฎความปลอดภัย
เช���น แทนการไม่อนุญาต average_score
และ rating_count
คุณสามารถสร้างกฎความปลอดภัยที่ให้ลูกค้าเปลี่ยน
ช่อง name
, location
, city
, address
, hours
และ 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']));
}
}
}
ซึ่งหมายความว่าหากในอนาคต มีการนำแอปร้านอาหารของคุณไปทำซ้ำ
มีช่อง telephone
อยู่ พยายามแก้ไขช่องนั้นไม่สำเร็จ
จนกว่าคุณจะย้อนกลับและเพิ่มช่องนั้นลงในรายการ hasOnly()
ในการรักษาความปลอดภัย
ก��
ประเภทช่องที่บังคับใช้
ผลกระทบอีกประการหนึ่งของ Cloud Firestore ที่ไม่มีสคีมาคือไม่มีสคีมา
การบังคับใช้ที่ระดับฐานข้อมูลสำหรับประเภทข้อมูลที่สามารถจัดเก็บไว้ใน
ฟิลด์ที่เฉพาะเจาะจง อย่างไรก็ตาม การดำเนินการเช่นนี้สามารถบังคับใช้ในกฎความปลอดภัยได้
ด้วยโอเปอเรเตอร์ is
เช่น กฎความปลอดภัยต่อไปนี้บังคับให้score
ของรีวิว
ต้องเป็นจำนวนเต็ม ฟิลด์ headline
, content
และ author_name
เป็นสตริง และ review_date
เป็นการประทับเวลา
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
);
}
}
}
}
ประเภทข้อมูลที่ถูกต้องสำหรับโอเปอเรเตอร์ is
คือ bool
, bytes
, float
, int
list
, latlng
, number
, path
, map
, string
และ timestamp
is
โอเปอเรเตอร์ยังรองรับข้อมูล constraint
, duration
, set
และ map_diff
ด้วย
แต่เนื่องจากภาษาเหล่านี้สร้างขึ้นจากภาษาของกฎความปลอดภัยเอง และ
ที่ลูกค้าไม่ได้สร้างขึ้น คุณไม่ค่อยได้ใช้ในเชิงปฏิบัติ
แอปพลิเคชัน
ประเภทข้อมูล list
และ map
ไม่รองรับอาร์กิวเมนต์ทั่วไปหรืออาร์กิวเมนต์ประเภท
อีกนัยหนึ่งคือ คุณสามารถใช้กฎความปลอดภัยเพื่อบังคับใช้
มีรายการหรือแผนที่ แต่คุณไม่สามารถบังคับให้ช่องมีรายการ
ของจำนวนเต็มหรือสตริงทั้งหมด
ในทำนองเดียวกัน คุณสามารถใช้กฎความปลอดภัยเพื่อบังคับใช้ค่าประเภทสำหรับ ข้อมูลในรายการหรือแผนที่ (โดยใช้สัญลักษณ์เบรกหรือชื่อคีย์ตามลำดับ) แต่ไม่มีทางลัดที่จะบังคับใช้ประเภทข้อมูลของสมาชิกทั้งหมดในแผนที่ หรือรายการพร้อมกัน
เช่น กฎต่อไปนี้ช่วยให้มั่นใจว่าช่อง tags
ในเอกสาร
มีรายการ และรายการแรกเป็นสตริง และยังช่วยให้มั่นใจว่า
ฟิลด์ product
มีแผนที่ซึ่งมีชื่อผลิตภัณฑ์ที่
เป็นสตริงและจำนวนที่เป็นจำนวนเต็ม
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
}
}
}
}
ต้องมีการบังคับใช้ประเภทช่องทั้งเมื่อสร้างและอัปเดตเอกสาร ดังนั้นคุณอาจพิจารณาสร้างฟังก์ชันตัวช่วยที่คุณสามารถ เรียกใช้ทั้งในส่วนสร้างและอัปเดตของกฎความปลอดภัย
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
}
}
}
}
ประเภทการบังคับใช้สำหรับช่องที่ไม่บังคับ
โปรดทราบว่าการโทรหา request.resource.data.foo
บน
เอกสารที่ไม่มี foo
อยู่จะทำให้เกิดข้อผิดพลาด ดังนั้น
กฎความปลอดภัยที่เรียกใช้ดังกล่าวจะปฏิเสธคำขอ คุณสามารถจัดการกับเรื่องนี้
โดยใช้แท็ก get
ใน request.resource.data
เมธอด get
ช่วยให้คุณระบุ
อาร์กิวเมนต์เริ่มต้นสำหรับฟิลด์ที่คุณกำลังดึงข้อมูลจากแผนที่ ถ้าฟิลด์นั้น
ไม่มีอยู่
เช่น หากเอกสารการตรวจสอบมีช่อง photo_url
ด้วยหรือไม่ก็ได้
และช่อง tags
(ไม่บังคับ) ที่คุณต้องการยืนยันคือสตริงและรายการ
ตามลำดับ คุณสามารถทำได้โดยการเขียนฟิลด์
reviewFieldsAreValidTypes
ให้เป็นฟังก์ชันต่อไปนี้
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;
}
การดำเนินการนี้จะปฏิเสธเอกสารที่มี tags
อยู่ แต่ไม่ใช่รายการในขณะที่ยังอยู่
การอนุญาตเอกสารที่ไม่มีฟิลด์ tags
(หรือ photo_url
)
ไม่อนุญาตการเขียนบางส่วน
หมายเหตุสุดท้ายเกี่ยวกับกฎการรักษาความปลอดภัยของ Cloud Firestore คือ แก้ไขเอกสาร หรือปฏิเสธการแก้ไขทั้งหมด คุณไม่สามารถสร้างกฎความปลอดภัยที่ยอมรับการเขียนไปยังบางฟิลด์ใน ขณะปฏิเสธบุคคลอื่นในการดำเนินงานเดียวกัน