8

I don't understand how can I set a limit of files upload in a day. I want users to publish a maximum of 10 photos per day. On the database side I put a increment counter. If it reaches a certain size it does not allow a user to post other contents. but by the storage side this is impossible. An attacker can publish all files that he want without limits. Is there a solution to prevent this situation? Thanks in advance. At moment my security rules are:

service firebase.storage {
  match /b/projectid/o {
    match /Photo/{user}/{photo}/image.jpg {
      allow write: if request.auth != null && 
                      request.auth.uid == user && (
                      request.resource.size < 5 * 1024 * 1024 && photo.size() < 32 || 
                      request.resource == null);
      allow read: if request.auth != null && 
                     request.auth.uid == user
    }
  }
}
DThink
  • 429
  • 2
  • 18
  • 2
    There are a couple of things you can do. 1) Implement rules that only allow validated users to read/write to the node in question. That would prevent an attacker for just uploading to any node. 2) Implement a 'upload_count' node for the day and have uploads (writes) include the upload as well as a timestamp. Set up a validation rule which will only allow uploading if the count is less than 10 uploads for that day. You'll want to look at the predefined rule variables 'now' and 'new data' and compare the now to the timestamp of the data attempting to be written. – Jay Sep 23 '16 at 17:53
  • if you see my rules, each user can write only in its node, but the problem is that each user can write unlimited files in its node. i not understand the second point. in the database i have already a counter but in the storage how can i store a counter? Remember that the database and storage rules are different and separate and an attacker can upload a file without update the counter in the database – DThink Sep 23 '16 at 18:49
  • no way to solve this? – DThink Sep 25 '16 at 09:47

1 Answers1

4

Well, there's a very simple way of doing this, and there's the right way of doing this.

The hacky way of only allowing a certain number of files to be uploaded in a certain time period is to name the files with some numerical property: say users/{userid}/0.jpg through users/{userid}/9.jpg (for 10 photos).

You can write a rule to check that as follows:

// Match all filenames like 0.jpg
match /users/{userId}/{photoId} {
  allow write: if photoId.matches('^\d\.jpg$')
}

If you need more granularity than order of magnitude, you can do something like:

// Match all filenames like YYY.jpg where YYY is a number less than XXX
match /users/{userId}/{photoId} {
  allow write: if int(photoId.split('\.')[0]) < XXX
}

That only solves half our problem though: we can restrict the number of files, but what if a user just wants to upload over them? Luckily, we can write a rule that prevents an end user from overwriting their file ever (though we've got to carve out deletions), or within a given time period. Let's explore:

// Allow files to be overwritten once a day, written if there's nothing there, or deleted as often as desired
match /users/{userId}/{photoId} {
  allow write: if request.time > resource.timeCreated + duration.value(1, "d") || resource.size == 0 || request.resource.size == 0
}

These can be combined into function:

function isAllowedPhotoId(photoId) {
  return int(photoId.split('\.')[0]) < XXX
}

function canOverwritePhoto() {
  return request.time > resource.timeCreated + duration.value(1, "d") || resource.size == 0 || request.resource.size == 0
}

match /users/{userId}/{photoId} {
  allow write: if isAllowedPhotoId(photoId) && canOverwritePhoto()
}

Long term, the solution is being able to reference Database data from within Storage, and vice-versa. Unfortunately, that world isn't here yet, but we're working towards it.

Mike McDonald
  • 15,609
  • 2
  • 46
  • 49
  • i see that with this method is a problem when i delete a file. if i delete certain files i have a folder with non ordered files. this is a bit fastidious when i want to save a photo with certain ID – DThink Sep 26 '16 at 15:01
  • You're right--it puts the burden on the client to properly rename the files. You can pick a simple hashing scheme: mod the name of the file by *n* and then put it in that location. If it fails, increment by 1 (linear probing) or by n^2 (quadratic probing) and add the file there. – Mike McDonald Sep 26 '16 at 17:13
  • ok. i hope in the future in a really unified platform between storage and database. For example, i remember when i used Parse, if i upload an image, automatically i had the reference of this image saved in my database. i think that a similar feature It could be useful. thanks and good work – DThink Sep 26 '16 at 20:54
  • Agreed with that--we're working on it. That said, by splitting things apart, we gain a number of advantages (more robust uploads and downloads, file sizes up to 5TB [vs 10MB], raw access to files, reduction in cost, control of object lifecycle, and soon the ability to choose regions in which the files are stored). – Mike McDonald Sep 27 '16 at 03:38
  • ok i try to use a linear scheme. the problem is that at any failed uploaded i use a bandwidth and with photos > 1MB the procedure is very slow because at any failed uploaded the client try to reupload in the next position. is not a good solution – DThink Sep 27 '16 at 12:34
  • It would be great to read first the contents of the photos folder by client side. with an array of pictures. but i not know if is possible – DThink Sep 27 '16 at 12:50
  • I recommend combining this with the Realtime Database (which can store references to objects, as well as updated timestamps) and perform that check. Agree that upload bandwidth is tough on mobile phones. And yes, as mentioned we're working on adding cross-service rules to handle this type of integration. – Mike McDonald Sep 27 '16 at 16:13