我具有以下文档层次结构: / organizations / {orgId} / classes / {classId} / students / {studentId}
想法是班级文档中有一个TeacherUid字段,该字段当前存储分配给班级的老师的Uid。只有一位老师或管理员才能阅读/创建/编辑班上的学生。 *请注意,我只是测试老师的阅读,遇到了这个障碍,之后我将对创建/更新权限应用相同的规则。
我有以下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值相同。
我已经使用在线Firebase控制台Firestore模拟器以及运行本地的Firestore模拟器运行的Mocha单元测试进行了测试。
我似乎无法弄清楚问题是什么。
这是我的test.js文档的相关部分:
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());
});
});
这是运行单元测试时的错误输出:
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.
答案 0 :(得分:0)
U。 :)发现了问题。在这里发布我的答案,以防其他人绊倒。
每个Firestore custom function documentation都需要在其使用的变量范围内声明该函数,或者可以将这些变量作为参数传递。
类似于SO的相同问题: Moving Firestore security rule to custom function breaks rule
以下两个选项之一有效:
选项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。虽然我不喜欢在代码中声明通过所有参数传递的函数也很丑陋,并且甚至不在该范围之外调用该函数,所以它更有意义在那里宣布。