Firestore 安全规则 - 如何检查某个字段是不是被修改?
Posted
技术标签:
【中文标题】Firestore 安全规则 - 如何检查某个字段是不是被修改?【英文标题】:Firestore Security Rules - How can I check that a field is/isn't being modified?Firestore 安全规则 - 如何检查某个字段是否被修改? 【发布时间】:2018-06-18 23:46:38 【问题描述】:对于我的一生,我无法理解为什么以下内容会导致false
允许写入。假设我的 users
集合一开始是空的,我正在从我的 Angular 前端编写以下形式的文档:
displayName: 'FooBar',
email: 'foo.bar@example.com'
我目前的安全规则:
service cloud.firestore
match /databases/database/documents
match /users/userId
function isAdmin()
return resource.data.role == 'ADMIN';
function isEditingRole()
return request.resource.data.role != null;
function isEditingOwnRole()
return isOwnDocument() && isEditingRole();
function isOwnDocument()
return request.auth.uid == userId;
allow read: if isOwnDocument() || isAdmin();
allow write: if !isEditingOwnRole() && (isOwnDocument() || isAdmin());
一般来说,我不希望任何用户能够编辑自己的角色。普通用户可以编辑自己的文档,管理员可以编辑任何人的文档。
为false
存根isEditingRole()
给出了预期的结果,因此我将其范围缩小到该表达式。
写入总是返回错误,我无法确定原因。任何想法或修复都会有所帮助!
编辑 1
我尝试过的事情:
function isEditingRole()
return request.resource.data.keys().hasAny(['role']);
和
function isEditingRole()
return 'role' in request.resource.data;
和
function isEditingRole()
return 'role' in request.resource.data.keys();
编辑 2
请注意,管理员最终会为用户设置角色,因此角色最终可能存在于文档中。这意味着,根据下面的Firestore docs,该请求将有一个role
键,即使它不在原始请求中。
资源中存在的请求中未提供的字段将添加到
request.resource.data
。规则可以通过比较request.resource.data.foo
和resource.data.foo
来测试字段是否被修改,因为知道resource
中的每个字段也会出现在request.resource
中,即使它没有在写入请求中提交。
据此,我认为排除了“编辑1”中的三个选项。我确实尝试了request.resource.data.role != resource.data.role
的建议,但这也不起作用……我不知所措,开始怀疑 Firestore 中是否真的存在错误。
【问题讨论】:
【参考方案1】:如果您创建一个自定义函数来检查更新,您的规则将更具可读性和可维护性。例如:
service cloud.firestore
match /databases/database/documents
function isUpdatingField(fieldName)
return (!(fieldName in resource.data) && fieldName in request.resource.data) || resource.data[fieldName] != request.resource.data[fieldName];
match /users/userId
// Read rules here ...
allow write: if !isUpdatingField("role") && !isUpdatingField("adminOnlyAttribute");
【讨论】:
不幸的是,我已经根据文档尝试了所有“应该”工作的方式,这就是其中之一。我将更新我的帖子以包含我尝试过的内容。 @menehune23 我认为 keys().hasAny() 正在工作。如果你说它不是,我们绝对应该在 Google 小组中提出它应该是的! 是的,我仍然很困惑。我实际上发现使用in
和hasAny()
似乎都按预期运行,但由于某种原因,role
字段被添加到我不希望它的请求中(即即使@987654325 @ 在资源中不存在)。可能是缓存问题?正如我过去将它添加到文档中一样,但清除了集合以重新开始。
当我更清楚时我会发布更新,但我开始取得一些进展。
它不起作用,很遗憾谷歌误导了我们【参考方案2】:
所以最后,我似乎假设resource.data.nonExistentField == null
会返回false
,而实际上它返回Error
(根据this 和我的测试)。所以我最初的解决方案可能已经遇到了。这令人费解,因为根据the docs,相反的情况应该起作用,但也许文档指的是“不存在”的值,而不是键——一个微妙的区别。
我仍然没有 100% 清楚,但这是我最终得到的结果:
function isAddingRole()
return !('role' in resource.data) && 'role' in request.resource.data;
function isChangingRole()
return 'role' in resource.data && 'role' in request.resource.data && resource.data.role != request.resource.data.role;
function isEditingRole()
return isAddingRole() || isChangingRole();
另一件让我感到困惑的事情是,根据文档,我不需要 isChangingRole()
中的 && 'role' in request.resource.data
部分,因为它应该由 Firestore 自动插入。尽管情况似乎并非如此,但删除它会导致我的写入因权限问题而失败。
可以通过将写入分成create
、update
和delete
部分来澄清/改进,而不仅仅是allow write: if !isEditingOwnRole() && (isOwnDocument() || isAdmin());
。
【讨论】:
【参考方案3】:我使用writeFields
解决了这个问题。请试试这个规则。
allow write: if !('role' in request.writeFields);
就我而言,我使用list
来限制更新字段。它也有效。
allow update: if !(['leader', '_created'] in request.writeFields);
【讨论】:
您的in
规则似乎总是评估为真。 in
运算符仅检查给定列表是否具有特定值(不适用于列表)。为了做你想做的事,试试这个:!(request.writeFields.hasAny(['leader', '_created']));
request.resource.keys.hasAny() 方法最适合不使用 SDK 并坚持使用模拟器的人。
writeFields 不再出现在文档中,显然已被弃用。 ***.com/a/52192476/4458849【参考方案4】:
Tom Bailey (https://***.com/a/48177722/5727205) 的解决方案看起来很有希望。
但在我的情况下,我需要防止字段被编辑,并且可能会出现该字段根本不存在于现有数据中的情况。因此,我添加了一个检查该字段是否存在。
此解决方案会检查两项检查:
-
如果该字段不在请求中且不在现有数据中(等于字段未修改)
或请求与现有数据相同(等于字段未修改)
function isNotUpdatingField(fieldName)
return
( !(fieldName in request.resource.data) && !(fieldName in resource.data) ) ||
request.resource.data[fieldName] == resource.data[fieldName];
【讨论】:
【参考方案5】:通过这个单一的功能,您可以检查一个字段是否正在/没有被创建/修改。
function incomingDataHasFields(fields)
return ((
request.writeFields == null
&& request.resource.data.keys().hasAll(fields)
) || (
request.writeFields != null
&& request.writeFields.hasAll(fields)
));
用法:
match /xxx/xxx
allow create:
if incomingDataHasFields(['foo']) // allow creating a document that contains 'foo' field
&& !incomingDataHasFields(['bar', 'baz']); // but don't allow 'bar' and 'baz' fields to be created
【讨论】:
【参考方案6】:这是一个将所有这些都考虑在内的函数:
不会触发诸如“对象上的属性名称未定义”之类的安全规则错误。 适用于set()
和update()
在使用firebase.firestore.FieldValue.delete()
明确尝试删除字段时有效
可在创建和更新文档时使用(update
、create
)。
function fieldNotWrittenByUser(field)
return (
(
(field in request.resource.data)
&& (resource != null && field in resource.data)
&& request.resource.data[field] == resource.data[field]
)
|| (
!(field in request.resource.data) && (resource == null || !(field in resource.data))
)
)
说明
请记住,request.resource.data
表示 成功写入操作后的资源,即“未来”文档。
如果该字段出现在未来的文档中,它必须与现有文档中的值相同。
或者,如果将来的文档中不存在该字段,则仅当资源尚不存在或现有文档也缺少该字段时才允许该操作。
来源:https://www.sentinelstand.com/article/firestore-security-rules-examples
【讨论】:
“(记住 request.resource.data 代表成功写入操作后的资源)”对我来说值得点赞:)【参考方案7】:由于文档中对 writeFields 的引用已经消失,我不得不想出新的方法来做我们可以用 writeFields 做的事情。
function isSameProperty(request, resource, key)
return request.resource.data[key] == resource.data[key]
match /myCollection/id
// before version !request.writeFields.hasAny(['property1','property2','property3', 'property4']);
allow update: isSameProperty(request, resource, 'property1')
&& isSameProperty(request, resource, 'property2')
&& isSameProperty(request, resource, 'property3')
&& isSameProperty(request, resource, 'property4')
【讨论】:
我不得不做类似的事情。如果我们以后在文档中添加更多字段也不好,我们会很高兴有一个像“[资源没有修改,除了这个字段]然后OK”这样的功能,而不是手动为所有字段重复“isSameProperty”......还需要更新此规则...如果存在我错过的此类功能,很想知道!或者如果有更好的方法来解决这个问题。【参考方案8】:这似乎有点过头了,但是对于更新文档,您可能有其他非用户生成的字段,例如。角色,创建等。您需要可以测试这些字段不会更改的功能。因此满足这三个 FN。
function hasOnlyFields(fields)
if request.resource.data.keys().hasOnly(fields)
function hasNotChanged(fields)
return (fields.size() < 1 || equals(fields[0]))
&& (fields.size() < 2 || equals(fields[1]))
&& (fields.size() < 3 || equals(fields[2]))
&& (fields.size() < 4 || equals(fields[3]))
&& (fields.size() < 5 || equals(fields[4]))
&& (fields.size() < 6 || equals(fields[5]))
&& (fields.size() < 7 || equals(fields[6]))
&& (fields.size() < 8 || equals(fields[7]))
&& (fields.size() < 9 || equals(fields[8]))
function equals(field)
return field in request.resource.data && field in resource.data && request.resource.data[field] == request.resource.data[field]
所以在更新用户文档时,用户只能更新他们的姓名、年龄和地址,而不能更新角色和电子邮件:
allow update: if hasOnlyFields(['name', 'age', 'address']) && hasNotChanged(['email', 'roles'])
注意 hasNotChanged 最多可以检查 9 个字段。此外,这些不是您想要做的唯一检查。您还需要检查文档的类型和所有权。
【讨论】:
【参考方案9】:发现这条规则运作良好:
function propChanged(key)
// Prop changed if key in req but not res, or if key req and res have same value
return (
(key in request.resource.data) && !(key in resource.data)
) || (
(key in request.resource.data) && request.resource.data[key] != resource.data[key]
);
【讨论】:
以上是关于Firestore 安全规则 - 如何检查某个字段是不是被修改?的主要内容,如果未能解决你的问题,请参考以下文章
即使firestore安全规则中的文档字段不可用,也如何允许访问?
有没有办法使用Firestore安全规则使用FieldValue.increment()更新文档字段?
在 Cloud Firestore 规则中 - 如何检查密钥是不是为空