Firestore安全性:拒绝更新/如果请求资源中的字段写入(在模拟器中工作,而不是IRL)

时间:2018-10-24 22:28:35

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

我的用户个人资料数据存储在Firestore中。我在Firestore中也有一些配置文件字段,这些字段决定了用户权限级别。如果用户包含任何会影响其权限级别的更改,我想拒绝用户对其Firestore个人资料进行写入或更新的功能。

用户的Firestore文档中用于其个人资料的示例字段

permissionLevel: 1
favoriteColor: red

文档ID与用户的身份验证uid相同(因为只有用户才可以读取/写入/更新其个人资料)。

如果用户的Firestore更新或写入包含PermissionLevel字段,我想拒绝更新或写入,以防止用户更改自己的权限级别。

当前的Firestore规则

当我在模拟器中构建对象以测试包括或不包括称为“ permissionLevel”的字段时,此方法工作正常。但这拒绝了来自客户端Web SDK的所有更新/写入请求。

service cloud.firestore {
  match /databases/{database}/documents {

    // Deny all access by default
    match /{document=**} {
      allow read, write: if false;
    }

    // Allow users to read, write, and update their own profiles only
    match /users/{userId} {
        // Allow users to read their own profile
      allow read: if request.auth.uid == userId;

      // Allow users to write / update their own profile as long as no "permissionLevel" field is trying to be added or updated
      allow write, update: if request.auth.uid == userId &&
            request.resource.data.keys().hasAny(["permissionLevel"]) == false;
    }
  }
}

客户端功能

例如,此功能尝试通过更新Firestore字段来仅在用户上次活动时进行更新。这将返回错误Error updating user refresh time: Error: Missing or insufficient permissions.

/**
 * Update User Last Active
 * Updates the user's firestore profile with their "last active" time
 * @param {object} user is the user object from the login auth state
 * @returns {*} dispatched action
 */
export const updateLastActive = (user) => {
    return (dispatch) => {
        firestore().settings({/* your settings... */ timestampsInSnapshots: true});

        // Initialize Cloud Firestore through Firebase
        var db = firestore();

        // Get the user's ID from the user retrieved user object
        var uid = firebaseAuth().currentUser["uid"];

        // Get last activity time (last time token issued to user)
        user.getIdTokenResult()
        .then(res => {
            let lastActive = res["issuedAtTime"];

            // Get reference to this user's profile in firestore
            var userRef = db.collection("users").doc(uid);

            // Make the firestore update of the user's profile
            console.log("Firestore write (updating last active)");
            return userRef.update({
                "lastActive": lastActive
            })
        })
        .then(() => {
            // console.log("User lastActive time successfully updated.");
        })
        .catch(err => {
            console.log("Error updating user refresh time: ", err);
        })
    }
}

如果我从Firestore规则中删除此行,则此功能也可以正常工作。我看不到他们之间有什么关系,以及为什么它在模拟器中可以正常工作。 request.resource.data.keys().hasAny(["permissionLevel"]) == false;

2 个答案:

答案 0 :(得分:2)

好的,我找到了一个解决方案,但我在firebase文档中找不到任何官方文档来支持此方法。它在模拟中不起作用,但可以使用IRL。

替换(来自上面的示例)

request.resource.data.keys().hasAny(["permissionLevel"]) == false

此操作

!("permissionLevel" in request.writeFields);

完整工作权限示例

service cloud.firestore {
  match /databases/{database}/documents {

    // Deny all access by default
    match /{document=**} {
      allow read, write: if false;
    }

    // Allow users to read, write, and update their own profiles only
    match /users/{userId} {
        // Allow users to read their own profile
      allow read: if request.auth.uid == userId;

      // Allow users to write / update their own profile as long as no "admin" field is trying to be added or created
      allow write, update: if request.auth.uid == userId &&
            !("permissionLevel" in request.writeFields);
    }
  }
}

只要密钥permissionLevel存在于Firestore请求映射对象中,此操作就可以成功地阻止更新或写入操作,并允许其他预期的更新。

文档帮助

Firestore Security Docs Index列出了“ rules.firestore.Request#writeFields”-但是,单击它时,结果页面甚至根本没有提到“ writeFields”。

我将基于rules.Map的原则用于

x中的k 检查密钥x是否存在于地图x

答案 1 :(得分:0)

您可以考虑添加权限级别的另外两件事:

  1. 为用户创建一个单独的子集合,该子集合将包含一个文档,其中包含您不希望用户更改的信息。可以为该文档提供不同的权限控制。
  2. 使用具有自定义声明的Firebase身份验证令牌。奖励:此方法不会触发对数据库的读取。我建议您查看以下Firecast:

Add the Firebase Admin SDK to Your Server指南也很有帮助。

我是开发游戏的新手,但这是我使用ItelliJ IDEA手动创建自定义声明的方法:

import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import com.google.firebase.auth.FirebaseAuth;
import com.google.firebase.auth.FirebaseAuthException;
import com.google.firebase.auth.UserRecord;

import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

public class App {

    //This will be the UID of the user we modify
    private static final String UID = "[uid of the user you want to modify]"; 

    //Different Roles
    private static final int USER_ROLE_VALUE_BASIC = 0; //Lowest Level - new user
    private static final int USER_ROLE_VALUE_COMMENTER = 1; 
    private static final int USER_ROLE_VALUE_EDITOR = 2; 
    private static final int USER_ROLE_VALUE_MODERATOR = 3; 
    private static final int USER_ROLE_VALUE_SUPERIOR = 4; 
    private static final int USER_ROLE_VALUE_ADMIN = 9;

    private static final String FIELD_ROLE = "role";

    //Used to Toggle Different Tasks - Currently only one task
    private static final boolean SET_PRIVILEGES = true; //true to set User Role

    //The privilege level being assigned to the uid.
    private static final int SET_PRIVILEGES_USER_ROLE = USER_ROLE_VALUE_BASIC;

    public static void main(String[] args) throws IOException {

        // See https://firebase.google.com/docs/admin/setup for setting this up
        FileInputStream serviceAccount = new FileInputStream("./ServiceAccountKey.json");

        FirebaseOptions options = new FirebaseOptions.Builder()
                .setCredentials(GoogleCredentials.fromStream(serviceAccount))
                .setDatabaseUrl("https://[YOUR_DATABASE_NAME].firebaseio.com")
                .build();

        FirebaseApp.initializeApp(options);

        // Set privilege on the user corresponding to uid.
        if (SET_PRIVILEGES){
            Map<String, Object> claims = new HashMap<>();
            claims.put(FIELD_ROLE, SET_PRIVILEGES_USER_ROLE);
            try{
                // The new custom claims will propagate to the user's ID token the
                // next time a new one is issued.
                FirebaseAuth.getInstance().setCustomUserClaims(UID, claims);

                // Lookup the user associated with the specified uid.
                UserRecord user = FirebaseAuth.getInstance().getUser(
                System.out.println(user.getCustomClaims().get(FIELD_ROLE));

            }catch (FirebaseAuthException e){
                System.out.println("FirebaseAuthException caught: " + e);
            }
        }

    }
}

build.gradle依赖项当前为:

implementation 'com.google.firebase:firebase-admin:6.7.0'