Firestore使用电子邮件密钥对文档地图进行规则和查询,以便与用户共享数据

时间:2018-03-13 21:09:43

标签: firebase google-cloud-firestore firebase-security firebase-security-rules

用例摘要

  1. 用户A创建故事
  2. 用户A通过电子邮件(通过云功能发送)与未知(对应用)用户B共享故事
  3. 用户B收到有关故事的电子邮件
  4. 用户B访问应用并创建新帐户
  5. 用户B查看/阅读由用户A创建的故事
  6. 注意:故事只能由他们与谁共享或由

    创建

    我正在建立一个基于角色的访问系统。我一直在看role based access firestore documentation而我错过了一件。

    考虑一个只能由共享该故事的用户阅读的故事。大多数示例包括firestore示例使用UID都有关键来识别共享用户。但是,该用户当前可能不是firebase应用程序的用户,另外用户如何分配该UID。

    故事数据

    {
      title: "A Great Story",
      roles: {
        aliceUID: {
          hasRole: true,
          type: "owner",
        },
        bobUID: {
          hasRole: true,
          type: "reader",
        }
      }
    }
    

    故事查询

    firebase.firestore().collection('stories').where(`roles.${user.uid}.hasRole`, '==', true)
    

    第二部分可以通过维护单独的用户集合来解决,然后您可以从他们的电子邮件地址中找到用户,但这不会解决从未登录过的用户。

    打算分享故事的用户可以为用户添加电子邮件地址。然后使用firebase功能,我们可以发送电子邮件通知用户共享故事,用户可以登录应用程序并阅读该故事。

    如果我们继续使用此方法,那么您将没有UID,只有一个电子邮件地址作为密钥。

    故事数据

    {
      title: "A Great Story",
      roles: {
        alice@yahoo.com: {
          hasRole: true,
          type: "owner",
        },
        bob@gmail.com: {
          hasRole: true,
          type: "reader",
        }
      }
    }
    

    故事查询

    firebase.firestore().collection('stories').where(`roles.${user.email}.hasRole`, '==', true)
    

    更新了Firestore规则 - 来自documentation

    function getRole(rsc) {
      // Read from the "roles" map in the story document.
      return rsc.data.roles[request.auth.uid] || rsc.data.roles[request.auth.token.email];
    }
    

    我无法让电子邮件查询正常工作。这个SO issue提到了

      

    不幸的是,不允许使用点作为地图键。所以电子邮件地址不起作用。

    我不明白为什么这会在规则方面发生冲突。它确实会使一个可能无效的where子句

    e.g。

    .where(`roles.${user.email}.hasRole`, '==', true) -> .where(`roles.bob@gmail.com.hasRole`, '==', true)
    

    看起来像无效的JS,遗憾的是[]是无效的字符,所以我们无法做到

    .where(`roles[${user.email}]hasRole`, '==', true)
    

    我看到的最后一件事就是Firebase talk用来逃避电子邮件地址,比如

    function encodeAsFirebaseKey(string) {
      return string.replace(/\%/g, '%25')
        .replace(/\./g, '%2E')
        .replace(/\#/g, '%23')
        .replace(/\$/g, '%24')
        .replace(/\//g, '%2F')
        .replace(/\[/g, '%5B')
        .replace(/\]/g, '%5D');
    };
    

    这似乎可以修复查询where子句,它是一个有效的数据结构,但它不是valid Firestore rule,这意味着它没有真正的安全实施。

    关于如何实现这一点的任何想法?请包含有效的数据结构,firestore规则和查询。我已经展示并看到了许多例子,其中三个都是非工作解决方案。

    谢谢!

3 个答案:

答案 0 :(得分:1)

这是Control Access with Custom Claims and Security Rules的用例。

  

Firebase Admin SDK支持在用户上定义自定义属性   账户。这提供了实现各种访问的能力   Firebase中的控制策略,包括基于角色的访问控制   应用。这些自定义属性可以为用户提供不同级别的   访问(角色),在应用程序的安全规则中强制执行。

     

可以为以下常见情况定义用户角色:

     
      
  • 授予用户访问数据和资源的管理权限。
  •   
  • 定义用户所属的不同组。
  •   
  • 提供多级访问:
  •   
  • 区分付费/未付费订阅者。
  •   
  • 区分主持人与普通用户。
  •   
  • 教师/学生申请等
  •   

您需要站起来一个节点服务器(技能水平低)。像下面这样的脚本可以生成声明。

var admin = require('firebase-admin');

var serviceAccount = require("./blah-blah-blah.json");

admin.initializeApp({
    credential: admin.credential.cert(serviceAccount),
    databaseURL: "https://my-app.firebaseio.com"
});

admin.auth().setCustomUserClaims("9mB3asdfrw34ersdgtCk1", {admin: true}).then(() => {
    console.log("Custom Claim Added to UID. You can stop this app now.");
});

然后在您的客户端,执行以下操作:

firebase.auth()。onAuthStateChanged(function(user){     if(user){

    //is email address up to date? //do we really want to modify it or mess w it?
    switch (user.providerData[0].providerId) {
        case 'facebook':
        case 'github':
        case 'google':
        case 'twitter':
            break;
        case 'password':
            // if (!verifiedUser) {
            // }
            break;
    }

    //if admin
    firebase.auth().currentUser.getIdToken().then((idToken) => {
        // Parse the ID token.
        const payload = JSON.parse(window.atob(idToken.split('.')[1]));
        // Confirm the user is an Admin or whatever
        if (!!payload['admin']) {
            switch (thisPage) {
                case "/admin":
                    showAdminStuff();
                    break;
            }
        }
        else {
            if(isAdminPage()){
                document.location.href="/";
            }
        }
    })
    .catch((error) => {
        console.log(error);
    });
}
else {
    //USER IS NOT SIGNED IN

}

});

答案 1 :(得分:1)

基本问题是我不知道如何正确地制定有效的查询。事实证明,您不需要在一行中创建查询。

您可以使用FieldPath构建查询参数。

var path = new firebase.firestore.FieldPath('roles', email ,'hasRole');
firebase.firestore().collection('stories').where(path, '==', true)

这解决了原始缺失的部分。

答案 2 :(得分:0)

从我收集到的内容来看,您希望将故事设为私密,但可以与任何人共享。您最关心的是没有应用但拥有共享链接的用户。

因此,您最大的问题是firebase的工作方式意味着您无法在不使用某种登录的情况下限制对数据的访问。

如果您可以要求新用户登录,那么您的答案应该只是动态链接。这些链接通过安装和登录一直是持久的,这意味着任何人都可以获得附加了故事访问数据的动态链接。您只需要在应用程序的mainActivityAppDelegate等效项中添加一个监听器,以记录动态链接数据并在登录后运行指定任务。

如果您希望完全远离登录,则设置动态链接以绕过登录过程并将新安装用户直接指向故事。然而,第二个选项需要更多工作并且安全性较低,因为您可能会被迫复制故事数据,以便对具有正确链接故事的任何人进行开放访问。