หน้านี้สร้างขึ้นจากแนวคิดใน การจัดโครงสร้างกฎความปลอดภัย และ เงื่อนไขการเขียนสำหรับกฎความปลอดภัย เพื่ออธิบายวิธีที่คุณสามารถใช้กฎความปลอดภัยของ Cloud Firestore เพื่อสร้างกฎที่อนุญาตให้ไคลเอนต์ดำเนินการกับบางฟิลด์ในเอกสาร แต่ไม่ใช่ฟิลด์อื่น
อาจมีบางครั้งที่คุณต้องการควบคุมการเปลี่ยนแปลงในเอกสารที่ไม่ได้อยู่ที่ระดับเอกสาร แต่อยู่ที่ระดับฟิลด์
ตัวอย่างเช่น คุณอาจต้องการอนุญาตให้ไคลเอ็นต์สร้างหรือเปลี่ยนแปลงเอกสาร แต่ไม่อนุญาตให้แก้ไขฟิลด์บางฟิลด์ในเอกสารนั้น หรือคุณอาจต้องการบังคับใช้เอกสารใดๆ ที่ลูกค้าสร้างขึ้นเสมอจะมีชุดฟิลด์บางชุด คู่มือนี้ครอบคลุมถึงวิธีที่คุณสามารถทำงานบางอย่างเหล่านี้ให้สำเร็จโดยใช้กฎความปลอดภัยของ Cloud Firestore
อนุญาตให้เข้าถึงแบบอ่านเฉพาะบางฟิลด์เท่านั้น
การอ่านใน Cloud Firestore ดำเนินการในระดับเอกสาร คุณอาจดึงเอกสารฉบับเต็ม หรือไม่ดึงข้อมูลเลย ไม่มีวิธีเรียกเอกสารบางส่วนกลับมา ไม่สามารถใช้กฎความปลอดภัยเพียงอย่างเดียวเพื่อป้องกันไม่ให้ผู้ใช้อ่านฟิลด์เฉพาะภายในเอกสาร
หากมีฟิลด์บางฟิลด์ในเอกสารที่คุณต้องการซ่อนไม่ให้ผู้ใช้บางรายซ่อน วิธีที่ดีที่สุดคือการใส่ฟิลด์เหล่านั้นในเอกสารแยกต่างหาก ตัวอย่างเช่น คุณอาจพิจารณาสร้างเอกสารในคอลเลกชันย่อย private
ดังนี้:
/พนักงาน/{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
จากนั้นคุณสามารถเพิ่มกฎความปลอดภัยที่มีระดับการเข้าถึงที่แตกต่างกันสำหรับคอลเลกชันทั้งสองได้ ในตัวอย่างนี้ เรากำลังใช้ การอ้างสิทธิ์การรับรองความถูกต้องแบบกำหนดเอง เพื่อบอกว่าเฉพาะผู้ใช้ที่มี 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
เป็นอย่างน้อย คุณสามารถทำได้โดยการเรียก 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']);
}
}
}
ซึ่งช่วยให้สามารถสร้างร้านอาหารด้วยช่องอื่นๆ ได้เช่นกัน แต่ช่วยให้มั่นใจได้ว่าเอกสารทั้งหมดที่สร้างโดยลูกค้ามีอย่างน้อยสามช่องนี้
ห้ามฟิลด์เฉพาะในเอกสารใหม่
ในทำนองเดียวกัน คุณสามารถป้องกันไม่ให้ไคลเอ็นต์สร้างเอกสารที่มีฟิลด์เฉพาะได้โดยใช้ 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 คืออนุญาตให้ไคลเอ็นต์ทำการเปลี่ยนแปลงเอกสารหรือปฏิเสธการแก้ไขทั้งหมด คุณไม่สามารถสร้างกฎความปลอดภัยที่ยอมรับการเขียนบางฟิลด์ในเอกสารของคุณในขณะที่ปฏิเสธฟิลด์อื่นในการดำเนินการเดียวกัน