Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

FirebaseFirestoreException: PERMISSION_DENIED: Missing or insufficient permissions. #6443

Open
MattSkala opened this issue Nov 8, 2024 · 12 comments

Comments

@MattSkala
Copy link
Contributor

[REQUIRED] Step 2: Describe your environment

  • Android Studio version: 2024.2.1 Patch 1
  • Firebase Component: Firestore
  • Component version: Firebase BOM 33.4.0

[REQUIRED] Step 3: Describe the problem

Steps to reproduce:

We are seeing some users experiencing a crash with "io.grpc.StatusException - PERMISSION_DENIED: Missing or insufficient permissions." in Crashlytics. The full stack trace is pasted below.

We've been seeing this over the past few months over several Firebase SDK versions. It happens quite rarely and we are not able to reproduce it. It seems the exception is thrown in the Firestore background service, so we are not able to locate our code that could trigger this and there is no way to catch the exception.

Based on the analytics logs it seems to happen when we access the user's document in Firestore shortly after creating an account. On the first sigh it looks like a race condition between Firebase Auth and Firestore, but it doesn't seem to be on our side as we wait for FirebaseAuth.signInWithCredential task to complete or AuthStateListener to trigger before calling any Firestore methods.

      Fatal Exception: com.google.firebase.firestore.FirebaseFirestoreException: PERMISSION_DENIED: Missing or insufficient permissions.
       at com.google.firebase.firestore.util.Util.exceptionFromStatus(Util.java:113)
       at com.google.firebase.firestore.core.SyncEngine.notifyUser(SyncEngine.java:629)
       at com.google.firebase.firestore.core.SyncEngine.handleRejectedWrite(SyncEngine.java:511)
       at com.google.firebase.firestore.core.MemoryComponentProvider$RemoteStoreCallback.handleRejectedWrite(MemoryComponentProvider.java:135)
       at com.google.firebase.firestore.remote.RemoteStore.handleWriteError(RemoteStore.java:748)
       at com.google.firebase.firestore.remote.RemoteStore.handleWriteStreamClose(RemoteStore.java:704)
       at com.google.firebase.firestore.remote.RemoteStore.access$600(RemoteStore.java)
       at com.google.firebase.firestore.remote.RemoteStore$2.onClose(RemoteStore.java:218)
       at com.google.firebase.firestore.remote.AbstractStream.close(AbstractStream.java:365)
       at com.google.firebase.firestore.remote.AbstractStream.handleServerClose(AbstractStream.java:419)
       at com.google.firebase.firestore.remote.AbstractStream$StreamObserver.lambda$onClose$3(AbstractStream.java:160)
       at com.google.firebase.firestore.remote.AbstractStream$CloseGuardedRunner.run(AbstractStream.java:67)
       at com.google.firebase.firestore.remote.AbstractStream$StreamObserver.onClose(AbstractStream.java:146)
       at com.google.firebase.firestore.remote.FirestoreChannel$1.onClose(FirestoreChannel.java:173)
       at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java)
       at io.grpc.internal.ClientCallImpl.access$300(ClientCallImpl.java)
       at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:742)
       at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:723)
       at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
       at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:133)
       at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:487)
       at java.util.concurrent.FutureTask.run(FutureTask.java:264)
       at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:307)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
       at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor$DelayedStartFactory.run(AsyncQueue.java:235)
       at java.lang.Thread.run(Thread.java:1012)


 Caused by io.grpc.StatusException: PERMISSION_DENIED: Missing or insufficient permissions.
       at io.grpc.Status.asException(Status.java:541)
       at com.google.firebase.firestore.util.Util.exceptionFromStatus(Util.java)
       at com.google.firebase.firestore.core.SyncEngine.notifyUser(SyncEngine.java:629)
       at com.google.firebase.firestore.core.SyncEngine.handleRejectedWrite(SyncEngine.java:511)
       at com.google.firebase.firestore.core.MemoryComponentProvider$RemoteStoreCallback.handleRejectedWrite(MemoryComponentProvider.java:135)
       at com.google.firebase.firestore.remote.RemoteStore.handleWriteError(RemoteStore.java:748)
       at com.google.firebase.firestore.remote.RemoteStore.handleWriteStreamClose(RemoteStore.java:704)
       at com.google.firebase.firestore.remote.RemoteStore.access$600(RemoteStore.java)
       at com.google.firebase.firestore.remote.RemoteStore$2.onClose(RemoteStore.java:218)
       at com.google.firebase.firestore.remote.AbstractStream.close(AbstractStream.java:365)
       at com.google.firebase.firestore.remote.AbstractStream.handleServerClose(AbstractStream.java:419)
       at com.google.firebase.firestore.remote.AbstractStream$StreamObserver.lambda$onClose$3(AbstractStream.java:160)
       at com.google.firebase.firestore.remote.AbstractStream$CloseGuardedRunner.run(AbstractStream.java:67)
       at com.google.firebase.firestore.remote.AbstractStream$StreamObserver.onClose(AbstractStream.java:146)
       at com.google.firebase.firestore.remote.FirestoreChannel$1.onClose(FirestoreChannel.java:173)
       at io.grpc.internal.ClientCallImpl.closeObserver(ClientCallImpl.java)
       at io.grpc.internal.ClientCallImpl.access$300(ClientCallImpl.java)
       at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInternal(ClientCallImpl.java:742)
       at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1StreamClosed.runInContext(ClientCallImpl.java:723)
       at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
       at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:133)
       at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:487)
       at java.util.concurrent.FutureTask.run(FutureTask.java:264)
       at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:307)
       at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
       at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
       at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor$DelayedStartFactory.run(AsyncQueue.java:235)
       at java.lang.Thread.run(Thread.java:1012)
        
@milaGGL
Copy link
Contributor

milaGGL commented Nov 8, 2024

Hi @MattSkala, thank you for reporting this issue. Could you please enable debug level logging and see if it is possible to capture more info on the crash?

@google-oss-bot
Copy link
Contributor

Hey @MattSkala. We need more information to resolve this issue but there hasn't been an update in 5 weekdays. I'm marking the issue as stale and if there are no new updates in the next 5 days I will close it automatically.

If you have more information that will help us get to the bottom of this, just add a comment!

@MattSkala
Copy link
Contributor Author

Could you please enable debug level logging and see if it is possible to capture more info on the crash?

Hi, as mentioned above, unfortunately, we are not able to reproduce the issue locally. We just get crash reports from a small percentage (but still a considerable amount) of our users.

I can just add that the Crashlytics issue ID cf4e5375be64dd352e247f10f18c271c, and our project number 499050530164, in case it helps in any way.

@milaGGL
Copy link
Contributor

milaGGL commented Nov 18, 2024

Hi @MattSkala, unfortunately, I cannot access the crashlytics log. And the log provided indicates that it is failing to write into firestore (handleRejectedWrite) due to permissions issue. So high likely, it failed at the "creating an account" stage, not "accessing user's document " stage later.

It will be more efficient if you can file a firebase support ticket, provide the Crashlytics issue ID, project Id, and if possible a timestamp when the issue happened, so that firebase team/ backend team can get a closer look at the server logs.

Out of curiosity, what is the security rule the project is using?

@MattSkala
Copy link
Contributor Author

So high likely, it failed at the "creating an account" stage, not "accessing user's document " stage later.

I just checked that all the Crashlytics logs have the ID of the authenticated user attached, so it seems that the account was created successfully, but then it failed when writing the user document.

It will be more efficient if you can file a firebase support ticket, provide the Crashlytics issue ID, project Id, and if possible a timestamp when the issue happened, so that firebase team/ backend team can get a closer look at the server logs.

Thanks for the suggestion, I've also created a support ticket (Case 10321781).

Out of curiosity, what is the security rule the project is using?

We only allow the authenticated user to read their own document:

    match /users/{user_id} {
      allow read, write: if request.auth.uid == user_id;
    }

@milaGGL
Copy link
Contributor

milaGGL commented Nov 19, 2024

interesting, would it be possible to check "request.auth.uid == user_id" condition before sending the request? could it be one of the id is still null when the request is made?

@lehcar09
Copy link
Contributor

Hi @MattSkala, I'll mark this as needs-info for now. Don't worry if the issue closes due to stale, we can always reopen this once we have new information. Thanks!

@dryaz
Copy link

dryaz commented Dec 13, 2024

I have the same crash log and able to reproduce it on my side.
I have KMP+CMP project for android, web, ios (https://wesplit.app/)

Mobile apps crashses and WEB just log error into console.

The flow in my case

  • Login
  • Open group (create if none)
  • Go to profile (on web you'd have group details on the right pane but in mobile you'll also need to close screen)
  • Logout (you'll loose access to group mentioned above).

I use gitlive firebase lib to handle everything in common code but yet crash comes from underlying native impl.

Crash is happening when I use restricted resource during the session.
E.g. if you just open app, go to profile and logout -> no crash.

Seems like some internal sync engine creates some listeners in case I use possible resticted sources and there is no way to release it.

@MattSkala dunno about your product - check if you have logout flow. If so -> try to use restricted source and logout right away.

FATAL EXCEPTION: DefaultDispatcher-worker-2
 Process: app.wesplit, PID: 12297
 com.google.firebase.firestore.FirebaseFirestoreException: PERMISSION_DENIED: Missing or insufficient permissions.
 	at com.google.firebase.firestore.util.Util.exceptionFromStatus(Util.java:113)
 	at com.google.firebase.firestore.core.EventManager.onError(EventManager.java:247)
 	at com.google.firebase.firestore.core.SyncEngine.removeAndCleanupTarget(SyncEngine.java:642)
 	at com.google.firebase.firestore.core.SyncEngine.handleRejectedListen(SyncEngine.java:478)
 	at com.google.firebase.firestore.core.MemoryComponentProvider$RemoteStoreCallback.handleRejectedListen(MemoryComponentProvider.java:125)
 	at com.google.firebase.firestore.remote.RemoteStore.processTargetError(RemoteStore.java:596)
 	at com.google.firebase.firestore.remote.RemoteStore.handleWatchChange(RemoteStore.java:479)
 	at com.google.firebase.firestore.remote.RemoteStore.access$100(RemoteStore.java:60)
 	at com.google.firebase.firestore.remote.RemoteStore$1.onWatchChange(RemoteStore.java:188)
 	at com.google.firebase.firestore.remote.WatchStream.onNext(WatchStream.java:114)
 	at com.google.firebase.firestore.remote.WatchStream.onNext(WatchStream.java:38)
 	at com.google.firebase.firestore.remote.AbstractStream$StreamObserver.lambda$onNext$1$com-google-firebase-firestore-remote-AbstractStream$StreamObserver(AbstractStream.java:126)
 	at com.google.firebase.firestore.remote.AbstractStream$StreamObserver$$ExternalSyntheticLambda0.run(D8$$SyntheticClass:0)
 	at com.google.firebase.firestore.remote.AbstractStream$CloseGuardedRunner.run(AbstractStream.java:67)
 	at com.google.firebase.firestore.remote.AbstractStream$StreamObserver.onNext(AbstractStream.java:113)
 	at com.google.firebase.firestore.remote.FirestoreChannel$1.onMessage(FirestoreChannel.java:162)
 	at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1MessagesAvailable.runInternal(ClientCallImpl.java:667)
 	at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1MessagesAvailable.runInContext(ClientCallImpl.java:654)
 	at io.grpc.internal.ContextRunnable.run(ContextRunnable.java:37)
 	at io.grpc.internal.SerializingExecutor.run(SerializingExecutor.java:133)
 	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:487)
 	at java.util.concurrent.FutureTask.run(FutureTask.java:264)
 	at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:307)
 	at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
 	at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)
 	at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor$DelayedStartFactory.run(AsyncQueue.java:235)
 	at java.lang.Thread.run(Thread.java:1012)
 	Suppressed: kotlinx.coroutines.internal.DiagnosticCoroutineContextException: [StandaloneCoroutine{Cancelling}@f36ca15, Dispatchers.IO]
 Caused by: io.grpc.StatusException: PERMISSION_DENIED: Missing or insufficient permissions.
 	at io.grpc.Status.asException(Status.java:541)
 	at com.google.firebase.firestore.util.Util.exceptionFromStatus(Util.java:111)
 	... 26 more

@dconeybe
Copy link
Contributor

@dryaz Thank you for your report. Since you can reproduce the issue, could you enable Firestore debug logging, then reproduce, then capture the logcat output and post it? Unfortunately, the stack trace alone does not give enough context for us to investigate.

@dconeybe dconeybe self-assigned this Dec 17, 2024
@dryaz
Copy link

dryaz commented Dec 17, 2024

@dryaz Thank you for your report. Since you can reproduce the issue, could you enable Firestore debug logging, then reproduce, then capture the logcat output and post it? Unfortunately, the stack trace alone does not give enough context for us to investigate.

sure, here it is
fb.txt

My app package is app.wesplit

Just in case here is the struct of storage (screenshot) and rules:

rules_version = '2';

service cloud.firestore {
  match /databases/{database}/documents {
    match /users/{userId} {
      allow write, read, update: if userId == request.auth.uid;
      allow create: if request.auth.uid != null;
    }
  	match /groups/{documentId} {
      allow read, update: if request.auth.uid in resource.data.tokens || request.auth.token.groupId == documentId || request.resource.data.publicToken == resource.data.publicToken;
      allow create: if request.auth.uid != null;
      allow delete: if false;
    }
    // Specific rules for expenses under /groups/{documentId}
    match /groups/{documentId}/expenses/{expenseId} {
      allow read, create: if request.auth.uid in get(/databases/$(database)/documents/groups/$(documentId)).data.tokens
                       || request.auth.token.groupId == documentId;

      allow update, delete: if (request.auth.uid in get(/databases/$(database)/documents/groups/$(documentId)).data.tokens
                       || request.auth.token.groupId == documentId)
                     && (
                       !('protectedBy' in resource.data) || resource.data.protectedBy.size() == 0
                       || request.auth.uid in resource.data.protectedBy
                     );
    }
    match /fxrates/{documents=**} {
    	allow read: if request.auth.uid != null || request.auth.token.groupId != null;
      allow write, create, update: if false;
    }
    match /promo/{documents=**} {
      allow read, write, create, update: if false;
    }
  }
}

CleanShot 2024-12-17 at 22 06 23@2x

And here what happening when I open the screen of the group (after which logout causes crash):

    @OptIn(ExperimentalCoroutinesApi::class)
    fun refresh() =
        viewModelScope.launch {
            accountRepository.get().onEach {
                if (it is Account.Anonymous && token != null) {
                    // login with group token
                }
            }.distinctUntilChanged().flatMapLatest { account ->
                when (account) {
                    Account.Unknown,
                    Account.Anonymous,
                    -> flow { emit(State.Loading) }

                    is Account.Authorized,
                    Account.Restricted,
                    ->
                        groupRepository
                            .get(groupId, token)
                            ....
                }
            }.catch {
                _dataState.update {
                    State.Error(State.Error.Type.FETCH_ERROR)
                }
            }.collect {
                _dataState.value = it
            }
        }

@dryaz
Copy link

dryaz commented Dec 17, 2024

@dryaz Thank you for your report. Since you can reproduce the issue, could you enable Firestore debug logging, then reproduce, then capture the logcat output and post it? Unfortunately, the stack trace alone does not give enough context for us to investigate.

I've just realised that even though there is NO direct mention on my package in crash but it's still my mistake.

I've wrapped every single place where I subscribe for data with

.catch {
            analyticsManager.log(it)
        }

And this crash is gone. So basically when I open group the subscription to the data keeps alive*.
*I create viewmodel on navigation layer and provide it inside composable, so viewmodel for group lives a bit longer then composable.

Even so I've wrapped group info with this catch I still have nested components that subsribe for respective data (e.g. fxRates). So even so the crash is the same as for TS I bet that in both cases we both made same mistake and just keep listening for smth to which user loose access to. Most likely nothing need to be fixed on Firebase side.

@MattSkala
Copy link
Contributor Author

So even so the crash is the same as for TS I bet that in both cases we both made same mistake and just keep listening for smth to which user loose access to. Most likely nothing need to be fixed on Firebase side.

Thanks for the insight. It could be possible, but unfortunately it's very difficult to debug on our side as we have many Firestore listeners across the app and we can't reproduce the crash.

It would dramatically help if the error message was more descriptive and included the collection/document the app is trying to listen to. Is this something that would be feasible to do on SDK side?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants