I am struggling to access document references in the firestore rules. My database looks like this. (Simplified for brevity):
curriculum
session1
roles
admin
--- canEditContent
user
--- canEditContent
users
userid
--- role
roles/admin <document reference>
I want to access the admin permissions based on the document reference.
I have tried several ways however can't seem to get anywhere. This is my code so far
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function isSignedIn() {
return request.auth != null;
}
function getUser() {
return get(/databases/$(database)/documents/users/$(request.auth.uid));
}
function getUserRole() {
let role = get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role;
return get(role);
}
match /curriculum/{curriculum} {
allow write: if isSignedIn() && getUserRole().data.canEditContent;
}
match /users/{userId} {
allow read, update, delete, write: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
}
}
I have tried many ways and can't seem to solve it.
Thanks in advance
EDIT
Added screenshots below of collections
Users collection showing role as a document ref to a role document in the roles collection.
Roles collection
I can see two issues in your rules:
get needs the full document path, so your function getUserRole wont work. Try this instead:
function getUserRole() {
let role = getUser().data.role;
return get(path("/databases/" + database + "/documents/" + role));
}
Your rule uses the role canEditContent but the data you show uses editContent, is that on purpose?
As already mentioned please provide the complete set of data & query & rules, here we cant see the query you are using. Also note that you can use the Firestore emulator to get information on what rule is failing and where.
Related
I have two root collections, users and workspaces. A user document has a workspaces array with the workspace document IDs the user is a part of. Below is the security rule I try to use. I had two approaches, one is to get the user document from the root collection and check its workspaces array for the workspaceId the other is to check for existance of the userId in the members subcollection. Both end up throwing the same error: FirebaseError: Null value error. for 'list' # L15
If I separate the allow read into allow get and allow list and just write true for list it fixes the error but obviously I want to restrict the access so users can only get their own workspaces (where they are members).
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /workspaces/{workspaceId} {
allow read: if isLoggedIn() && userIsInWorkspace(workspaceId);
}
function isLoggedIn() {
return request.auth != null && request.auth.uid != null;
}
function userIsInWorkspace(workspaceId) {
return exists(/databases/$(database)/documents/workspaces/$(workspaceId)/members/$(request.auth.uid));
}
}
}
Approach with get:
function userIsInWorkspace(workspaceId) {
let workspacesOfUser = get(/databases/$(database)/documents/users/$(request.auth.uid)).data.workspaces;
return workspaceId in workspacesOfUser;
}
UPDATE:
I tried implementing it with custom claims like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read: if isLoggedIn() && request.auth.uid == userId;
}
match /workspaces/{workspaceId}/{document=**} {
allow read, write: if isLoggedIn() && userIsInWorkspace(workspaceId);
}
function isLoggedIn() {
return request.auth != null && request.auth.uid != null;
}
function userIsInWorkspace(workspaceId) {
return workspaceId in request.auth.token.workspaces;
}
}
}
So my concept was that I have users and workspaces as rootcollections and workspaces has subcollections such as teams and reports and so on. I still get the same null value error. When this comes up is in the above mentioned error so list requests does the error. My use case is that when you are logged out from the app the workspace slug that is stored on every workspace entry in firestore gets added az a query parameter so the user can be redirected back to the exact workspace. To make this happen I do a list request so basically I query the workspaces collection where the slug is the given slug from the url.
const workspaceSnapshot = await db
.collection('workspaces')
.where('slug', '==', this.$route.query.slug)
.limit(1)
.get()
This request creates my error but from this I cannot make out anything. I would suppose that when I give a condition for reads and writes that includes get and list as well.
Queries are case-sensitive. You said you had two root collections, namely Users and Workspaces, but you are querying against users and workspaces. This will not yield results. Try changing your query:
function userIsInWorkspace(workspaceId) {
let workspacesOfUser = get(/databases/$(database)/documents/Users/$(request.auth.uid)).data.workspaces;
return workspaceId in workspacesOfUser;
}
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.
I've added rules to my Cloud Firestore database but still receive these issue messages:
any user can read your entire database
any user can write to your entire database
See the rules below.
Indeed, any user can read and write into "users" collection, otherwise I won't be able to log in/register a user. How to solve the issue?
Thanks
The problem with your current rules is that it allows any user to read/write any other user's user document. You need to restrict it a bit more to look at the userId of the documents. Something like this
service cloud.firestore {
match /databases/{database}/documents {
// Ensure the user is authenticated
function userIsAuthenticated() {
return request.auth != null && request.auth.uid != null
}
// Ensure the user owns the existing resource
function userOwnsResource() {
return userIsAuthenticated() && request.auth.uid == resource.data.userId
}
// Ensure the user owns the new resource
function userOwnsNewResource() {
return userIsAuthenticated() && request.auth.uid == request.resource.data.userId
}
match /users/{userId=**} {
allow read, update, delete: if userOwnsResource();
allow create: if userOwnsNewResource();
}
}
}
userOwnsResource checks that the user can access an existing resource. userOwnsNewResource checks that they can create a new document for their userId. You can obviously reuse these functions in other rules which is handy.
Suppose there are the following rules in place
service cloud.firestore {
match /databases/{database}/documents {
function roomAccess() {
return resource.data.allow_anonymous == true || (request.auth.uid == resource.data.uid);
}
function writeOnlyOwner() {
return request.auth.uid == resource.data.uid;
}
match /rooms/{room_id} {
allow read: if roomAccess();
allow write: if writeOnlyOwner();
allow create: if request.auth.uid != null;
}
}
}
Also, this is what an document in the collection rooms looks like
Question
Is there any way to query the specific room where the access_key is set to some value?
db.collection('rooms')
.where('access_key', '==', access_key)
.get()
It is important to note that i'm querying as the creator of the room (and the room.uid with match my uid
Nonetheless, it fails on access rights.
And the documentation doesn't seem to describe such behavior.
As far as I understood, the query fails because there might be some rooms, with this access_key, for which roomAccess() would fail.
But I can't seem to find a way around it.
My problem was that I should have been checking for
resource.data.allow_anonymous == true || request.auth.uid != null since I wanted to get the room either if the user is authenticated or it is public.
In this case, all the documents retrieved by access_key query match the security contraints
I am trying to base a security rule on a reference to another object.
I have a collection of users and collection of roles. A user object has a field called "role" that is a reference to a particular document in the roles collection.
users
id
name
role <-- reference to particular role
roles
id
name
isSuperUser
The goal here is to allow a user with a particular role (the role with isSuperUser == true) to edit any other role or it's sub-collections;
Here are my rules that I would have thought would have worked:
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId=**} {
allow read, write: if request.auth.uid == userId;
}
match /roles/{roleId=**} {
function isSuperUser() {
return get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role.isSuperuser == true;
}
allow read: if request.auth.uid != null;
allow write: if isSuperUser();
}
}
I have confirmed the following works, but it's not really that useful...
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role != null;
If there is a better way to accomplish role base security, I am all ears.
The lack of any debugging tools makes this quite frustrating.
I know it's been a while since the original question but I've had a similar issue and I hope this could help you or others.
Your condition is:
get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role.isSuperuser == true;
But role is a reference, which (apparently) means you need to get it as well. Try this:
get(get(/databases/$(database)/documents/users/$(request.auth.uid)).data.role).data.isSuperuser == true;
Have you tried to move the wildcard to a nested path?
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId} {
allow read, write: if request.auth.uid == userId;
match /{role=**} {
allow read: if request.auth.uid != null;
allow write: if isSuperUser();
}
}
}
}