have one application where we have multiple communities and user that have roles to write message to it.
Each community has users under its own wing ({communityId}/meta_users/{uid}) I have written a function that access the roles under for user, I tried to go through the document but could not file any help.
I simulation I check that userRoleLevel will not allow to read even if the user has a role('admin') assigned to him.
please help me to correct it. I have pasted snippet of the rule below. let me know if need more info
function userRoleLevel(community){
return exists(/databases/$(database)/documents/communities/$(community)/meta_users/$(request.auth.uid)/roles/level)
&& get(/databases/$(database)/documents/communities/$(community)/meta_users/$(request.auth.uid)/roles/level).data.role;
}
service cloud.firestore {
match /databases/{database}/documents {
// All read is allowed for now.
match /{document=**} {
allow read;
}
match /communities/{community} {
allow write: if owner();
match /{category}/{message}{
allow write: if loggedIn()
&& (userRoleLevel(community) in ['owner', 'admin', 'writer']);
}
You have the function userRoleLevel() returning a Boolean instead of a String containing the role. You won't be able to use userRoleLevel(community) in ['owner', 'admin', 'writer'] unless you do the following.
function userRoleLevel(community){
if( exists(/databases/$(database)/documents/communities/$(community)/meta_users/$(request.auth.uid)/roles/level) ) {
return get(/databases/$(database)/documents/communities/$(community)/meta_users/$(request.auth.uid)/roles/level).data.role;
} else {
return false; // return something that won't match your roles
}
}
Finally, I found the solution to this problem.
Thing is the location of the function it is outside the scope of the database schema and it is using segment variable for the database scope.
The solution is to move this function inside the database schema.
service cloud.firestore {
match /databases/{database}/documents {
// All read is allowed for now.
match /{document=**} {
allow read;
}
// function should be located here <--------
function userRoleLevel(community){
return get(/databases/$(database)/documents/communities/$(community)/meta_users/$(request.auth.uid)/roles/level).data.role;
}
...
}
Hope, this helps others too.
Related
So i'm making an app with a friends system and trying to set up rules for firebase to handle reads & writes if the users is friends or not.
I'm very stuck at a particular call that i just don't have any idea on how to make.
My firestore is structured as follows:
users/userUID/places/documentsofplaces
each userdocument have some fields of the usual information, name, username, etc. and an array of friendsUID.
I have managed to get the first part down, that a user can only read and write if it's UID matches the documentUID, and looking in the friendslist a friend can only read but not write.
The next part, in the places collection, just throws me off, how can i get the parent document and compare the userUID to a UID in the friendslist?
This is what i have so far:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
// Allow write and read if user, and read if friend
allow write: if isUser(userId);
allow read: if isUser(userId) || isFriend();
function isUser(userId) {
return (request.auth.uid == userId);
}
function isFriend() {
return (request.auth.uid in resource.data.friendsList);
}
}
match /users/{userId}/places/{documents} {
allow write: if isUser(userId);
allow read: if isUser(userId) || isFriend(userId);
function isUser(userId) {
return (request.auth.uid == userId);
}
function isFriend(userId) {
return (request.auth.uid in get(/databases/$(database)/documents/users/userId.resource.data.friendsList));
}
}
}
}
Any help is greatly appreciated!
Your document get() should look more like this:
get(/databases/$(database)/documents/users/$(userId)).data.friendsList
You have to use variables with $(var) notation inside the document path. get() returns a Resoruce object with a data property. I suggest reading over the documentation for accessing other documents for more details.
Consider the following :
A Firebase auth user has an ACL (access control list)
user.roles = [ a, b, c ]
Each record in a collection must be secured with a list of roles that are permitted to perform read/write operations on the document:
resource.access.roles = [ c, d, e ]
If there is an intersection between the two arrays, the operation should be permitted.
function userHasAccess (resource) {
return getUser().roles.hasAny(resource.data.access.roles)
}
match /{collection}/{id} {
allow read: if userHasAccess(resource)
}
Note: in the Firebase console, getUser().roles is an array and resource.data.access.roles is an array and there IS an intersection between them. Consequently, IN THE CONSOLE, the request is permitted.
Question: how to query this collection from the client whilst satisfying the rule.
What I had expected is that this would work.
ref
.collection(collection)
.where('access.roles', 'array-contains-any', user.roles)
... would satisfy the rule and allow the read operation, but it does not. It throws standard permissions error, even though I can read the ref.collection(collection).doc(id) just fine.
FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
Would appreciate an example or better a ref to the docs where it shows how to formulate a query in the client that satisfies the hasAny() rule that the framework provides.
** EDIT: ** Added full details per Doug's recommendation:
The database is structured as follows:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isAuthUser(userId) {
return request.auth.uid == userId;
}
function isTenantUser(env, tenantId) {
return get(/databases/$(database)/documents/envs/$(env)/usersMap/$(request.auth.uid)).data.tenant == tenantId
}
function getUser(env, tenantId) {
return get(/databases/$(database)/documents/envs/$(env)/tenants/$(tenantId)/users/$(request.auth.uid)).data
}
function userHasAccess (env, tenantId, resource) {
return resource.data.access.role in getUser(env, tenantId).roles
}
// note if set env here, have to pass through all fns to match path
match /envs/{env} {
match /usersMap/{userId} {
allow read: if isAuthUser(userId)
}
match /tenants/{tenantId} {
allow read: if isTenantUser(env, tenantId)
allow update: if getUser(env, tenantId).account.isAdmin
match /lists/{settingId} {
allow read, update: if isTenantUser(env, tenantId)
}
match /settings/{settingId} {
allow read: if true
allow update: if getUser(env, tenantId).account.isAdmin
}
// todo: users can't change admin flag or email
match /users/{userId} {
allow read: if true
allow create: if getUser(env, tenantId).account.isAdmin
allow update: if getUser(env, tenantId).account.isAdmin || request.auth.uid == userId
}
// todo: permissions on read/update
match /{collection}/{id} {
allow read, update, delete: if userHasAccess(env, tenantId, resource)
allow create: if true
}
}
}
}
}
Based on this, I want to secure access to the following url:
/databases/$(database)/documents/envs/$(env)/tenants/$(tenantId)/{collection}/{documentId}
eg:
/databases/$(database)/documents/envs/devleopment/tenants/shalom-shul-20AZ/cases/case-one
with ACLs where the user record is:
and the case record is:
Under these conditions, the rules evaluate as allowed in the firebase console, but the client-side query as posted returns denied error (even though i can read any individual doc just fine).
I'm trying to prevent the creation of a new document if there is one that currently exists in the collection with two id fields with the same values as the incoming data.
Right now, I'm trying to attempt this with Firestore rules.
I've found way no way in the simulator to actually do this as I need to test the incoming data against every document in the collection.
I'm not quite sure what I'm missing here...
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read;
}
match /someCollection/{someCollectionID} {
allow write: if existingData().someValue != incomingData().someValue &&
existingData().anotherValue != incomingData().anotherValue
}
}
function existingData() {
return resource.data
}
function incomingData() {
return request.resource.data
}
}
I have the next firestore rule:
service cloud.firestore {
match /databases/{database}/documents {
match /{usuarios=**} {
allow write: if get(/usuarios/$(request.auth.uid)).data.level == 0 || get(/usuarios/$(request.auth.uid)).level == 0;
}
}
}
And I get "permission-denied" when I tried this query:
firebase.firestore().collection('usuarios').doc(uid).set({...});
This is my DB actually:
pd: I want to add info about new users in my db (by his UID)
That's going to be very expensive and inefficient since you pay per get request. Instead, you should write your rules as if you're defining the database's structure. Here's what I mean:
service cloud.firestore {
match /databases/{database}/documents {
match /usuarios/{uid} {
// Give write access to any field within the document who's id is the uid
// Add other validation as needed.
allow write: if uid == request.auth.uid
// Give admin access to anyone who's `level == 0`
// Make sure to add the `databases...` boilerplate
|| get(/databases/$(database)/documents/usuarios/$(request.auth.uid)).data.level == 0;
// If necessary, give access to sub-collections.
// (We can't do it for uid or it will become a path object that we can't use as a string.)
/{sub=**} {
allow write: if uid == request.auth.uid;
}
}
}
}
If you'd like to see fully flushed-out rules that are doing something very similar, I have an open source exemple. Cheers!
Using Firestore, I'm trying to figure out how to restrict new user accounts to those matching a whitelist of emails. The problem is, I don't even know if it's even possible to use security rules to prevent the creation of new user accounts. I've attempted a lot of combinations. This was my best attempt:
service cloud.firestore {
match /users/{userId} {
allow read: if true;
allow create, update: if
request.resource.data.email == "bbbbb#asdfljflsaj.com";
}
}
I've also tried:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if true;
allow create, update: if
request.resource.data.email == "bbbbb#asdfljflsaj.com";
}
}
}
And:
service cloud.firestore {
match /users/{userId} {
allow read: if true;
allow create, update: if
request.auth.email == "bbbbb#asdfljflsaj.com";
}
}
I'm calling 'createUserWithEmailAndPassword' to create the user and keep hoping one of the variations I try is successful, but to no avail at this point in time. I'm starting to wonder if it's even possible. Any help would be greatly appreciated!