Firestore.rules自定义函数未按预期评估 [英] Firestore.rules custom function not evaluating as intended
问题描述
我具有以下文档层次结构: /organizations/{orgId}/classes/{classId}/students/{studentId}
I have the following document hierarchy: /organizations/{orgId}/classes/{classId}/students/{studentId}
想法是班级文档有一个TeacherUid字段,该字段当前存储分配给班级的老师的Uid.只有一位老师或管理员才能阅读/创建/编辑班上的学生. *请注意,我只是测试老师的阅读,遇到了这一障碍,之后我将对创建/更新权限应用相同的规则.
The idea is the class document has a teacherUid field, which currently stores the Uid of the teacher assigned to the class. Only a teacher or admin should be able to read/create/edit students in the class. *Note that I'm only testing reading by the teacher, ran into this hurdle, after which I'll apply the same rule to the create/update permission.
我有以下firestore.rules:
I have the following firestore.rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /organizations/{orgId} {
allow read: if isAdmin();
allow create, update: if isAdmin();
match /classes/{classId} {
allow read: if request.auth.uid != null;
allow create, update: if isAdmin();
match /students/{studentId} {
allow read: if isAdmin() || belongsToCurrentClass();
allow create, update: if isAdmin();
}
}
}
}
}
function isAdmin() {
// Removed for security. isAdmin routine currently works correctly
}
function belongsToCurrentClass() {
// returns true if the authenticated user is the teacher of the requested class
return get(/databases/$(database)/documents/organizations/$(orgId)/classes/$(classId)).data.teacherUid == request.auth.uid;
}
这似乎不起作用.虽然它正确地允许管理员读取/创建/编辑,但它不允许具有相同的request.auth.uid的经过身份验证的用户读取,该请求与存储在父类文档中的TeacherUid值相同.
This does not appear to be working. While it correctly allows admins to read/create/edit it does not allow reading by the authenticated user with the same request.auth.uid as the teacherUid value stored in the parent class document.
我已经使用在线Firebase控制台Firestore模拟器以及运行本地的Firestore模拟器运行的Mocha单元测试对此进行了测试.
I've tested this using both the online firebase console firestore simulator as well as running mocha unit tests hitting the local firestore emulator.
我似乎无法弄清楚问题是什么.
I can't seem to figure out what the issue is.
这是我的test.js文档的相关部分:
Here are the relevant portions of my test.js document:
const fs = require('fs');
const path = require('path');
const TEST_FIREBASE_PROJECT_ID = 'test-firestore-rules-project';
const firebase = require('@firebase/testing');
const authTeacher = {
uid: 'testTeacher1',
};
const authAdmin = {
// Removed for security
};
before(async () => {
// The above was from the codelab. Commenting out the below since we aren't testing rules at this moment.
const rulesContent = fs.readFileSync(path.resolve(__dirname, '../../firestore.rules'));
await firebase.loadFirestoreRules({
projectId: TEST_FIREBASE_PROJECT_ID,
rules: rulesContent,
});
});
after(() => {
firebase.apps().forEach(app => app.delete());
});
...
describe('Classes/Students/* rules', () => {
const testClassPath = 'organizations/testOrg/classes/testClass';
const testStudentPath = testClassPath + '/students/testStudent';
const newStudentPath = testClassPath + '/students/newStudent';
const testOtherClassPath = 'organizations/testOrg/classes/testClass';
const testOtherStudentPath = testOtherClassPath + '/students/testOtherStudent';
const newOtherStudentPath = testOtherClassPath + '/students/newOtherStudent';
const dbUnauth = firebase
.initializeTestApp({
projectId: TEST_FIREBASE_PROJECT_ID,
})
.firestore();
const dbTeacher = firebase
.initializeTestApp({
projectId: TEST_FIREBASE_PROJECT_ID,
auth: authTeacher,
})
.firestore();
const dbAdmin = firebase
.initializeTestApp({
projectId: TEST_FIREBASE_PROJECT_ID,
auth: authAdmin,
})
.firestore();
before(async () => {
const admin = firebase
.initializeAdminApp({
projectId: TEST_FIREBASE_PROJECT_ID,
})
.firestore();
// Create Class - for testing classes that belong to the authenticated user
await admin.doc(testClassPath).set({
teacherUid: authTeacher.uid,
});
// Create Student
await admin.doc(testStudentPath).set({
name: 'John Smith',
});
// Create Other Class - for testing classes that belong to other users
await admin.doc(testOtherClassPath).set({
teacherUid: 'someOtherTeacherUid',
});
// Create Other Student
await admin.doc(testOtherStudentPath).set({
name: 'Cave Johnson',
});
});
after(() => {
// Clear data from the emulator
firebase.clearFirestoreData({ projectId: TEST_FIREBASE_PROJECT_ID });
});
it('Unauthenticated users cannot access students', async () => {
await firebase.assertFails(dbUnauth.doc(testStudentPath).get());
});
it('Unauthenticated users cannot create students', async () => {
await firebase.assertFails(
dbUnauth.doc(newStudentPath).set({
name: 'Jane Doe',
})
);
});
it('Non-admin users can read students', async () => {
await firebase.assertSucceeds(dbTeacher.doc(testStudentPath).get());
});
it('Non-admin users cannot read students from another user', async () => {
await firebase.assertFails(dbTeacher.doc(testOtherStudentPath).get());
});
it('Non-admin users can edit students', async () => {
await firebase.assertSucceeds(
dbTeacher.doc(testStudentPath).set({
anotherProperty: 'Some Value',
})
);
});
it('Non-admin users cannot edit students from another user', async () => {
await firebase.assertFails(
dbTeacher.doc(testOtherStudentPath).set({
anotherProperty: 'Some Value',
})
);
});
it('Non-admin users can create students', async () => {
await firebase.assertSucceeds(
dbTeacher.doc(newStudentPath).set({
name: 'Jane Doe',
})
);
});
it('Non-admin users cannot create students in a class they do not belong to', async () => {
await firebase.assertFails(
dbTeacher.doc(testOtherStudentPath).set({
name: 'Jane Doe',
})
);
});
it('Non-admin users cannot delete students', async () => {
await firebase.assertFails(dbTeacher.doc(testStudentPath).delete());
});
it('Admin users can read students', async () => {
await firebase.assertSucceeds(dbAdmin.doc(testStudentPath).get());
});
it('Admin users can create students', async () => {
await firebase.assertSucceeds(
dbAdmin.doc(newStudentPath).set({
name: 'Jane Doe',
})
);
});
it('Admin users can edit students', async () => {
await firebase.assertSucceeds(
dbAdmin.doc(testStudentPath).set({
anotherProperty: 'Some Value',
})
);
});
it('Admin users cannot delete students', async () => {
await firebase.assertFails(dbAdmin.doc(testStudentPath).delete());
});
});
这是运行单元测试时的错误输出:
Here is the error output when running the unit tests:
PS C:\Local\Personal\Angular Projects\TSI\functions> npm test
> functions@ test C:\Local\Personal\Angular Projects\TSI\functions
> mocha
Organization rules
√ Unauthenticated users cannot read organizations (48ms)
√ Unauthenticated users cannot create orgs organizations
√ Unauthenticated users cannot delete organizations
√ Non-admin users cannot read organizations (45ms)
√ Non-admin users cannot edit organizations
√ Non-admin users cannot create organizations
√ Non-admin users cannot delete organizations
√ Admin users can read organizations (47ms)
√ Admin users can create organizations
√ Admin users can edit organizations
√ Admin users cannot delete organizations
Classes rules
√ Unauthenticated users cannot access classes
√ Unauthenticated users cannot create classes
√ Unauthenticated users cannot delete classes
√ Non-admin users can read classes (38ms)
√ Non-admin users cannot edit classes
√ Non-admin users cannot create classes
√ Non-admin users cannot delete classes
√ Admin users can read classes
√ Admin users can create classes
√ Admin users can edit classes
√ Admin users cannot delete classes
Classes/Students/* rules
√ Unauthenticated users cannot access students
√ Unauthenticated users cannot create students
1) Non-admin users can read students
√ Non-admin users cannot read students from another user
2) Non-admin users can edit students
√ Non-admin users cannot edit students from another user
3) Non-admin users can create students
√ Non-admin users cannot create students in a class they do not belong to
√ Non-admin users cannot delete students
√ Admin users can read students
√ Admin users can create students
√ Admin users can edit students
√ Admin users cannot delete students
32 passing (3s)
3 failing
1) Classes/Students/* rules
Non-admin users can read students:
FirebaseError:
Null value error. for 'get' @ L15
at new FirestoreError ...
2) Classes/Students/* rules
Non-admin users can edit students:
FirebaseError: 7 PERMISSION_DENIED:
false for 'update' @ L16
at new FirestoreError ...
3) Classes/Students/* rules
FirebaseError: 7 PERMISSION_DENIED:
false for 'create' @ L16
at new FirestoreError ...
npm ERR! Test failed. See above for more details.
推荐答案
U. :)发现了问题.在这里发布我的答案,以防其他人绊倒.
Ugh. :) Found the issue. Posting my answer here in case this trips anyone else up.
根据功能需要的 Firestore自定义功能文档可以在其使用的变量范围内声明,也可以将变量作为参数传递.
Per the Firestore custom function documentation the function needs to be declared within the scope of the variables it's using, or the variables can be passed as arguments.
类似于SO上的相同问题: 将Firestore安全规则移至自定义功能中断规则
Similar to the same issue on SO here: Moving Firestore security rule to custom function breaks rule
以下两个选项之一有效:
Either one of the two following options works:
选项1-在变量范围内声明:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /organizations/{orgId} {
allow read: if isAdmin();
allow create, update: if isAdmin();
match /classes/{classId} {
function belongsToCurrentClass() {
// retuns true if the authenticated user is the teacher of the requested class
return get(/databases/$(database)/documents/organizations/$(orgId)/classes/$(classId)).data.teacherUid == request.auth.uid;
}
allow read: if request.auth.uid != null;
allow create, update: if isAdmin();
match /students/{studentId} {
allow read: if isAdmin() || belongsToCurrentClass();
allow create, update: if isAdmin() || belongsToCurrentClass();
}
}
}
}
}
function isAdmin() {
// Removed for security.
}
选项2-将变量作为参数传递:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /organizations/{orgId} {
allow read: if isAdmin();
allow create, update: if isAdmin();
match /classes/{classId} {
allow read: if request.auth.uid != null;
allow create, update: if isAdmin();
match /students/{studentId} {
allow read: if isAdmin() || belongsToCurrentClass();
allow create, update: if isAdmin() || belongsToCurrentClass(database, orgId, classId);
}
}
}
}
}
function isAdmin() {
// Removed for security.
}
function belongsToCurrentClass(database, orgId, classId) {
// returns true if the authenticated user is the teacher of the requested class
return get(/databases/$(database)/documents/organizations/$(orgId)/classes/$(classId)).data.teacherUid == request.auth.uid;
}
我个人选择了选项1.虽然我不喜欢在代码中声明通过所有参数传递的函数也很丑陋,并且甚至不会在该范围之外调用该函数,所以它更有意义在那里宣布.
I personally went for Option 1. While I don't like declaring functions within my code passing all the arguments was pretty ugly as well, and the function wouldn't even be called outside of that scope, so it made more sense to declare it there.
这篇关于Firestore.rules自定义函数未按预期评估的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!