Swift Firebase Rule Permissions Denied when observeSingleEvent - ios

I'm developing a app that allows teachers to record their students' lessons. Using Firebase Rules I want to allow teachers access to certain schools. I have the following Rules:
{
"rules": {
"Schools" : {
"$schoolId" : {
".read" : "data.child('Teachers').hasChild(auth.uid)",
".write" : "data.child('Teachers').hasChild(auth.uid) ||
root.child('User').child(auth.uid).child('Invitation').exists()"
}
},
"Users" : {
"$uid" : {
".read" : "auth.uid === $uid",
".write": "auth.uid === $uid"
}
}
}
}
In the simulator everything works fine, but when firing the below all permissions are denied to all schools.
DataService.ds.REF_SCHOOLS.observeSingleEvent(of: .value) { (snapshot) in
MySchools.mySchoolKeyList.removeAll()
MySchools.mySchoolList.removeAll()
if let snapshot = snapshot.children.allObjects as? [DataSnapshot] {
for snap in snapshot {
if let recordDict = snap.value as? Dictionary<String, AnyObject> {
if let name = (recordDict["SchoolName"] as? String) {
MySchools.mySchoolList.append(name)
}
MySchools.mySchoolKeyList.append(snap.key)
}
}
if MySchools.mySchoolList.isEmpty {
MySchools.mySchoolList.append("You do not belong to any schools.")
}
}
}
Below is snapshot of the Firebase database for reference:
Does anyone know what's wrong with my rules? Also how to handle the permission denied result would be appreciated.

Your code is trying to read from /Schools, but your rules don't give anyone permission to read from /Schools. So Firebase correctly rejects the read.
I have a feeling that you expected the read operation to automatically filter the result to only return the school nodes (/Schools/$schoolId) that the user has access to. But that is not how Firebase security rules work: rules cannot be used to filter data.
This is a common source of confusion for developers getting started with Firebase's server side security rules, so I recommend you check out of the some previous questions mentioning "rules are not filters".
Nowadays there is a way to get filtered data, without giving the user access to all Schools. For this you will need to:
Use a query to only request the schools that you're supposed to have access to.
Validate that this query is allowed on /Schools in your security rules.
For an example of this see query based rules in the documentation.
The code should be something like this:
let query = DataService.ds.REF_SCHOOLS.queryOrdered(byChild: "Teachers/"+uid).queryValue(equalTo: true);
query.observeSingleEvent(of: .value) { (snapshot) in
...
But the problem with this is that you would need to define an index for each teacher. While this is technically possible, it would lead to an unreasonable number of indexes.
In hindsight: your current data structure allows you to efficiently find the teachers for a specific school. It does not however allow you to efficiently find the schools for a specific teacher.
To efficiently allow finding the schools for a specific teacher, add an additional data structure to your database that maps schools to each teacher. E.g.
teacherSchools
teacherUid1
schoolId1: true
schoolId2: true
teacherUid2
schoolId2: true
schoolId3: true
With this structure you can easily read all school Ids for a teacher, and then load each school. This loading is a lot faster than most developers expect, since Firebase pipelines the requests over a single connection.
For a longer/another explanation of this approach, see Firebase query if child of child contains a value and Firebase Query Double Nested.

Related

firebase real time dB retrieving of specific data from multiple users in swift

so I'm working these days on a new project and I have a problem I can't solve, I hope someone can help me.
I'm working on an iOS app, I'm storing all user data on Firebase Real time dB.
My main goal is to get specific data from all users from particular positions,
For example:
Inside users, I have different UIDs of all the users in the dB.
In each one of them, there is a username, I would like to retrieve the username of each user.
In the future, I would like to store the location for each user under "Location". and then I would like to get all users that their location is "New-York".
I'll be glad to get some ideas on how to figure it out!
Thanks!
users
XLS37UqjasdfkKiB
username: "Donald"
ei8d4eYDafjQXyZ
username: "Barak"
etcj0lbSX5Oasfj
username: "Abdul"
rglmlG6Rasdgk5j
username: "Ron"
You can:
Load all JSON from /Users.
Loop over snapshot.children.
Then get the value of each child's username property.
These are also the most common navigation tactics when it comes to Firebase Realtime Database, so you'll apply these to pretty much any read operation.
Something like this:
Database.database().reference().child("isers").observe(.value, with: { (snapshot) in
if !snapshot.exists() {
print("No users found")
} else {
for case let userSnapshot as DataSnapshot in snapshot.children {
guard let dict = userSnapshot.value as? [String:Any] else {
print("Error")
return
}
let username = dict["username"] as? String
print(username)
}
}
})
Also see:
the documentation on reading data.
the documentation on listening for lists of data with a value event.
How to properly use queryOrderedByValue
Get Children of Children in Firebase Using Swift
And more from searching on [firebase-realtime-database][swift] children.

How to get specific info for all users?

How can I get the kaarsen array out of my Firebase database of every single user?
You seem to be nesting different types of data, which is a big anti-pattern in the Firebase Database. When you read data from Firebase, you always read entire nodes. So in your scenario you either read the entire user, or you don't read them. You cannot retrieve just the kaarsen node for each user. This is one of the many reasons why Firebase recommends against nesting different types of data.
In your case it seems best to split the kaarsen int a top-level node:
users
<uid>
email: ...
geboortedatum: ...
naam: ...
kaarsen
<uid>
...
With this structure you can get the kaarsen for all users by accessing /kaarsen.
let userID = FIRAuth.auth()?.currentUser?.uid
ref.child("users").child(userID!).child("kaarsen").observeSingleEvent(of: .value, with: { (snapshot) in
// Get user value
let value = snapshot.value as! Array
arrayOfKarsens.append(value)
// ...
}) { (error) in
print(error.localizedDescription)
}

Firebase complex query - iOS Swift

I got instagram like app.
I got firebase as backend which has :
post node - contains all the posts from different users
[post_id
post_user
post_name
post_content]
Problem:
I want to get the posts only from the users which I have followed.
My Steps:
I got list of following users_id in array
followersIds
I want to know if I can make query like
getFollowersList { (followersIds) in
print(followersIds)
self.postRef.queryOrdered(byChild: "user").queryEqual(toValue: followersIds).queryLimited(toLast: limit).observeSingleEvent(of: .value, with: { (snapshot) in
if snapshot.value is NSNull { completion(nil); return}
else {
let dict = snapshot.value as! [String:Any]
self.mapDictionaryToArrayOfPost(dict: dict, completion: { (posts) in
completion(posts)
})
}
}) { (error) in
debugPrint(error.localizedDescription)
}
}
ISSUE:
Firebase can't accept array as parameter in queryEqual()
I can do this filter by filtering in the app but i want to query firebase and get the result .
Beacause filtering in the app is not a good thing.
Any suggestion.
There is no way to do this query in one go in Firebase. As is common in most NoSQL solutions, you should model the data in a way that allows the use-cases that your app needs.
This means that: if you want to retrieve all posts from people you follow in one go, you should keep a list of all posts from people you follow in the database. Since you seem to be building something like a social network, that means that you're essentially building each user's wall:
following
uid1
uid2: true // so uid1 follows uid2 and uid3
uid3: true
uid3
uid2: true // and uid3 follows uid2
posts
post1
author: uid2 // uid2 has made a post
title: "...."
post2
author: uid3 // uid3 has made a post
title: "...."
wall
uid1
post1: true
post2: true
uid3
post1: true
So in this model we keep the key of each post of a user that you follow under /wall/myuid. Now you can easily get the list of posts to show the wall of a specific user. And from that you can loop over the keys and load each of them. The latter is not a slow operation on Firebase, since it pipelines the requests. See Speed up fetching posts for my social network app by using query instead of observing a single event repeatedly.
This type of data duplication is very common in NoSQL databases such as Firebase, which is why we cover it in our documentation on structuring data, in our firefeed demo app and in our new video series Firebase for SQL developers.

Showing posts from those users you follow - swift & firebase

I have made a following system where users can follow each other but it is not very useful at the moment. I cannot seem to get my head around how to only show the posts of the users you follow - so I am here to ask you how you would do it.
Let me just show you how the database is set up:
As you can see I have a tap called Users - in there all the users ID are stored. Whenever a user wants to follow another user, the tap Following is made and under there the user that the current user pressed follow - his/her ID is store under Following. And then the current users ID is added to the user he/she follows under Followers. Pretty simple but complicated to explain :D I hope you get where I'm going.
So now I want only to show posts of the users I am following. How would you do that? Would you do it like this: When a user is making a post it is added to his own name under a tap called Posts AND added to all the users names under PostsToFollow? I just think it would take up a lot of space in the database? Isn't there a better way to do this?
Well here is a part of my code that is getting me all of the current users followers. How can I use that code to, whenever I am creating a new post, add that to their postsToFollow?
func startObersvingDB() {
FIRDatabase.database().reference().child("Users").child(IDgotten).child("Followers").observeEventType(.Value, withBlock: { (snapshot: FIRDataSnapshot) in
var newUpdates = [FollowersStruct]()
for update in snapshot.children {
print(update)
let updateObject = FollowersStruct(snapshot: update as! FIRDataSnapshot)
newUpdates.append(updateObject)
}
self.updates = newUpdates.reverse()
self.tableView.reloadData()
}) { (error: NSError) in
print(error.description)
}
}
To Frank:
I've made this for adding the entire post to all the users that are following my user. I cannot do that because I have a like and comment button on the posts so the likes on one users posts will not be the same as other users posts and that is why I think the idea of adding the key to the post in the followers walls is great. But how do I do that?
I have made this so far, trying to accomplish what you said :-) :
let feed = Sweet(content: update, addedByUser: name!, profilePhoto: uid, likesForPost: ["user id": false], date: timePosted, category: 1, workoutComment: "", workoutTime: "")
let feedRef = FIRDatabase.database().reference().child("feed-items").childByAutoId()
feedRef.setValue(feed.toAnyObject())
FIRDatabase.database().reference().child("Users").child(uid).child("Followers").observeEventType(.Value, withBlock: { (snapshot: FIRDataSnapshot) in
var newFollowers = [FollowersStruct]()
for updateFollowers in snapshot.children {
let updateObjectFollowers = FollowersStruct(snapshot: updateFollowers as! FIRDataSnapshot)
newFollowers.append(updateObjectFollowers)
}
self.followings = newFollowers
print(self.followings.count)
for i in 0..<self.followings.count {
let followers = self.followings[i]
let feedRefFollowers = FIRDatabase.database().reference().child("Users").child(followers.Id).child("Wall").childByAutoId()
feedRefFollowers.setValue(feed.toAnyObject())
}
}) { (error: NSError) in
print(error.description)
}
Writing the post under the user's own name and all his follower's names is a typical example of fanning out the data. This indeed duplicates the data, but the benefit is that your read operations will be much faster and have significantly simpler code.
If you don't want to duplicate the entire post, you can also just write the key of the new post under each follower's "wall". Then when you need to show a list of posts for a user, you read their "wall" and client-side join each post. This is the approach taken by in our classic Firefeed example app.
Either of these approaches is a form of denormalization: duplicating part of your data to get better read performance.
I highly recommend reading this article on NoSQL data modeling and checking out our video series on Firebase for SQL developers. Oh... and this blog post on client-side fan-out also won't hurt.

How to only show friend's posts? Swift / Firebase

I just implemented a friend adding system and I'm scratching my head on how to only allow users to see friend's posts.
On my main screen where I'm displaying posts, I've got :
DataService.ds.REF_USERS.child(currentUser!).child("friends").observeSingleEventOfType(.Value) { (snapshot: FIRDataSnapshot) in
for snapshots in snapshot.children.allObjects {
let snap = snapshots as! FIRDataSnapshot
var frand = String(snap.key)
self.friendsArray.append(frand)
}
which pulls back the UID of all their friends.
I then want to search through all posts that have that UID as a child.
I figured maybe I could do a for-loop through the friendsArray with an equalTo query...? but then I'm not sure how to implement that with my currently existing query that shows back posts that I'm wanting to see :
DataService.ds.REF_POSTS.queryOrderedByChild("timestamp").queryStartingAtValue(cutoff).observeEventType(.ChildAdded, withBlock: { (snapshot:FIRDataSnapshot) in
-bunch of code that sets up my post and appends it to an array-
self.collectionView.reloadData()
})
Some of my data structure is :
users
3093098409384
friends
29834098209 : true
posts
099390234
userWhoPosted: 29834098209
So I'm pulling the friends out of the users-> friends -> UID bit, but how do I then get back posts made by those users, and THEN carry out my normal query back I'm doing?
Any guidance is appreciated.
Have a look at this article:
https://firebase.googleblog.com/2015/10/client-side-fan-out-for-data-consistency_73.html?m=1
Basically you need to "fanout" your data, that means replicating the "post" object data to multiple locations to make it easier to access.
i.e: create a "timeline" mode and under that each user's uid and under that posts from this user's followers.
Timeline: {
<user_uid>: {
<post_id>: {
// your post data
}
}
}
You do that by having each time a user submits a new post, send that post to the timeline of each of his followers.
It may sound strange but Firebase has a concept where you should duplicate data many times to make it easier to access.

Resources