If I have a collection in firebase with several documents inside and each document has a field called for example "role", is it possible to show a user only the documents that belong to him when the whole collection is fetched?
Yes. You can do this.
Let's assume the user has the role 'student', you can fetch all documents .where('role', isEqualTo: 'student').
NB: role could also be the userId (and you can fetch all documents where role is userId).
You can use the following firebase rules to prevent unauthorised
access.
match /document/{docs=**} {
allow write, read: if isAllowed();
}
function isAllowed() {
// request.resource.data is the data being fetched.
return request.resource.data.role == 'student';
}
function userRole() {
// returns the user role. Use this if the user role is stored in a document.
return get(/databases/$(database)/documents/users/$(userId)).data.role;
}
NB: your query must contain .where('role', isEqualTo: 'student') if not it will fail with insufficient permissions.
Related
I'm trying my best to set up a functionality in my application that will allow people who know a group's login and password to join it.
Each group has a document in the "Groups" collection, and each user has a document in the "Users" collection.
To keep the id and the password information, I have another collection named "AuthGroups", containing as many documents as there are groups, with two fields: "login" and "password". Each auth document has the same ID as the corresponding document the Groups collection.
So, here is my strategy:
When the user valid the login and password, a first query is sent to the database, to find a document with theses credentials in the "AuthGroups" collection.
If a document is found, its ID is used to do another query in the "Groups" collection to retrieve the group's data.
Queries could look like this:
var ID = await firestore.collection('AuthGroups')
.where('login', isEqualTo: login)
.where('password', isEqualTo: password)
.get()
.then((value) {
return value.docs.first.id;
});
var groupName = await firestore.collection('Groups')
.doc(id)
.get()
.then((value) {
return value.get('name');
});
Now, let's speak about firestore rules to make it secure...
To prevent someone malicious from seeing all documents in my "AuthGroup" collection. I told myself that my rules need to only allow queries containing both "login" and "password" fields. But I don't know how to do it right, and if it's even possible...
Same thing for the documents in the "Groups" collection: users can only get a document if they know its ID.
A solution could be to name my documents in my "AuthGroup" collection like "login + password", and store the group's ID in it. And in my rules, allow only list requests like that:
service cloud.firestore {
match /databases/{database}/documents {
match /AuthGroup/{organization} {
allow list: if request.auth != null;
}
}
}
I told myself that my rules need to only allow queries containing both "login" and "password" fields. But I don't know how to do it right, and if it's even possible.
It's not possible. You can't check for specific query parameters in security rules.
A solution could be to name my documents in my "AuthGroup" collection like "login + password", and store the group's ID in it. And in my rules, allow only list requests like that
Yes, that is a possible solution. Or you can hash a concatenation of login and password strings so you don't expose them in the document id or exceed the max length of that ID.
I am saving the below Data in the user's collection in firebase
{
"uid":"randomid",
"name":"name",
"number":"1234"
}
when I try to check if the user exists the below code works ok
const result = await firestore().collection('users').where('uid', '==', userid).get()
so can an authenticated user read the whole users' collections?
const result = await firestore().collection('users').get()
What security rules I can write to prevent users from reading a collection but only reading their info based on uid?
In security rules you can split the read access to get and list. So if you want the give access to each user to get only his own data you need to use the following rule (I assume each user document in the collection is the uid of this user):
match /users/{user} {
function isUserOwner() {
return request.auth.uid == user
}
allow get: if isUserOwner();
allow list: if false;
}
First you need to set the uid field to the UID of the user who created the document.
To get the current user id See documentation
const uid = user.uid;
To add the currently logged in User id as a field visit stack overflow example link for javascript
After adding UID you can use request.auth and resource.data variables to restrict read and write access for each document to the respective users. Consider a database that contains a collection of story documents. Have a look at below example
{
title: "A Great Story",
content: "Once upon a time...",
author: "some_auth_id",
published: false
}
You can use below security rule to restrict read and write access for each story to its author:
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /users/{storyid} {
// Only the authenticated user who authored the document can read or write
allow read, write: if request.auth != null && request.auth.uid == resource.data.author;
}
}
}
Note that the below query will fail for the above rule even if the current user actually is the author of every story document. The reason for this behavior is that when Cloud Firestore applies your security rules, it evaluates the query against its potential result set, not against the actual properties of documents in your database
// This query will fail
db.collection("stories").get()
The appropriate query for the above rule is
// This query will work
var user = firebase.auth().currentUser;
db.collection("stories").where("author", "==", user.uid).get()
For additional information on the above rules and query see official documentation
I want to delete all students in my Firestore database, to do this I used collection group but I had a problem with rules: I can't achieve to authorize read, delete & update permissions.
Code
Here is the dart code in Flutter to retrieve all students in any nested collections AND delete them:
FirebaseFirestore.instance
.collectionGroup('students')
.where('studentId', isEqualTo: studentId)
.get()
.then((querySnapshot) async {
for (var snapshot in querySnapshot.docs) {
await snapshot.reference.delete();
}
}
});
Rules
The rules I used but doesn't work because It seems resource.data.classId can't be accessed...
function isClassBelongToUser(classId) {
return classId in get(/databases/$(database)/documents/users/$(request.auth.uid)).data.classIds
}
match /{path=**}/students/{id} {
allow read, delete, update: if isSignedIn() && isClassBelongToUser(resource.data.classId); // TODO: resource.data.classId seems to not work
}
My database
classes / CLASS_ID / (students: collection, name: string, ...)
users / USER_ID / (classIds: array, firstName: string, ...)
Security rules don't filter data, but instead merely ensure that the operation you perform is authorized. See the documentation on rules are not filters.
Since your isClassBelongToUser check requires that the user exists in the classIds of a specific document, your query must ensure this condition is satisfied too. Since Firestore can only filter on values in the documents it returns, such a condition is unfortunately not possible.
You will have to adapt your data model to allow the use-case, for example by replicating the necessary information into the students document(s).
I am developing a web app on Firebase and Firestore, and here is what I am trying to do:
Users can upload their own posts, and set its visibility to public or private.
Any signed in user can see public posts, but only the users who are subscribing to the writer can view private posts. I am trying to write security rules for this.
Here is the database structure:
db.collection('posts').doc({postid})
//When a user writes a post, a new document is created. Includes the boolean 'public' field and the 'uid' field, which stores the writer's uid.
db.collection('subscription').doc({viewer_user_id})
//Once the logged-in user subscribes to another user, a document is created under the user's UID. The doc includes array of the UID of the users the viewer is subscribing.
Here are the descriptions of the relevant fields under each of the docs in 'posts' and 'subscription' collections:
posts/{postid}: {
'uid': Writer's uid (String)
'public': Boolean value to reflect visibility. If false, it is private.
}
subscription/{viewer's uid}: {
'subscription': Array of uids of the users the viewer is subscribing.
}
So, for the private documents, the basic idea is to look at the viewer's document in the subscription collection, get the data, and check whether the uid of the writer is included there. This will require some Javascript codes, but I don't know what the syntax would be in Firestore security rules, or is possible or not there to begin with.
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
function publicOrSubscribed() {
return request.auth.uid!=null&&(resource.data.public==true|| /*What could be the syntax here??*/)
}
match /posts/{postid} {
allow read: if publicOrSubscribed();
allow create: if request.auth.uid!=null;
allow update, delete: if request.auth.uid==resource.data.userid;
}
}
}
Any suggestions? If it is not possible in security rules, what might be the workaround? Thanks in advance!
I have a basic rule on my Firestore Cloud database. I want to give user cafes that user is in but i am always getting the error "Missing or insufficient permissions.". What is the thing that I miss?
service cloud.firestore {
match /databases/{db}/documents {
match /cafes{
allow read: if request.auth.uid != null;
match /{cafe}{
allow read: if get(/databases/$(db)/documents/cafes/$(cafe)/participants/$(request.auth.uid)) != null;
}
}
}
}
here is the code that i use to reach to documents
export class MyApp {
private rootPage:any;
// rootPage:any = HomePage;
constructor(platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen, afAuth: AngularFireAuth, afs: AngularFirestore) {
platform.ready().then(() => {
statusBar.styleDefault();
splashScreen.hide();
afAuth.auth.onIdTokenChanged(user=>{
if(user){
console.log(user.uid)
afs.collection('cafes').valueChanges().subscribe(console.log)
} else {
console.log('logged out')
}
})
});
}
}
One of the key sentences from the docs on security rules is
Every database request from a Cloud Firestore mobile/web client
library is evaluated against your security rules before reading or
writing any data.
and this should answer your question: You cannot query documents by their visibility for a specific user because this would require Firestore to read and evaluate every document.
If you read this rule
match /{cafe}{
allow read: if get(/databases/$(db)/documents/cafes/$(cafe)/participants/$(request.auth.uid)) != null;
}
as "Allow every user to view a cafe document if it contains his user id in the participants list" it becomes clear that Firestore needs to read the document to answer the question.
What you can try is allow read on all cafe documents and then query for those "the user is in" with the implication that users can read the other cafes as well.
You should take a look at this official guide for a concept on role based access on Firestore documents.
Update
I want to be more precise on the statement "You cannot query documents by their visibility for a specific user because this would require Firestore to read and evaluate every document."
You cannot query for all documents and expect to get those visible to the requesting user (like you do in afs.collection('cafes'), i. e. security rules are not filters.
What you can do is qualify (add a where clause) your query to only include the documents visible to the user. Firestore will match the qualified query against your security rules and if both match, fetch the desired results.