Firestore.rules自定义函数未按预期评估 [英] Firestore.rules custom function not evaluating as intended

查看:47
本文介绍了Firestore.rules自定义函数未按预期评估的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我具有以下文档层次结构: /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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆