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

问题描述 投票:0回答:2

我有用户配置文件数据存储在Firestore中。我在Firestore中也有一些配置文件字段,用于指定用户权限级别。我想拒绝用户编写或更新其Firestore配置文件的能力,如果它们包含任何会影响其权限级别的更改。

用户的firestore doc中用于其配置文件的示例字段

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;

firebase google-cloud-firestore firebase-security-rules
2个回答
3
投票

好的,我找到了一个解决方案,但我在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的原则

k in x检查映射x中是否存在密钥k


0
投票

您可以考虑为添加权限级别做的其他两件事:

  1. 为用户创建一个单独的子集合,然后包含一个文档,其中包含您不希望用户能够更改的信息。该文档可以被赋予不同的权限控制。
  2. 将Firebase验证令牌与自定义声明一起使用。额外奖励:此方法不会触发对数据库的读取。我建议看一下这些Firecast: Controlling Data Access Using Firebase Auth Custom Claims Minting Custom Tokens with the Admin SDK for Java

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'
© www.soinside.com 2019 - 2024. All rights reserved.