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()"
}
}
}
}
}
}
Related
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'
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()
"
}
}
}
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.
A = Database.database().reference().child("users");
A.observe(DataEventType.value, with: {snapshot in
The rules:
"rules": {
"users" :
".read": "auth.uid != null",
".write": "auth.uid != null" {
"$uid": {
"Garden" :{
".read": "auth.uid != null"
,".write": "$uid == auth.uid"
}
"Hose": .....
"House": ....
So as you'll see the users is not being assigned a rule because it would override rules for Garden, House, Hose etc. Therefore, I want to alter the code that looks for the snapshot in A to go more directly to the childs of users, vs stopping at users and consequently not passing the security test (since users has no rules).
What comes after A.observe:
for users in snapshot.children.allObjects as! [DataSnapshot] {
let usersObject = users.value as? [String: AnyObject]
let usersGarden = usersObject?["Garden"] as? String
let usersHose = usersObject?["Hose"] as? String
let usersHouse = usersObject?["House"] as? String
......
let USA = UserH(Garder: usersGarden, Hose: usersHose...)
self.users.append.
self.table.reloadData()
You'll see that the targets are the childs that come after uid, so a way to target them could allow me to avoid stopping at users in the snapshot A
As is, the code and the rules in the question match, the code reads the users node and the rules allow any authenticated user to read that node iterate over the child data.
I believe your actual question is
How do I get a more granular control of what can be written to a child
node in Firebase using Firebase Rules
suppose we have a structure that matches your rules
users
uid_0
garden: "rose"
hose: "green"
uid_1
garden: "tomato"
hose: "black"
and suppose we only want to allow a user to update their own garden; other users cannot modify that users garden. e.g. uid_0 can modify only the node /users/uid_0/garden. uid_1 can only modify /users/uid_1/garden etc.
Here's a rule that lets all authenticated users read the users node but only the currently authenticated user can write to their own garden node
{
"rules": {
".read": false,
".write": false,
"users" : {
".read": "auth != null",
"$uid": {
"garden": {
".write": "$uid === auth.uid" //only the authenticated user can write
}
}
}
}
}
Note that I set read and write to false at a high level and am granting access at a lower, more granular level.
So in this case, if there was another node at the same level as 'users' nobody could read or write to it.
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