realtime database rules wildcard - flutter

as soon as I use a wildcard I get this error:
Unhandled Exception: [firebase_database/permission-denied] Client doesn't have permission to access the desired data
works:
{
"rules": {
"user": {
".write": "auth != null",
".read": "auth != null"
}
}
doesn't work:
{
"rules": {
"user": {
"$userId": {
".write": "auth != null && $userId === auth.uid",
".read": "auth != null"
}
}
}
doesn't work:
{
"rules": {
"user": {
"$userId": {
".write": true,
".read": true
}
}
}
db structure:
code:
List<User>? userlist;
late Query query;
void initState() {
userAuth.FirebaseAuth.instance.authStateChanges().listen((userAuth.User? user) {
final FirebaseApp abcApp = Firebase.app();
final FirebaseDatabase database = FirebaseDatabase.instanceFor(app: abcApp);
userlist = [];
query = database
.ref().child("user").orderByChild("userId")
.equalTo(
ownUid==true
?user!.uid
:widget.peerId
);
_onOrderAddedSubscription1 = query.onChildAdded.listen(onEntryAdded1);
_onOrderChangedSubscription1 = query.onChildChanged.listen(onEntryChanged1);
});
super.initState();
}

My guess is that you're trying to read from /users. If you do that with the second set of rules, it gets rejected as those rules don't grant anyone permission to read all of /users - but only allows one to read /users/$uid.
It helps to recall that security rules on their own don't filter data, but instead merely check whether all data access is authorized.
So if you want to allow reading from /users, you need a rule on /users that allows that read. And if you want to allow reading specific data under /users, you either need to read from that specific path or combine a query and rules so that the rules can verify that the client is only reading data they're authorized for.
This problem comes up quite regularly, so I also recommend checking out more questions about 'rules are not filters'

Related

Using Firebase Security Rules, how can I secure a node that has transactions run on it?

In my database, I have a users node that contains data under a user ID.
This includes their bio, number of followers, whether the user is a moderator, and more.
users
{
userId1
{
bio: "Example bio..."
followers: 250
moderator: true
...
}
}
In order for the number of followers to be correct, I use a transaction block to increment the followers property every time the follow button is clicked. There are a few other properties that require transaction blocks as well.
Unfortunately, I have discovered that in order for the transactions to work, the security rules for the $userId node must be set to: “.write”: “auth != null”. Otherwise, the number of followers property won’t be incremented when someone clicks the follow button. Because the transaction block queries the entire user, we can’t limit the security rules to just the “followers” property.
"users":
{
"$userId":
{
// Has to be set like this or transactions won't work
".read": "auth != null",
".write": "auth != null",
"bio":
{
// This will have no effect due to rule cascading
".write": "auth.uid === $userId"
}
"moderator":
{
// This will have no effect due to rule cascading
".write": ...
}
}
}
And since rules cascade, this makes it seem impossible to set specific rules for any other properties under user, including bio and whether the user is a moderator etc. This makes the user property vulerable to changes by malicious users.
The same thing happens for a post and likes, the example used in the Firebase documentation. Because the transaction block queries the entire post, we can’t limit the security rules to just the “likes” property. All of the other post properties will have to settle for the “.write”: “auth !=null” setting because of cascading.
The best I can do is to use validation, but that won’t stop malicious users from setting their follow count to 10,000 or making themselves a moderator if they somehow gain access.
Using Firebase Rules, is there any way to secure nodes that have transactions run on them?
Edit: More Info
This is a simplified version of what my transaction block looks like for incrementing the follower count:
// Run transaction block on the user in the "users" node
userRef.runTransactionBlock({ (currentData: MutableData) -> TransactionResult in
// Store the user
if var user = currentData.value as? [String: AnyObject]
{
// Get the number of followers
var numberOfFollowers = user["numberOfFollowers"] as? Int ?? 0
// Increase the number of followers by 1
numberOfFollowers += 1
// Set the new number of followers
user["numberOfFollowers"] = numberOfFollowers as AnyObject?
// Set the user value and report transaction success
currentData.value = user
return TransactionResult.success(withValue: currentData)
}
return TransactionResult.success(withValue: currentData)
})
This is how followers are stored in my database:
myDatabase: {
followers: {
"andrew098239101": {
// These are all the user ID's of users that follow "andrew098239101"
"robert12988311": true
"sarah9234298347": true
"alex29101922": true
"greg923749232": true
}
"robert12988311": {
"alex29101922": true
}
}
...
users: {
"andrew098239101": {
// Andrew's user info
"bio": "hello I am Andrew"
"numberOfFollowers": 4
"moderator": true
...
}
"robert12988311": {
"bio": "I'm Robert"
"numberOfFollowers": 1
"moderator": false
...
}
}
}
There is a similar node for following, etc.
You should be able to set the looser write permission on the followers property only. So something like this:
"users": {
"$userId": {
".read": "auth != null",
".write": "auth.uid === $userId",
"followers": {
".write": "auth.uid !== null"
},
}
}
With this, all authenticated users can write to /users/$uid/followers, but only the owner of a profile can write the other values.
I'd recommend separating the followers from the other user profile data, so that you have two top-level lists: users/$uid with the user profile that only the owner can write, and followers/$uid with the data that followers can write.
I also recommend not just storing the count, but also storing the UID of their followers. In fact, I'd use this structure:
users: {
"uidOfMichael": {
followers: {
"uidOfFrank": true,
"uidOfJames": true,
"uidOfAndrew": true
},
followerCount: 3
},
"uidOfFrank": {
followers: {
"uidOfJames": true,
"uidOfAndrew": true
},
followerCount: 2
},
"uidOfJames": {
followers: {
"uidOfAndrew": true
},
followerCount: 1
},
"uidOfAndrew": {
followers: {
"uidOfJames": true
},
followerCount: 1
}
}
Now you can validate these additional things:
A user can only write their own UID to followers.
A user can only increment followerCount when they added their UID to followers
The rules for that would be something like (typos possible):
"users": {
"$userId": {
".read": "auth != null",
".write": "auth.uid === $userId",
"followers": {
"$followerId": {
".write": "auth.uid !== $followerId"
}
},
"followerCount": {
".write": "
newData.val() === data.val() + 1 &&
!data.parent().child('followers').child(auth.uid).exists() &&
newData.parent().child('followers').child(auth.uid).exists()
"
}
}
}

How can I securely allow everyone to read and auth users to write to application with Firebase Realtime database?

I am building a social media application in which anyone can read what is going on but have to sign in to interact with each post. I would like to find a way to allow all my users to be able to do this without having an insecure database. I am almost done with development and I am currently trying to clean up a few things.
My current Rules are set to true for development purposes:
{
"rules": {
".read": true,
".write": true
}
}
I have tried a few rules
1.
{
"rules": {
"$uid": {
".read": true,
".write": "auth.uid == $uid"
}
}
}
Which allows everyone to read but doesn't allow writing due to permissions.
2)
{
"rules": {
"$uid": {
".read": "auth.uid == $uid",
".write": "auth.uid == $uid"
}
}
}
Which doesn't allow anyone to read or write due to permissions.
3.
{
"rules": {
".read": "auth.uid != null",
".write": "auth.uid != null"
}
}
Which works but I get an email later telling me my database is insecure.
Here is an example that causes a permission denied error:
A) When the user clicks the send new announcement button
This func is called.
StorageService.sendAnnouncementDataToDatabase(photoUrl: "", announcementComment: announcementComment, ratio: CGFloat(0), onSuccess: {
ProgressHUD.showSuccess("SWEET")
})
B) Inside sendAnnouncementDataToDatabase func
let ref = Ref().databaseAnnouncements
let newAnnouncementID = ref.childByAutoId().key ?? ""
let newAnnouncementReference = ref.child(newAnnouncementID)
guard let currentUser = Api.User.CURRENT_USER else {
return
}
var dict = ["userId" : currentUserId, "photoUrl" : photoUrl]
newAnnouncementReference.setValue(dict, withCompletionBlock: {(error,ref) in
// permission denied occurs here
})
I have read through many documents, and stack question. As well as tried to set rules for each path. Same outcome.
Any ideas would be helpful.
Thank you.
If this is the actual goal
in which anyone can read what is going on but have to sign in to
interact with each post
then this rule will do it
{
"rules": {
".read": true,
".write": "auth != null"
}
}
As it allows anyone to read anything, but only authenticated users can write.
However, it's pretty insecure as Frank mentioned in his comments. We may be able to expand on this solution a bit but without understanding the entire use case it could go well beyond what can be posted here as a full answer.

Real time database REST API Constraint index field must be a JSON primitive

I'm trying to make a query to the Real-time database from google using their rest API.
this is how the data of "waiters" path looks like :
{
"-Kb2dYPV0yUXpD_1moc9": {
"id": "Ky7gTz0BFyRudih0DhSAe3lwci13",
"role": "normal"
},
"-Kb2etFm1xHd8sSsESeK": {
"id": "VRBNr5OnMQaDoLpkKppapyDW8JZ2",
"role": "admin"
},
}
On the client-side, I know the id and what I would like to get is the role (i.e "admin" or whatever it is) but I don't know the autogenerated location-id ("-Kb2etFm1xHd8sSsESeK").
Does someone know how to proceed?
I tried :
static Future<RoleType> fetchRole(String userId, String token) async {
try {
var url =
"https://xxx.firebaseio.com/waiters.json?orderBy=\"id\"&equalTo=\"" +
"$userId\"" +
"?auth=" +
token;
final response = await http.get(url);
final extractedData = json.decode(response.body);
if (extractedData == null) return null;
return RoleType(extractedData['role']);
} catch (error) {
print("[fetchRole]:: " + error.toString());
throw error;
}
}
the URL without the token gives :
https://xxx.firebaseio.com/waiters.json?
orderBy="id"&equalTo="VRBNr5OnMQaDoLpkKppapyDW8JZ2"?auth=token
But I get this error :
{error: Constraint index field must be a JSON primitive}
I still get this error when I update the rules as follow:
{
"rules": {
".read": "auth != null", // 2020-11-9
".write": "auth != null", // 2020-11-9
"waiters": {
".indexOn": ["id"]
}
}
}
Thanks in advance for your help!
For some reason, it works now. I don't know why... Mysteries of CS.
Hi just got the same error when i was playing with ionic with angular.
It turn out one of my character is not an English character, i typed “” instead of "" in Chinese character.
Hope this help if anyone passing by ;)

Firebase security rule does not prevent removeValue()

I have set the security rule the following, but the .removeValue() is still able to delete records. What am I doing wrong?
{
"rules": {
".read": "auth != null",
".write": "auth != null && newData.exists()"
}
}
Here is the code (in swift) that attempts to remove an entry and according to the security rules should fail, but it succeeds:
let ref = FIRDatabase.database().reference(withPath: "myDatabase/customerIDs")
ref.child("\(customerID)").child(scheduleIDs[indexPath.row]).removeValue()
Your code removes a single value from /myDatabase/customerIDs/$customerId/$scheduleId. Your rules only reject writes that delete the entire database, not writes that delete a single schedule ID. If you want to disallow those, add a rule on the correct path too.
Something like:
{
"rules": {
"myDatabase": {
"customerIDs": {
"$customerId": {
"$scheduleId": {
".validate": "newData.exists()"
}
}
}
}
}
}

Firebase rule get target user uid

I'm writing a Firebase rule to allow user a to access user b's 'Test' property:
"Test": {
".read":"root.child('Users').child(auth.uid).child('Test').child('subtest').val().contains('xxxxxxxxxxxxxxxxMdv3wQRjIs2')",
}
Just figured out that the rule checks on the requestor (user a) not the target user b (could be wrong :) ).
Is there any way to write a rule to represent the target user's uid like 'auth.uid' for the requestor?
Or anything could be done at the target user level?
Is this what you're looking to do?
"$target":{
".write": "root.child('path').val() == 'Value'"
}
or
"Users": { //Path to where you store your users (case sensitive)
"$uid": {
".read": "$uid === auth.uid",
".write": "$uid === auth.uid"
}
}
EDIT: OP found the solution using ".read": "data.child('allowed').val() == auth.uid",
This should get you the user's UID:
let userID = FIRAuth.auth()!.currentUser!.uid