Firebase Rules - How to check if user exists in sub document - ios

I am working on a Forum-like structure using Firebase Store / Firebase Rules. My structure is something like this:
Collection --- Document ------ Collection --- Document
Topic1 CreationDate UsersJoined UserUID1
Topic2 Title UserUID2
Topic3 UpdatedDate UserUID3
... ... ...
Basically, each Topic has a Collection of Users. My goal is to be able to write a security rule where only Users in the 'UsersJoined' can read/write to the corresponding Topic. This is what I have right now as my rules:
service cloud.firestore {
match /databases/{database}/documents {
match /Topics/{topicUID} {
allow read, create, update, delete: if exists(/databases/$(database)/documents/Topics/$(topicUID)/UsersJoined/$(request.auth.uid));
match /UsersJoined/{userUID=**} {
allow read, create, update, delete;
}
}
}
}
So when I use the built in Simulator, the read works just fine; however, when I request to read it via my code for IOS, it tells me that I don't have sufficient permission.
I've tried just doing allow read: if request.auth.uid != null;, and I am able to read. I am confident that the UserUID does exist within the UsersJoined collection.
I've also tried creating a "sister" collection where I store my User IDs in, so my structure looks like this:
Collection ----------- Document
MyTestUserCollection UserUID1
Topic1 UserUID2
Topic2 ...
...
I then used this rule: if exists(/databases/$(database)/documents/MyTestUserCollection/$(request.auth.uid)); and the read works as well, both on the simulator and IOS codes.
My problem is not being able to read when the User List is nested within the Topic. So my question is... by writing a rule that checks ("reads") data in a nested collection, am I violating the "allow read" rule (since technically it hasn't determined whether I can read yet)? Or am I over complicating things a bit and there is a better way to structure my Collections/Documents? Or am I just not writing the rule correctly?
I don't believe my code on IOS is the issue, but just in case this is what I'm doing to request to read from my database: (the user is logged in via Firebase Auth)
[[myFirestore collectionWithPath:#"Topics"]
getDocumentsWithCompletion:^(FIRQuerySnapshot *snapshot, NSError *error) {
if (error != nil) {
NSLog(#"Error getting documents: %#", error);
} else {
NSLog(#"Read it");
}
}];
Any help is greatly appreciated!

Your security rules allow a user to read a specific topic, if they are following that topic. Your code tries to read all topics, which your rules don't allow. So that explains why the server rejects the read operation.
This is easiest to remember by realizing that rules themselves don't filter data. They instead either allow a listener or not. And since your rules don't allow a listener on all of /Topics, that listener gets rejected.
One solution is to only read the specific topic that you are a follower of. To determine the topic(s), you might need to store a document with the user's list of topics, such as in a /Profiles collection. This is quite common on NoSQL databases: you're essentially storing both sides of the many-to-many relationship.
Alternatively you can try to validate the query, but I'm not quite sure if that can be made to work for your situation.

Related

Realm Swift: Question about Query-based public database

I’ve seen all around the documentation that Query-based sync is deprecated, so I’m wondering how should I got about my situation:
In my app (using Realm Cloud), I have a list of User objects with some information about each user, like their username. Upon user login (using Firebase), I need to check the whole User database to see if their username is unique. If I make this common realm using Full Sync, then all the users would synchronize and cache the whole database for each change right? How can I prevent that, if I only want the users to get a list of other users’ information at a certain point, without caching or re-synchronizing anything?
I know it's a possible duplicate of this question, but things have probably changed in four years.
The new MongoDB Realm gives you access to server level functions. This feature would allow you to query the list of existing users (for example) for a specific user name and return true if found or false if not (there are other options as well).
Check out the Functions documentation and there are some examples of how to call it from macOS/iOS in the Call a function section
I don't know the use case or what your objects look like but an example function to calculate a sum would like something like this. This sums the first two elements in the array and returns their result;
your_realm_app.functions.sum([1, 2]) { sum, error in
if let err = error {
print(err.localizedDescription)
return
}
if case let .double(x) = result {
print(x)
}
}

Firebase security rules for collections in collection

I am trying to set a firebase security rules for all collections in one collection with Firebase Firestore. I have a collection named Chats inside of it I have two collection one is thread and the other is users. All the documents ids are random.
This is how my Firebase Data Looks Like:
I am trying to set the security for all the chats & thread & users. Because chats includes the thread collection and the user collection but when setting the rule below: it does not work properly: Missing or insufficient permissions.
// Chats can be read & written by all users
match /Chats/{document} {
allow read, create, update, delete, write: if true
}
I also tried:
match /Chats/{document}/thread/{document1} {
allow read, create, update, delete, write: if true
}
match /Chats/{document}/users/{document2} {
allow read, create, update, delete, write: if true
}
But it did not work, so if someone can please help me with it.
Your current rules only matches documents in the /Chats collection:
match /Chats/{userId}
To make it also match the subcollections , it'd have to be:
match /Chats/{document=**} {
Also see the Firebase documentation on securing hierarchical data, specifically the section on recursive wildcards.

How to flag and report user in firebase firestore database?

I have application which have multiple users, one of the major thing left is to block and report users in firebase.
I am trying to look for the solution for the same by googling for it, but till now not any particular success.
I would like to know how I can achieve that. Please guide me for that,
and how the firestore security rules should be to achieve the same?
The typical approach is to have a collection that contains the blocked users, with one document for each blocked user and with the ID of that document being the UID of that user.
With that structure in place, your security rules can check for the existence of such a document and then block the user.
There's a great example of this in the blog post 7 tips on Firebase security rules and the Admin SDK (it's tip 7). The rules from there:
service cloud.firestore {
match /databases/{database}/documents {
function isBlackListed() {
return exists(/databases/$(database)/documents/blacklist/$(request.auth.uid))
}
// Collections are closed for reads and writes by default. This match block
// is included for clarity.
match /blacklist/{entry} {
allow read: if false;
allow write: if false;
}
match /posts/{postId} {
allow write: if !isBlackListed()
}
}
}

Allow Firebase Storage Bucket to be accessed only by some users

I have a main "folder" in my storage directory called users-projects and then I create folders for every users' projects. I want to allow the users to access only their projects and the projects they are invited in, like a Dropbox or Google Drive folder with collaborators.
In the Documentation they says:
Include group information (such as a group ID or list of authorized
uids) in the file metadata
So here is my questions:
Can I do this directly with the folder?
How can I store the list of authorized uids?
I am programming an iOS app in Swift.
Here is my actual code for the Rules:
service firebase.storage {
match /b/on-team.appspot.com/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
match /users-projects {
match /{projectId} {
allow read, write: if ?????
}
}
}
}
Here is the official documentation: https://firebase.google.com/docs/storage/security/user-security .
I think the correct static rule would be :
allow read, write: if request.auth.uid == 'a-user-id' || request.auth.uid == 'another-user-id' || ...
But I guess you are looking for dynamic rules :)
For the owners it's quite simple to setup a dynamic rule with the folder name :
match /users-projects/{projectId}/{userId} {
allow read, write: if request.auth.uid == userId
For more complex cases like invited users, you can try using the custom metadata to store invited uids in the file, and match them against user id accessing that ressource, example rule:
allow read: if resource.metadata.invited.matches(request.auth.uid);
The custom metadata values can only be strings, so I suggest you store them as coma-separated value so you can edit them easily, and at the same time use a simple match in the access rule.
Note: this is only scalable while invitedUids.join(',') length is shorter than maximum length of custom metadata values. (I don't know that value). If your app is not built to accept hundreds of invited users, it should be ok, otherwise you might need to setup a server-side access mecanism which build a unique download link for each invited user, instead of relying on simple rules.
Also, I don't think you can use token groupId value to enforce access security in your case (as depicted in the docs), because you have a many-to-many relationship between users and folders/files. (users will not belong to only one group)
So, to answer your questions:
The resource object in the rules only apply to files, if using metadata to enforce access, they need to be updated on each file in the folder if they share the same access rules
The metadata is just a string->string key-value store, you just need to store user ids as a string in a arbitrary non-reserved key, as explained above.
Store individual project in a directory using their userid
/users-projects/user_id/project1
/users-projects/user_id/project2
service firebase.storage {
match /b/on-team.appspot.com/o {
match /{allPaths=**} {
allow read, write: if request.auth != null;
}
match /users-projects/{user_id} {
allow read, write: if request.auth.uid == user_id
}
}
}

RestKit - use identificationAttributes that are not part of the response

In RestKit is it possible to use identificationAttributes that are actually not part of the JSON response?
My case is the following - I have a service that lists all articles for the currently logged-in user like http://example.com/json/articles.json
My problem is the following - since the application allows multiple users to login, I keep the articles in the database together with the userId for each article. If I set the articleMapping.identificationattributes = #["articleId"], then I have a problem if two users using the device have the same article - it will be overwritten regardless of the userId, because it is not part of the response.
To sum up the facts:
For the JSON request I do not send the userId, it is part of the
server session only, so I think that I cannot use RKRoute
I do the mapping of the article with the user manually after RestKit mapping.
I do not have the userId property as part of the JSON response, it exists only inside the ArticleManagedObject.
Is there a way to inform RestKit that during the mapping, it should check the articleId+userId combination as an identificator? I tried using identificationPredicate with no success.
EDIT:
An example response from the server, when UserA is logged in:
{
"data":{
"articles":[
{
"articleId":1,
"title":"Objective C Basics"
},
{
"articleId":2,
"title":"Xcode Basics"
}
]
}
}
and here is the response when UserB is logged in:
{
"data":{
"articles":[
{
"articleId":1,
"title":"Objective C Basics"
},
{
"articleId":3,
"title":"Java Basics"
}
]
}
}
If UserA logs in, everything is fine. But if UserB logs in from the same device, then article 1 is mapped to UserB, and from now on, the connection between UserA and article 1 is lost.
As I understand from your suggestion, the only solution is to return also the user id from the service, set RKUnionAssignmentPolicy and let RestKit take care of the mapping (currently I am manually making the mapping between articles and users after RestKit).
Another question that I have - is it possible to set the identificationAttributes or identificationPredicate so that it makes a separation between object article 1 for UserA and object article 1 for UserB.
You currently do the user to article mapping outside RestKit, this is fine, but you will need to modify this process a little.
To begin with, I'm assuming here that the article response is the full set of articles for the user. If not then things get more tricky and you'll need to modify the below to account:
Start by getting all of the existing articles for a user. With this we're going to look at what needs to be removed and what needs to be added.
As we iterate through the articles we have received we can check the existing articles for a match, if we find one we have no work to do. If we don't find a match we need to add the relationship to the existing set, which will be a union with any relationship to any other user.
Next we want to remove the list of new articles from the list of the existing articles to get the list of deletions, for these we just need to break the link, again leaving other users unchanged.

Resources