我有用户配置文件数据存储在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文档中找不到任何官方文档来支持这个。它在模拟中不起作用,但它适用于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
您可以考虑为添加权限级别做的其他两件事:
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'