We need to verify the user's role inside the firebase rules. A user can have one of the following roles: SUPPORT, ADMIN, MODERATOR.
The User object contains a roles collection, so that we are able to put some meta data on the respective role:
User Object
I tried to use this collection inside my firestore rules but I did not yet succeeded:
function getUserRoles(userId) {
return get(/databases/$(database)/documents/users/$(userId)/roles);
}
function hasRole(userId, role) {
return getUserRoles(userId) in role;
}
Unfortunately I was not able to find something in the docs how I can check if the role list contains the requeted role as document ID.
get() is only able to fetch a single document, not an entire collection. So, what you're trying right now is just not possible. Also bear in mind that you can only get() up to 10 documents per request.
Consider rewriting hasRole like this in order to check if the user's roles collection contains a document with the named permission:
function hasRole(userId, role) {
return exists(/databases/$(database)/documents/users/$(userId)/roles/$(role))
}
This assumes role is the string name of the role, for example, "SUPPORT". You will have to call this function for each individual role you want to check.
If you need to check the contents of the document as well, you will have to get() it instead, and look at the fields of the document..
Related
I want to secure a collection of accounts documents with a field access.users that contains an array of user DocumentReferences which are allowed to access an account document in the collection.
accounts.access.users = [ //array of user document references ]
In my query (JS client library) I am setting the query:
db.collection('accounts').where('access.users', 'array-contains', userRef)
To secure the data, I want to write a rule:
function userHasAccountAccess () {
let user = getUser(); // returns users document reference based on auth uid
// - here - need to check that the users document reference was requested by the query ie - that `request.query` contains the `access.users` field and that value of this filter in an array/list which includes a reference to the users' document
}
match /accounts/{docId} {
list: if userHasAccountAccess();
}
... but it seems from the docs that the only properties available on a query are limit, offset and orderBy, so then I am unable to test or secure this way.
So how are others securing their data in this type of access role ACL scenario for LIST type requests?
So after some digging, I found the answer.
Where posting a query (LIST) request as so:
db.collection('accounts').where('access.users', 'array-contains', userRef).limit(5)
... it seems that limit, orderBy and offset become properties of request.query in the security rules, but the where filters become properties of resource.data.
This is confusing because a) resource.data is usually a map of document data being posted (ie when saving records) and b) the docs describe resource.data as such.
I don't know if i am able to ask this question properly, but here it goes...
I have a firebase collection, which has the bool field 'isAdmin'. So now when users are logging in, i want to set a rule to check whether this field is 'true', before allowing them access to another collection i got.
Is there a way to do it?
It sounds like you want to define access based on a value in a document about the current user. For an example of that, have a look at the documentation on attribute based access control. Based on the example there:
service cloud.firestore {
match /databases/{database}/documents {
// For attribute-based access control, Check a boolean `admin` attribute
allow write:
if get(/databases/$(database)/documents/users/$(request.auth.uid)).data.isAdmin
== true;
allow read: true;
}
}
So here each user has a document in the users collection with the document ID the same as their UID, and the rule then gets that document and checks for the specific value you want.
I have a collection of users, and I have a separate collection of usernames. In my collection usernames I store different usernames as doc_ids. That is, under collection usernames I can have doc_ids as first, second, third, and so on. Under each doc_id I store the following info:
{
ownerId: id,
dateUpdated: someDate
}
When I change some user's username, I execute a batch query, where I first delete the oldUsername doc, and then insert the newUsername doc with the appropriate fields. My question is regarding one of the security rules, related to the usernames collection. Do I need to check, if I already have such username (that is such doc_id). Do I need the following rule:
match /usernames/{username} {
allow create: if !exists(/databases/$(database)/documents/usernames/$(username))
}
I think this rule, is redundant since I am enforcing the uniqueness of collection ids, but I already saw it on a few other posts, so I wanted to check other people's opinions.
Yup, that rule does nothing as the create will only be triggered when the document doesn't exist yet. If the document already exists, its .update will be triggered.
This type of check is common in a .write, but not needed when you're using the more granular .create.
I'm making a structure that looks something like this:
users
user1
user2
user3
group
group1
group2
group3
subcollections1
subdoc11
subdoc12
subcollections2
subdoc21
subdoc22
So explaining it, I have a users collection and group collection. All users must be part of one and only one group. A group has a 'members' field which I check if the user is a member or not.
So my rules are something like this for now.
match /groups/{document=**} {
allow read, write: if resource.data.users[request.auth.uid] != null;
}
My question is, would it accurately check if user is in group1->doc-'members' even when writing to a subcollection or would it check in group1->doc->subcollection->doc-'members' in that case?
Because the members list is only in the groups collection but not in the subcollection so the second situation is not wanted.
Although I haven't actually tried, the resource.data variable is a refernce to the document being accessed. So if the path accesses the document /groups/group3/subcollections1/subdoc11 that would be the document referenced by resource.data. Therefore I think you're rule won't work as expected.
Regarding how to implement the desired behavior, note that it's best to define Firestore rules at the most granular level. This would prevent to accidentally grant access when a stricter rule ought to be applied but instead a wider rule is applied. I would suggest something alike this:
match /groups/{groupId} {
allow read, write: if resource.data.users[request.auth.uid] != null;
}
match /groups/{groupId}/{subcollection}/{documentId} {
allow read, write: if get(/databases/$(database)/documents/$(groupId)).data.users[request.auth.uid] != null
}
For further reference check this pages about how rules work and sample rules.
I'm facing insufficient permissions for this firestore security check.
service cloud.firestore
{
match /databases/{database}/documents
{
match /events/{eventID}
{
allow read:
if get(/databases/$(database)/documents/events/$(eventID)/authorizations/$(request.auth.uid)).data.EVENT_READ == true;
}
}
}
the get document is hardcoded in the firestore database and the simulator returns true but for the actual query returns insufficient privileges.
I tested and moved the authorizations subcollection to the same level as users collection and it works. Am i missing out anything?
Additional testing: Reading the document directly does not result in insufficient privileges. I'm testing to see if it's an issue with listing but to my knowledge read should cover both get and list in firestore security rules.
Update: Listing seems to be the issue here. I tried to list the entire collection with only one document and it results in the insufficient privileges.
Works:
this.angularFirestore.collection('events').doc(eventID).valueChanges();
Doesn't work (updated):
this.angularFirestore.collection('events', query => query.where('admins', 'array-contains', auth.uid)).valueChanges()
My firestore database:
/events/event1_id
- field 1: some string
- field 2: some string
- admins: array of uid strings
/authorizations/<uid> #uid for $(request.auth.uid)
- EVENT_READ: true
Update 2: Updated the doesn't work query string which I tried out. It is intriguing that if i move the /authorizations sub collection out to be the same level as /events collections, the query will not fail.
Your first query works because it's accessing the events collection with a specific document. That specific document meets the rules criteria, because you've arranged for the get() to allow it.
Your second query doesn't work because it's attempting to get all of the documents in events collection. Your rule does not specifically allow that. It looks like you expect your rule to filter out the events that aren't allowed access based on the contents an unknown number of other documents. You need to be aware that security rules are not filters. Please click through to the documentation and read that section. The client must only request documents that are known to be readable according to rules. It can't depend on rules to filter documents that are not allowed.
If you want to be able to query for all events that the current user has access to, your current database structure will not work. You will need to put all the relevant information in the events collection itself. This means you should consider something like putting the UID of each user that's allowed to read the event in the document itself, then filter on that field. Or have some other collection that you can query in this way.