Setup:
My top collection is named users
Each user is named for their unique ID, uid
I want to make a rule so that no matter what document or sub-collection is being accessed, if it will compare uid to the name of current user in users to allow
Current attempt:
Note that this WORKS for top level documents, but as soon as I try to work with a sub-collection within that user, it fails
If it matters, there will be 7 named sub-collections that are always the same between users
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{user} {
function isSignedIn() {
return request.auth.uid != null;
}
allow read, write: if isSignedIn() && request.auth.uid == user
}
}
}
Any help would be appreciated. I think I need to add some ** somewhere?
You will want to use a recursive wildcard to match all documents in all subcollection under the top-level collection.
match /users/{user}/{everything=**} {
function isSignedIn() {
return request.auth.uid != null;
}
allow read, write: if isSignedIn() && request.auth.uid == user
}
In rules version 2, recursive wildcards match 0 or more path segments.
Related
I have Security rules like below in my Firestore database
just to be sure I want to ask here for best practice of security rules firestore
So I have collection of userData and communityPost
user data only can be access by content owner that create it (content owner can create and update it)
for community post I want everyone auth and non-auth user can read the data (public)
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// Allow only authenticated content owners access
match /userdata/{document} {
allow read, write: if request.auth != null && request.auth.uid == userId
}
match /communityPost/{document} {
allow read: if true;
}
}
}
is this correct rules ?? this is my first time dealing with collection based rules,
thanks for your time
You can check the properties within the document
match /userdata/{document} {
allow read, write: if request.auth != null && request.auth.uid == resource.data.owner
}
or you can match the document id to the user
You can check the properties within the document, notice the match path
match /userdata/{userID} {
allow read, write: if request.auth != null && request.auth.uid == userID
}
As for making it public, Firestore discourages purely open database so you need a few conditional statements
You can check the properties within the document
match /communityPost/{document} {
allow read: if resource.data.public == true;
}
I'm trying to construct rules that allow a user to access all sub-collections and documents of a given account, if they are listed as a user of that account.
This works perfectly for retrieving individual documents, event nested ones. However, it fails when trying to list documents in a sub-collection.
This does not appear to me to be an instance of "rules are not filters": my query should categorically pass for every possible item queried, as it's based on their root ancestor document.
I've also read here (although I couldn't find it in the documents) that list operations also fail if you try and perform a get() for each queried document. I don't believe my rules violate this, either, as the get() command only needs to be run once, and will categorically return the same document for each queried document, as, again, it's based on a common ancestor.
Is my reasoning on the above rules wrong, or is there something else I'm not doing right?
My rules are as follows:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function hasAccountPermission(request, path, permission) {
let accountId = path[0];
let account = get(/databases/$(database)/documents/accounts/$(accountId));
let role = account.data.users[request.auth.uid];
return (
request.auth != null
&& role != null
&& (permission == null || permission in account.data.roles[role])
);
}
match /users/{user} {
allow read, update, delete: if request.auth != null && request.auth.uid == user;
allow create: if request.auth != null;
}
match /accounts/{account=**} {
allow read: if hasAccountPermission(request, account, null)
allow write: if hasAccountPermission(request, account, 'all')
}
}
}
Example database is as follows:
/accounts/bobstuff
roles: {
admin: ['all']
}
users: {
bob: 'admin'
}
/docs/adocument
field: value
A get operation on /accounts/bobstuff/docs/adocument succeeds
A list operation on /accounts/bobstuff/docs/ fails
Ok. It turns out it was was violating the second principle - or at least, Firestore thought it was.
By changing the rules to capture the first path segment, instead of capturing the whole path, and extracting the first segment manually, it appears Firestore was able to determine that only a single get() would be required, and therefore, the rules were valid.
This required splitting the rule into {account} and {account}/{doc=**}. The side-effect of this is that I could also make the rules more efficient by avoiding a get() call for the root case.
Complete (working) rules:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function hasAccountPermission(request, account, permission) {
let role = account.data.users[request.auth.uid];
return (
request.auth != null
&& role != null
&& (permission == null || permission in account.data.roles[role])
);
}
match /users/{user} {
allow read, update, delete: if request.auth != null && request.auth.uid == user;
allow create: if request.auth != null;
}
match /accounts/{account} {
allow read: if hasAccountPermission(request, resource, null)
allow write: if hasAccountPermission(request, resource, 'all')
allow create: if request.auth != null
}
match /accounts/{account}/{doc=**} {
allow read: if hasAccountPermission(request, get(/databases/$(database)/documents/accounts/$(account)), null)
allow write: if hasAccountPermission(request, get(/databases/$(database)/documents/accounts/$(account)), 'all')
}
}
}
I am currently working on a a app and in that user needs to make a new account. Your Enters first name and last name then the app automatically suggest a username which is unique and it will be the document name of that user. I had set the firestore secutity rules as follows,
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if request.auth != null;
}
}
after user enters the username it checks that the username used or not before move to the next screen.
Future<bool> checkUsernameExist(String name)async{
bool usernameExistSate;
await firestore.collection('users').doc(name).get().then((docSnapShot){
if(docSnapShot.exists){
usernameExistSate = true;
}else{
usernameExistSate = false;
}
});
return usernameExistSate;
}
Currently above system works fine without any problem. But I have a problem, With the firebase security rules sets to below condition how users able to read the documents to check the similar document names are present?
allow read, write: if request.auth != null;
First, I would not use the usernames to store your data in firestore but the uid provided when you are authenicated with google auth. This will allow you much safer access to the database with security rules like this:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{userId}/{document=**} {
allow read, write, update, delete: if request.auth != null && request.auth.uid == userId;
allow create: if request.auth != null;
}
}
}
For your second problem I would just create a second collection in the root of the firebase project named for example usernames with all usernames stored in a big list so you can query them safely via the firebase API. For that to be possible you have to give the authenticated device access to this collection too via for example adding this under
match /users/...
match /usernames/{document=**} {
allow read, write, update, delete, create: if request.auth != null;
}
Of course then, you have to keep track of both lists when making changes. But this way an authenticated user has only access to his data and all usernames in the worst case.
I have a problem of understanding with the security rules of cloud firestore. I don't understand how to check a user's uid with a map where they can access their data.
Thank you in advance for your answers
Here is the rules i have tried
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
//TEST ONLY
//match /{document=**} {
// allow read, write;
//}
// match logged in user doc in users collection
match /users/users/{userId} {
allow create : if request.auth.uid != null;
allow read : if request.auth.uid == userId;
match /{anything=**} {
allow read, write : if request.auth.uid == userId;
}
}
}
}
And here an exemple of how i use Firestore :
// Collection reference
final CollectionReference _usersCollection = Firestore.instance.collection('users');
Stream<List<double>> get existingRecord {
return _usersCollection.document('users').snapshots()
.map(_existingRecordListFormSnapshot);
}
List<double> _existingRecordListFormSnapshot(DocumentSnapshot snapshot) {
...
double tips = snapshot.data['$uid']['data']['$year']['$month']['$week']['$day']['Tips'];
}
My problem is that I want only the user to have access to their data. For this I made a 'users' document which contains a 'users' map which contains all user data, named with their unique uid. And I am unable to set up the security rules so that only the user has access to his data.
The diagram looks like this:
users / users / [{userID}, {userID}, ...]
I don't know if I'm clear but I really can't find how to only allow the user to access their data in the users data map
Dont put all the users in the same document, instead you should have one document per user, (with the document's name equal to the Firestore uid for simpler rules).
Then your rule can simply be:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
// match logged in user doc in users collection
match /users/{userId} {
allow create : if request.auth.uid != null;
allow read : if request.auth.uid == userId;
match /{anything=**} {
allow read, write : if request.auth.uid == userId;
}
}
}
}
As I understand it, Firestore does not allow queries within fields of type array, which is a shame. Therefore, if you want to be able to query the contents of an array you have to set up a field as an object type and then set the fields like a map, this is called a nested map. I want to have a map where the key is the ID of another user. Therefore, the database structure is:
database
users
{userId}
friends
userId1: true
userId2: true
The 'userId1' and 'userId2' field names will vary depending on the userId of the person listed as a friend.
The question is, how do I write my security rule so I can find my documents (via my {userId}) and the documents of other users where my {userId} is a field in the 'friends' object of the other user's document?
I guess it needs to be something like..
match /users/{userId} {
allow read, update, delete: if resource.data.userId == request.auth.uid;
allow read: if resource.data.friends.{userId} == true;
}
But of course this does not work because you cannot seem to use the variable {userId} to name the field that you want to perform a test on. So, if this cannot be done, what is a way to search for documents and have my {userId} stored somehow in someone else's document?
Edit
Well, I think I have the rules determined (see below). However, when trying to test these rules I can't seem to write a Swift call to retrieve data based on that friends object. My Swift call is:
db.collection("users").whereField(FieldPath(["friends.\(userId)"]), isEqualTo: true)
So, my questions are:
Are the rules below correct?
How do I make a Swift call to find the people with a certain userId in the field name of an object type?
service cloud.firestore {
match /databases/{database}/documents {
match /users/{documentId} {
allow read, write: if isOwner();
allow read: if getFriend(request.auth.uid) == true;
function isOwner() {
return request.auth.uid == resource.auth.uid;
}
function getFriend(userId) {
return getUserData().friends[userId]
}
function getUserData() {
return get(/databases/$(database)/documents/rooms/{documentId}).data
}
}
}
}
I still have not resolved the problem of accessing fields in an object, but it is noted that my Security Rules where generally invalid. You cannot have multiple lines with the same rule type in it, you cannot have multiple lines with 'allow: read' for example. You must use && and ||. For example, the correct definition for the basic rules if you want to check two things are:
// Database rules
service cloud.firestore {
// Any Cloud Firestore database in the project.
match /databases/{database}/documents {
// Handle users
match /users/{documentId} {
// The owner can do anything, you can access public data
allow read: if (isOwner() && isEmailVerified()) || isPublic();
allow write: if isOwner() && isEmailVerified();
// Functions //
function isSignedIn() {
return request.auth != null;
}
function isOwner() {
return request.auth.uid == resource.data.userId;
}
function isPublic() {
return resource.data.userId == "public";
}
function isEmailVerified() {
return request.auth.token.email_verified
}
}
}
}