Firestore集合组查询符合文档规则却失败,提示字段未定义
Firestore集合组查询安全规则问题排查
我在使用Firestore集合组查询时遇到安全规则问题,已参照官方文档案例配置规则,并使用规则模拟器测试。数据结构为forums集合下嵌套posts子集合,运行集合组查询获取author为id_authedDB的posts文档,预期仅返回1条符合条件的结果,但模拟器提示published或author字段未定义。以下是相关信息:
数据结构
forums/ forum_id/ posts/ post_id2:{ author:"another_autor" published:true } forum_id2/ posts/ post_id:{ author:"id_authedDB" published:true }
安全规则
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { // Returns `true` if the requested post is 'published' // or the user authored the post function authorOrPublished() { return resource.data.published == true || request.auth.uid == resource.data.author; } match /{path=**}/posts/{post} { // Anyone can query published posts // Authors can query their unpublished posts allow list: if authorOrPublished(); // Anyone can retrieve a published post // Authors can retrieve an unpublished post allow get: if authorOrPublished(); } match /forums/{forumid}/posts/{postid} { // Only a post's author can write to a post allow write: if request.auth.uid == resource.data.author; } } }
测试代码
const { readFileSync, createWriteStream } = require('fs'); const http = require("http"); const testing = require('@firebase/rules-unit-testing'); const { initializeTestEnvironment, assertFails, assertSucceeds } = testing; const { doc, setDoc, setLogLevel, getDocs, query, collectionGroup, where } = require('firebase/firestore'); /** @type testing.RulesTestEnvironment */ let testEnv; const projectId = "builder-d0c31" const host = '127.0.0.1' const port = 8080 before(async () => { // Silence expected rules rejections from Firestore SDK. Unexpected rejections // will still bubble up and will be thrown as an error (failing the tests). setLogLevel('error'); testEnv = await initializeTestEnvironment({ projectId, firestore: { host, port, rules: readFileSync('firestore.rules', 'utf8') }, }); }); after(async () => { // Delete all the FirebaseApp instances created during testing. // Note: this does not affect or clear any data. await testEnv.cleanup(); // Write the coverage report to a file const coverageFile = 'firestore-coverage.html'; const fstream = createWriteStream(coverageFile); await new Promise((resolve, reject) => { const { host, port } = testEnv.emulators.firestore; const quotedHost = host.includes(':') ? `[${host}]` : host; http.get(`http://${quotedHost}:${port}/emulator/v1/projects/${testEnv.projectId}:ruleCoverage.html`, (res) => { res.pipe(fstream, { end: true }); res.on("end", resolve); res.on("error", reject); }); }); console.log(`View firestore rule coverage information at ${coverageFile}\n`); }); beforeEach(async () => { await testEnv.clearFirestore(); }); ///////////////////////// // Main test starts here ///////////////////////// describe("posts", () => { it('should allow authedDB to get post', async function() { // create a user const uid = 'id_authedDB' const authedDB = testEnv.authenticatedContext(uid).firestore(); // Setup: Create documents in DB for testing (bypassing Security Rules). await testEnv.withSecurityRulesDisabled(async (context) => { const db = context.firestore() await setDoc(doc(db, 'forums/forum_id/posts/post_id'), {author:uid, published:true}) await setDoc(doc(db, 'forums/forum_id2/posts/post_id2'), {author:'another_autor', published:true}) }); await assertSucceeds(getDocs(query(collectionGroup(authedDB, 'posts', where("author", "==", uid))))); }); });
终端报错信息

问题原因与修复方案
问题根源
Firestore集合组查询的安全规则会遍历所有匹配的posts集合文档,对每篇文档应用规则判断。当规则直接访问resource.data.published或resource.data.author时,若某篇文档缺失这些字段,就会抛出"字段未定义"的错误。
修复步骤
- 添加字段存在性检查:在
authorOrPublished函数中先判断字段是否存在,再进行值比较,避免未定义错误。 - 优化写操作规则:强制创建/更新文档时必须包含指定字段,从源头避免字段缺失。
修改后的安全规则:
rules_version = '2'; service cloud.firestore { match /databases/{database}/documents { function authorOrPublished() { // 先验证字段存在,再判断权限条件 return resource.data.keys().hasAll(['published', 'author']) && (resource.data.published == true || request.auth.uid == resource.data.author); } match /{path=**}/posts/{post} { allow list: if authorOrPublished(); allow get: if authorOrPublished(); } match /forums/{forumid}/posts/{postid} { // 创建文档时强制字段存在且author为当前用户 allow create: if request.auth != null && request.resource.data.author == request.auth.uid && request.resource.data.keys().hasAll(['published', 'author']); // 更新文档时验证用户为作者 allow update: if request.auth != null && resource.data.author == request.auth.uid; // 删除操作仅作者可执行 allow delete: if request.auth != null && resource.data.author == request.auth.uid; } } }
额外验证建议
可以添加测试用例,模拟缺少published或author字段的文档,验证规则是否能正确拦截此类文档的访问,确保规则健壮性。
内容的提问来源于stack exchange,提问作者Tim Castelijn
相关产品推荐
相关产品推荐

