I have the following database collection '/player_profiles/{userId}'. I want to use the rule that only the request.auth.uid == userId unless they are only updating the field matches which is an array. I tried this, but it denied permissions
match /player_profiles/{userId}{
allow write: if userId == request.auth.uid;
allow update: if (request.resource.data.keys().hasOnly(["matches"]) && request.auth != null);
}
And here is the Flutter code that runs the update to add in the item to the matches array:
await DatabaseProvider()
.db
.collection(PLAYER_COLLECTION)
.document(widget.userProfile.userId)
.updateData({
'matches': FieldValue.arrayUnion([profile.userId])
});
Im new to firestore rules but i thought this would work
Was able to get the behavior I wanted with this:
match /player_profiles/{userId}{
allow write: if request.auth.uid == userId || (request.auth != null && request.resource.data.diff(resource.data).affectedKeys().hasOnly(["matches"]));
}
Looking at #Doug Stevenson's answer though I can add in making sure they only are allowed to put in their own userId into other peoples profiles.
request.resource.data.keys() always contains every field in the document being written, regardless of whether or not it's being updated. You should use the new MapDiff API instead to compare what's being written with what currently exists.
I think you will want to do something like this:
if
(request.resource.data.diff(request.resource.data).affectedKeys().hasOnly(["matches"]) &&
(request.resource.data.matches.toSet().difference(resource.data.matches) == [userId].toSet()) ||
request.auth == null;
I have not tested, but I think you get the idea. You will need to make liberal use of the linked API documentation for security rules to work with maps, sets, and lists effectively.
Related
Here are my firestore rules:
match /databases/{database}/documents {
match /customers/{customerId} {
allow write: if exists(/databases/$(database)/documents/$(request.resource.data.referred_by));
allow read: if true;
}
}
The following data does not pass this rule(I was able to create the data from client app by removing the exists(/databases/$(database)/documents/$(request.resource.data.referred_by)):
So how do we deal with the reference field so that I can validate whether this reference exists already or not?
EDIT:
The type of the referred_by field is reference.
It seems that its a limitation of firestore at this stage. We are not able to validate whether a reference exists or not.
I further did some digging using the rules playground. The request.resource.data.referred_by has a path child. So I used $(request.resource.data.referred_by.path). Still that did not work because the $(request.resource.data.referred_by.path) resolves to customers%2FB85CSuRJQZWUKt2jgxOy (url-encoded).
I don't know of a way to url decode this in firestore rules.
So at this time I am changing my data models to use referred_by as a 'string' instead of a reference.
I hope this thing is resolved in future by firestore team.
I'm trying to write a security rule for firestore DB and I'm having trouble for some reason. Does the syntax in the following code look wrong to anyone?
match /posts/{postType} {
allow read: if true;
allow write: if get(/databases/$(database)/documents/posts/$(postType)).data.creator_id ==
request.auth.uid;
}
I attached a screenshot of the path and the data structure. The problem I am getting is "get" is returning null.
Data & Path:
error:
You must change the rule.
Instead of:
allow write: if get(/databases/$(database)/documents/posts/$(postType)).data.creator_id == request.auth.uid;
You must write:
allow write: resource.data.creator_id == request.auth.uid;
request.resource.data.creator_id == request.auth.uid was the solution!
You're not using the simulator correctly. When asked to enter the path of the document to get, you must provide a path to an actual document. Do not type the wildcard from the match. The rules will determine the actual value of the wildcard at the time the rule is evaluated. If you enter "/posts/{postType}", you are telling it to literally load a document with the ID "{postType}", which doesn't exist.
I'm trying to implement the rule outlined here: https://stackoverflow.com/a/56047977/1604072
match /{path=**}/posts/{post} {
allow read: if request.auth.uid != null;
}
This is yielding this error: Invalid glob match expression. Glob matches
are permitted as the last segment in a match declaration path.
Is this the only/proper way to write this now?:
match /{path=**}{
match /collectionItems/{collectionItem} {
allow read: if request.auth.uid != null;
}
}
For future users, just found this bit of documentation. Not sure why I wasn’t able to find it originally:
Secure and query documents based on collection groups
In your security rules, you must explicitly allow collection group
queries by writing a rule for the collection group:
Make sure rules_version = '2'; is the first line of your ruleset. Collection group queries require the new recursive wildcard {name=**} behavior of security rules version 2.
Write a rule for you collection group using match /{path=**}/[COLLECTION_ID]/{doc}.
simply replace /{path=**}/ with /{prefix=**}/ and it should work
In the end it should look like this:
match /{prefix=**}/posts/{post} {
allow read: if request.auth.uid != null;
}
I have a set of Collections whose names all start with ABC and I want to write a single rule that applies to all of them regardless of what follows ABC. Something like:
match /ABC*/{anyid} {
allow read, write;
}
Is this possible? In the Rules Console there are no syntax errors highlighted, but the Simulator won't allow me to access the table with:
GET /ABC123/456
Any ideas?
As far as I know it is not currently possible to match on a partial collection (or document) name. It sounds like an interesting feature request though, so I recommend filing a feature request.
In the meantime, the only thing I can think of is matching all collections, and then testing the path through resource['__name__']:
match /53829635/{document} {
match /{col}/{doc} {
allow read: if resource['__name__'][5].matches('ABC.*')
}
}
The resource['__name__'] expression returns a Path, which can be indexes as an array to get the path segments. It has a form /databases/(default)/documents/collection/document, so the subcollection is at index 5. Since that is just a string, we can use matches on it. In this case I allow reading from any subcollection whose name starts with ABC.
Update: it turns out that you can also simply access the col wildcard, instead of looking up from the path. So this would work the same:
allow read: if col.matches('ABC.*')
I'm new to CKAN and encountered a problem with template helpers. Particularly in my case, I will have to invoke toolkit.get_action('group_list') in my own template helper. However, when I add the constraint like the following:
results = toolkit.get_action('group_list')(data_dict={'sort': 'package_count desc',
'type': 'MyType',
'all_fields': True})
The results that I get back is an empty list. If I remove the 'sort' constraint from the data_dict, I can get the results of list the groups with 'MyType'. I don't know what caused this problem, because when I followed ckan toolkit official examples, it works for without any problems. However, what I can think of is this customized group might have its own schema such that package_count can not be used as a sort key. Since there's no error message, I can't make further assumption.
I figured out my own problem. The implementation I've showed in my question is correct. However, the database is not being populated properly. Basically you have to populate some packages for the particular groups. If you have no packages within each groups this function will return an empty query result. With the advice of #DRead, I researched the source code of _group_or_org_list(). If you take a look at this piece of code:
if sort_info and sort_info[0][0] == 'package_count':
query = model.Session.query(model.Group.id,
model.Group.name,
sqlalchemy.func.count(model.Group.id))
query = query.filter(model.Member.group_id == model.Group.id) \
.filter(model.Member.table_id == model.Package.id) \
.filter(model.Member.table_name == 'package') \
.filter(model.Package.state == 'active')
else:
query = model.Session.query(model.Group.id,
model.Group.name)
If you don't have packages created for groups, filter(model.Member.table_id == model.Package.id) will filter all groups out.