FirebaseFirestoreException: PERMISSION_DENIED: Missing or insufficient permissions

Viewed 1127

I am having an issue with our app being able to read from firebase firestore. We have one app with 2 variations release and debug. It was working fine and I haven't changed anything.

The production/release is working fine.

I was working on a separate app that uses the same firestore and that one is also working fine(not yet released).

I googled the problem and most of them led me here to SO, but almost all the answers are to change the security rules to:

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
      allow read, write: if true;
    }
  }
}

which is insecure. Our rules are set to:

service cloud.firestore {
  match /databases/{database}/documents {
    match /{document=**} {
            allow read;
            allow write: if request.auth != null;
    }
  }
}

FBAuthUI.kt

class FBAuthUI : AppCompatActivity() {

    private val COLLECTION_PATH_USERS = "Users"
    private val RC_SIGN_IN: Int = 100
    private val RC_SIGN_UP: Int = 200
    private val TAG = "AuthUiActivity"

    private val TOS_URL = ""
    private val PRIVACY_POLICY_URL = ""


    // Choose authentication providers
    private val providers = arrayListOf(
            AuthUI.IdpConfig.EmailBuilder().build(),
            AuthUI.IdpConfig.GoogleBuilder().build(),
            AuthUI.IdpConfig.FacebookBuilder().build())


    private var userRef: CollectionReference? = null


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)


        // Create and launch sign-in intent
        startActivityForResult(
                AuthUI.getInstance()
                        .createSignInIntentBuilder()
                        .setAvailableProviders(providers)
                        .setLogo(R.drawable.logo_red)
                        .setTheme(R.style.SignInTheme)
                        .setTosAndPrivacyPolicyUrls(
                                TOS_URL,
                                PRIVACY_POLICY_URL)
                        .build(),
                RC_SIGN_IN)
    }


    override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
        super.onActivityResult(requestCode, resultCode, data)
        Log.i(TAG, requestCode.toString())
        if (requestCode == RC_SIGN_IN) {
            val idpResponse = IdpResponse.fromResultIntent(data)
            if (resultCode == Activity.RESULT_OK) {
                try {
                    if (idpResponse?.isNewUser() == true) {
            //signup code
            …
                    } else {
                        startActivity(Intent(this, MainActivity::class.java))
                    }
                } catch (e: JSONException) {
                    e.printStackTrace()
                }
            }else {
                // Sign in failed. If response is null the user canceled the
                // sign-in flow using the back button. Otherwise check
                // response.getError().getErrorCode() and handle the error.
                if (idpResponse != null) {
                    Log.d(TAG, idpResponse.error?.errorCode.toString())
                    Toast.makeText(this, "Unable to sign in. Please try again later", Toast.LENGTH_LONG).show()
                } else {
                    startActivity(Intent(this, LandingPageActivity::class.java))
                }
            }
        }
}

MainActivity.kt

class MainActivity : AppCompatActivity(),{
…

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        val homeFrag: Fragment = Home()
        val searchFrag: Fragment = SearchFragment()
        val profileFrag: Fragment = ProfileFragment()
        val bottomNav = findViewById<BottomNavigationView>(R.id.bottom_navigation)

        bottomNav.setOnNavigationItemSelectedListener { item ->
            when (item.itemId) {
                R.id.home_nav -> {
                        commitFragment(homeFrag)
                }
                R.id.nav_search -> {
                    commitFragment(searchFrag)
                }
                R.id.profile_nav -> {
                        commitFragment(profileFrag)
                }
                else -> {
                        commitFragment(homeFrag)
                }
            }
            true
        }

        if (savedInstanceState == null) {
            bottomNav.selectedItemId = R.id.home_nav
        }

        setSupportActionBar(toolbar)
        supportActionBar!!.setDisplayShowTitleEnabled(false)
    }

    private fun commitFragment(fragment: Fragment) {
        val fm = supportFragmentManager
        val fragTransition = fm.beginTransaction().addToBackStack(fragment.toString())
        fragTransition.replace(R.id.container, fragment).commit()
    }

    companion object {
        var bottomNav: BottomNavigationView? = null
        var CONTEXT: Context? = null
        private val TAG = MainActivity::class.java.simpleName
    }
}

SearchFragment.java

public class SearchFragment extends Fragment {
    private static final String COLLECTION_PATH_VENDORS = “BookStores”;

    private SearchAdapter searchAdapter;
    private ArrayList<BookStore> bookstores;
    private String searchString;
    private ImageView searchImage;
    private EditText searchET;
    Boolean clickedCategories;
    ArrayList<BookStore> bkstore = new  ArrayList<>();
    ArrayList<String> list = new  ArrayList<>();

    private RecyclerView recyclerView;
    private DatabaseReference dbRef;
    private CollectionReference bookStoreRef;
    private String category;


    @Override
    public View onCreateView(final LayoutInflater inflater, ViewGroup container,
                             Bundle savedInstanceState) {
        //Initialize view
        View fragmentView = inflater.inflate(R.layout.fragment_search, container, false);

        recyclerView = fragmentView.findViewById(R.id.sr_recycler);
        recyclerView.hasFixedSize();
        recyclerView.setLayoutManager(new LinearLayoutManager(getContext()));
        recyclerView.addItemDecoration(new DividerItemDecoration(recyclerView.getContext(),
                DividerItemDecoration.VERTICAL));

        // Obtain Cloud Firestore instance
        FirebaseFirestore db = FirebaseFirestore.getInstance();
        bookStoreRef = db.collection(COLLECTION_PATH_VENDORS);

        searchET = getActivity().findViewById(R.id.search_box);
        searchET.setCursorVisible(true);
        displayAll();


        SearchAdapter searchAdapter = new SearchAdapter(getContext(), bkstore);
        recyclerView.setAdapter(searchAdapter);

        return fragmentView;

    }

    private void displayAll() {
        Log.d("UserID Tag ", FirebaseAuth.getInstance().getCurrentUser().getUid());
        bookStoreRef.get().addOnCompleteListener(task -> {
            if (task.isSuccessful()) {
                for (QueryDocumentSnapshot document : task.getResult()) {

                    final BookStore aBookStore = new Bookstore();

                    aBookStore.setName(document.getString("Name"));
                   aBookStore.setCategory(document.getString("Category"));
                   aBookStore.setDescription(document.getString("Description"));
                   aBookStorek.setImage(document.getString("Image"));
                    aBookStore.setID(document.getId());

                    bkstore.add(DisplayResults(document));
                    recyclerView.removeAllViews();
                }
            } else {
                Log.w(TAG, "Error getting documents.", task.getException());
            }
        });
    }

    private BookStore DisplayResults(QueryDocumentSnapshot document){
        BookStore aBookStore = new BookStore();

        aBookStore.setName(document.getString("Name"));
                   aBookStore.setCategory(document.getString("Category"));
                   aBookStore.setDescription(document.getString("Description"));
                   aBookStorek.setImage(document.getString("Image"));
                    aBookStore.setID(document.getId());

        return aBookStore.;
    }

}

Log returns:

D/UserID Tag: E4ARmAfe7aVG8oRiizHqWJatZlY2
W/Firestore: (24.0.0) [Firestore]: Listen for Query(target=Query(BookStores order by __name__);limitType=LIMIT_TO_FIRST) failed: Status{code=PERMISSION_DENIED, description=Missing or insufficient permissions., cause=null}
W/ContentValues: Error getting documents.
    com.google.firebase.firestore.FirebaseFirestoreException: PERMISSION_DENIED: Missing or insufficient permissions.
        at com.google.firebase.firestore.util.Util.exceptionFromStatus(Util.java:117)
        at com.google.firebase.firestore.core.EventManager.onError(EventManager.java:166)
        at com.google.firebase.firestore.core.SyncEngine.removeAndCleanupTarget(SyncEngine.java:588)
        at com.google.firebase.firestore.core.SyncEngine.handleRejectedListen(SyncEngine.java:424)
        at com.google.firebase.firestore.core.MemoryComponentProvider$RemoteStoreCallback.handleRejectedListen(MemoryComponentProvider.java:104)
        at com.google.firebase.firestore.remote.RemoteStore.processTargetError(RemoteStore.java:577)
        at com.google.firebase.firestore.remote.RemoteStore.handleWatchChange(RemoteStore.java:461)
        at com.google.firebase.firestore.remote.RemoteStore.access$100(RemoteStore.java:53)
        at com.google.firebase.firestore.remote.RemoteStore$1.onWatchChange(RemoteStore.java:176)
        at com.google.firebase.firestore.remote.WatchStream.onNext(WatchStream.java:109)
        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:119)
        at com.google.firebase.firestore.remote.AbstractStream$StreamObserver$$ExternalSyntheticLambda3.run(Unknown Source:4)
        at com.google.firebase.firestore.remote.AbstractStream$CloseGuardedRunner.run(AbstractStream.java:67)
        at com.google.firebase.firestore.remote.AbstractStream$StreamObserver.onNext(AbstractStream.java:110)
        at com.google.firebase.firestore.remote.FirestoreChannel$1.onMessage(FirestoreChannel.java:131)
        at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1MessagesAvailable.runInternal(ClientCallImpl.java:656)
        at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1MessagesAvailable.runInContext(ClientCallImpl.java:641)
        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:462)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor$DelayedStartFactory.run(AsyncQueue.java:234)
        at java.lang.Thread.run(Thread.java:923)
     Caused by: io.grpc.StatusException: PERMISSION_DENIED: Missing or insufficient permissions.
        at io.grpc.Status.asException(Status.java:543)
        at com.google.firebase.firestore.util.Util.exceptionFromStatus(Util.java:115)
        at com.google.firebase.firestore.core.EventManager.onError(EventManager.java:166) 
        at com.google.firebase.firestore.core.SyncEngine.removeAndCleanupTarget(SyncEngine.java:588) 
        at com.google.firebase.firestore.core.SyncEngine.handleRejectedListen(SyncEngine.java:424) 
        at com.google.firebase.firestore.core.MemoryComponentProvider$RemoteStoreCallback.handleRejectedListen(MemoryComponentProvider.java:104) 
        at com.google.firebase.firestore.remote.RemoteStore.processTargetError(RemoteStore.java:577) 
        at com.google.firebase.firestore.remote.RemoteStore.handleWatchChange(RemoteStore.java:461) 
        at com.google.firebase.firestore.remote.RemoteStore.access$100(RemoteStore.java:53) 
        at com.google.firebase.firestore.remote.RemoteStore$1.onWatchChange(RemoteStore.java:176) 
        at com.google.firebase.firestore.remote.WatchStream.onNext(WatchStream.java:109) 
        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:119) 
        at com.google.firebase.firestore.remote.AbstractStream$StreamObserver$$ExternalSyntheticLambda3.run(Unknown Source:4) 
        at com.google.firebase.firestore.remote.AbstractStream$CloseGuardedRunner.run(AbstractStream.java:67) 
        at com.google.firebase.firestore.remote.AbstractStream$StreamObserver.onNext(AbstractStream.java:110) 
        at com.google.firebase.firestore.remote.FirestoreChannel$1.onMessage(FirestoreChannel.java:131) 
        at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1MessagesAvailable.runInternal(ClientCallImpl.java:656) 
        at io.grpc.internal.ClientCallImpl$ClientStreamListenerImpl$1MessagesAvailable.runInContext(ClientCallImpl.java:641) 
        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:462) 
        at java.util.concurrent.FutureTask.run(FutureTask.java:266) 
        at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301) 
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167) 
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641) 
        at com.google.firebase.firestore.util.AsyncQueue$SynchronizedShutdownAwareExecutor$DelayedStartFactory.run(AsyncQueue.java:234) 
        at java.lang.Thread.run(Thread.java:923) 
V/FA: Inactivity, disconnecting from the service
W/Firestore: (24.0.0) [WatchStream]: (9a4fb5d) Stream closed with status: Status{code=CANCELLED, description=Disconnecting idle stream. Timed out waiting for new targets., cause=null}.

I am lost as to what the issue could be. I even tried changing the security rule but still got the missing permissions error.

Anyone have any other suggestions?

3 Answers

Firestore security rules

  1. Open the Firebase console, and click your project.

  2. Click Firestore on the left-hand side of the screen.

  3. On the Firestore page, click the Rules tab.

  4. Replace the existing rules with the following rule:


rules_version = '2';
service cloud.firestore {
 match /databases/{database}/documents { 
  match /customers/{customerID} {
  allow read:
   if request.auth.uid != null
     && request.auth.token.firebase.sign_in_provider == "google.com"
     && request.auth.token.email == customerID
    }
  }
}

This rule grants read-only permission for the customers collection. The rule verifies that you have signed in through the Google sign-in provider that you configured in Identity Platform. It also makes sure that you can only retrieve documents with a customer ID that matches your email address.

Note: Requests to Firestore are always evaluated against the database security rules, whether the request is for retrieving a single document, or for executing a query.

  1. Click Publish.

You have two separate clauses here:

allow read;
allow write: if request.auth != null;

For write you require the user to be authentication, but for reading you specify no condition, which apparently means reading it not allowed.

My guess is that you want the if clause to apply to reads and writes, which you can do with:

allow read, write: if request.auth != null;

I'm not sure what the underlying issue was but after deleting google-services.json and re-downloading it, the issue seems to be resolved.

Related