10

I wonder if there is any simpler way to achieve this rule. Just started to experiment with firestore.

match /emails/{emailId} {

    allow write: if request.resource.data.attachments.size() == 0
    || request.resource.data.attachments.size() == 1 && request.resource.data.attachments[0].fileSize < 3 * 1024 * 1024 && (request.resource.data.attachments[0].filetype == 'image/png' || request.resource.data.attachments[0].filetype == 'image/jpg' || request.resource.data.attachments[0].filetype == 'application/vnd.ms-excel')
    || request.resource.data.attachments.size() == 2 && request.resource.data.attachments[0].fileSize < 3 * 1024 * 1024 && (request.resource.data.attachments[0].filetype == 'image/png' || request.resource.data.attachments[0].filetype == 'image/jpg' || request.resource.data.attachments[0].filetype == 'application/vnd.ms-excel') && request.resource.data.attachments[1].fileSize < 3 * 1024 * 1024 && (request.resource.data.attachments[1].filetype == 'image/png' || request.resource.data.attachments[1].filetype == 'image/jpg' || request.resource.data.attachments[1].filetype == 'application/vnd.ms-excel')
    || request.resource.data.attachments.size() == 3 && request.resource.data.attachments[0].fileSize < 3 * 1024 * 1024 && (request.resource.data.attachments[0].filetype == 'image/png' || request.resource.data.attachments[0].filetype == 'image/jpg' || request.resource.data.attachments[0].filetype == 'application/vnd.ms-excel') && request.resource.data.attachments[1].fileSize < 3 * 1024 * 1024 && (request.resource.data.attachments[1].filetype == 'image/png' || request.resource.data.attachments[1].filetype == 'image/jpg' || request.resource.data.attachments[1].filetype == 'application/vnd.ms-excel') && request.resource.data.attachments[2].fileSize < 3 * 1024 * 1024 && (request.resource.data.attachments[2].filetype == 'image/png' || request.resource.data.attachments[2].filetype == 'image/jpg' || request.resource.data.attachments[2].filetype == 'application/vnd.ms-excel')
    || request.resource.data.attachments.size() == 4 && request.resource.data.attachments[0].fileSize < 3 * 1024 * 1024 && (request.resource.data.attachments[0].filetype == 'image/png' || request.resource.data.attachments[0].filetype == 'image/jpg' || request.resource.data.attachments[0].filetype == 'application/vnd.ms-excel') && request.resource.data.attachments[1].fileSize < 3 * 1024 * 1024 && (request.resource.data.attachments[1].filetype == 'image/png' || request.resource.data.attachments[1].filetype == 'image/jpg' || request.resource.data.attachments[1].filetype == 'application/vnd.ms-excel') && request.resource.data.attachments[2].fileSize < 3 * 1024 * 1024 && (request.resource.data.attachments[2].filetype == 'image/png' || request.resource.data.attachments[2].filetype == 'image/jpg' || request.resource.data.attachments[2].filetype == 'application/vnd.ms-excel') && request.resource.data.attachments[3].fileSize < 3 * 1024 * 1024 && (request.resource.data.attachments[3].filetype == 'image/png' || request.resource.data.attachments[3].filetype == 'image/jpg' || request.resource.data.attachments[3].filetype == 'application/vnd.ms-excel');
}
Doug Stevenson
  • 297,357
  • 32
  • 422
  • 441
Mugetsu
  • 1,739
  • 2
  • 20
  • 41

1 Answers1

14

We don't allow loop constructs in Rules because we rely upon several optimization techniques that are much harder with more complex structures. We also don't charge compute time for Rules execution, which means we don't want them getting too complex and potentially abusive.

With the repetition in your rules, I'd highly encourage using the function() feature to simplify. For example this is < 1/3 the size:

match /emails/{emailId} {
    function attachments (){
      return request.resource.data.attachments();
    }

    function attach_cnt () {
      return attachments().size();
    }

    function valid_size(attach) {
      return attachments()[attach].fileSize < 3 * 1024 * 1024;
    }

    function valid_type(attach) {
      return (attachments()[attach].filetype == 'image/png' 
         || attachments()[attach].filetype == 'image/jpg'
         || attachments()[attach].filetype == 'application/vnd.ms-excel');
    }

    allow write: (attach_cnt() < 1 || (valid_size(0) && valid_type(0)))
    && (attach_cnt() < 2 || (valid_size(1) && valid_type(1)))
    && (attach_cnt() < 3 || (valid_size(2) && valid_type(2)))
    && (attach_cnt() < 4 || (valid_size(3) && valid_type(3)))
}

Here's how I simplified it (worth double-checking as I might have mistyped).

  1. I made a function attachments for the request data being accessed since it's used a bunched - this made it a lot easy skim the rules.
  2. I made a function attach_cnt for the number of attachments since that was checked a lot.
  3. Now I saw each attachment had a file size constraint, so I made a function valid_size for that test, using a parameter attach that I could pass it.
  4. Next up was function valid_type which worked the same way, but did the check to make sure it was a valid type.
  5. Now it was obvious that the same checks where being performed for attachment 0 for requests that had 2-4 items, etc. Reordering some of the logic enables you to only ever check each attachment once.
Dan McGrath
  • 41,220
  • 11
  • 99
  • 130
  • 23
    Feels weird to me how a supposedly superior product of RTDB has so many unaddressed limitations. Trying to enforce structural correctness is not only harder and more verbose due to a lack of a Bolt-like alternative, but also impossible at times (validating dynamic maps and arrays). Does Google have any plans in the roadmap to address these issues? – Alix Axel Mar 19 '19 at 00:06
  • @AlixAxel no solution yet to this! They have answered also here: https://stackoverflow.com/questions/64621560/is-there-any-for-loop-in-firebase-security-rules-for-realtime-database "it would lead to potentially unbounded run time". Another example here: https://stackoverflow.com/questions/51857305/how-to-validate-array-values-with-firestores-security-rules – Xao Aug 18 '23 at 17:15
  • I think you might have an edge case on the last item of attachments(), Lets assume 3. Then attach_cnt() < 4 = True, and (valid_size(3) && valid_type(3))) can potentially be false. Then True || False => True. To mitigate this issue, always add a last condition: && valid_size(attach_cnt()-1) && valid_type(attach_cnt()-1) – Xao Aug 19 '23 at 23:24