Adam Jack
Adam Jack

Reputation: 590

"Missing or insufficient permissions" on newly created document's sub-collection

I developed this first Firestore application not really recognizing I'd not architected the permissions model, in fact I bolted it on later. (I am wide open to feedback on permissions best practices and/or how better to implement permissions rules, if that is the root of the problem.)

As a data model I have top level 'event' documents with 'note' documents in a collection below an event, and (after the fact) I created permission rules to allow editing notes if the user created the event. (See below.)

Now, when testing the code (on iOS) the application creates an event document, then soon afterwards attempts to add a snapshot listener to the notes sub-collection. It is receiving a "Missing or insufficient permissions" error (which, of course, it didn't when permissions were not enforced.)

This event create is done in a view controller listing all events, and then the snapshot listener query occurs in a pushed view controller (after a segue.) If I pop the child view controller then push it again, it works.

Confusingly, it also does not happen every time, it is intermittent. I wonder if there is some race condition occurring between permissions and the newly created event document. (If I add a brief interval before retry it works correctly that very next attempt.)

Firebase Firestore version 0.13.4, Swift 4, Xcode 10.0

(Edit: The effectively same code does not fail on Android, and also works when offline.)

The pertinent permissions are here:

// Access granted... via ownership
function accessGranted() {
  return resource.data.userId == request.auth.uid;
}
function accessGrantedTo(container,rootId) {
  return get(/databases/$(database)/documents/$(container)/$(rootId)).data.userId == request.auth.uid;
}

// Events...
match /events/{event} {
  allow read, update, delete: if accessGranted();
  allow create: if request.auth.uid != null;
}
match /events/{event}/notes/{note} {
  allow read, update, delete: if accessGrantedTo("events",event);
  allow create: if accessGrantedTo("events",event);
}

Upvotes: 2

Views: 521

Answers (1)

Adam Jack
Adam Jack

Reputation: 590

I explored this topic with Google Firebase Firestore support [4-6103000026011] over the last month. After a number of back and forth conversations about "the object doesn't yet exist, wait for callback, etc.” it was finally elevated to engineering and the final resolution is "works as designed". Personally, I consider non deterministic behavior (works sometimes, fails sometimes) based on device speed a race condition. That said, maybe my scenario is not supported, so I am documenting here in case it helps others.

As I see it ... Firestore might be locally complete after the method call returns from a storage perspective, but its rules implementation is not. This all worked fine until I added the permissions rules, then things became non deterministic.

Every object created / listened to here is created by the same client in that client locally, yet fails for permissions reasons but works "after a moment/retry".

Bear in mind that maybe this is a tough scenario, I may well have created the problem through my requirements and choice of rules. Note:

  • I cannot leverage Firestore callbacks since they only fire when the device is online and the data has been synchronized to the server. That and (I read) Firestore is locally complete after method return, i.e. the objects exists after the method returns so "no need to wait for callback". [IMHO: Callbacks are of limited value in the real world (especially the intermittently online world of mobile devices) and I use them merely out of curiosity, for troubleshooting. I saw them in the method specification, failed to dig in to their timing, and used them assuming they were for the normal use case and got bitten when offline … and they didn’t get called. I assume I’m not the only developer that this is true for.] Note: I'm also not sure if the callbacks "solve" the problem only by taking some finite time, not by what internal actions they represent.
  • I recognize these rules are "interesting" (using functions to walk up the hierarchy) however I created them using Firestore's documentation and examples. (I've chosen not to simplify them, as support has suggested by putting an owner property on the lower object, 'cos in fact I was hoping to extend them further to meet my business case … allowing teams to work on the data, not just the creator.)
  • I do recognize that this code is creating object X then immediately attempting to listen to updates on a sub-collection of X that I did not create, that is (I assume) created for me. I did ask support if there were a way I could create that sub-collection to remove any race condition there, but that was not provided as a resolution. (That said, this works without problem when these permissions rules are not involved, so it “exists”.)

Long story short I added a seriously bugly “retry (a limited number of times) if listening to something the code just created and it gets a permissions failure error” … and the code seems to succeed on the second try. Bugly, and I’m not proud, but “working”.

BTW: I did create a standalone example to reproduce this. It is iOS (although Android fails also, and fails most reliably on the automated testing virtual machines Android provides.)

Code: Standalone iOS code - Github Repository

Rules: Rule Set - Github Gist

Upvotes: 2

Related Questions