如何在Firestore规则中校验仅更新单个指定字段?
我在做Firestore任务状态更新的功能时遇到了一个棘手的问题:应用端代码用来更新单个字段(比如helper_completed),但安全规则里的字段存在性校验始终返回false,试了好几种写法都没用。
先贴一下我的应用端Swift代码:
static func markTaskAsCompleted(field: String, task: Task) -> Promise<Bool> { return Promise<Bool> { seal in FirestoreHandler.shared.db.collection("tasks").document(task.id!).updateData([ "\(field)": true ], completion: { (err) in seal.resolve(err != nil ? false : true, err) }) } }
对应的Firestore安全规则,我想限制更新操作只能修改helper_completed这一个字段,规则代码如下:
match /tasks/{task_id} { ... allow update: if (...) || isValidTaskUpdate(); ... } function isValidTaskUpdate() { return currentUser().uid == existingData().assigned.user_id && incomingData().keys().size() == 1 && 'helper_completed' in incomingData(); } function incomingData() { return request.resource.data; }
实际执行时,最后那个'helper_completed' in incomingData()的校验一直返回false。我还试过下面两种写法,结果还是一样:
'helper_completed' in incomingData().keys(); incomingData().hasAny('helper_completed');
请问到底该怎么正确校验更新请求只修改了helper_completed这一个指定字段?
解决方案
问题的核心是你误解了request.resource.data的含义!当你用updateData方法更新文档时,request.resource.data返回的是整个文档的完整数据,而不是你提交的那单个字段。这就导致incomingData().keys().size()等于文档所有字段的总数,而不是你预期的1,字段存在性判断自然也会失效。
要解决这个问题,你需要用request.resource.data.diff(existingData()).affectedKeys()来获取实际被修改的字段列表,而不是直接用完整文档数据做判断。修改后的isValidTaskUpdate函数应该是这样:
function isValidTaskUpdate() { let modifiedKeys = request.resource.data.diff(existingData()).affectedKeys(); return currentUser().uid == existingData().assigned.user_id && modifiedKeys.size() == 1 && modifiedKeys.hasAny('helper_completed'); }
这个写法会先对比新旧文档数据,拿到真正被改动的字段集合,再校验集合大小为1且包含目标字段,完全符合你的需求。
你也可以用更简洁的写法直接判断修改字段是否完全匹配:
function isValidTaskUpdate() { return currentUser().uid == existingData().assigned.user_id && request.resource.data.diff(existingData()).affectedKeys() == ['helper_completed']; }
这两种写法都能解决你的问题,核心就是利用diff方法精准获取更新操作的改动字段,而不是用完整文档数据做无效判断。
内容的提问来源于stack exchange,提问作者Zat42

