I was able to read data from the firestore emulator but cannot add any data to it throught the client which is an android app.
This is the error:
W/Firestore: (24.0.0) [Firestore]: Write failed at Users/vBLloPGsMJrXGDZdZcVO: Status{code=PERMISSION_DENIED, description=Missing or insufficient permissions., cause=null}
My firestore.rules file in local environment
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, write: if true;
}
}
}
This is the code used to write to firestore
FirebaseFirestore.getInstance().collection("Users")
.document(userId)
.set(newUser)
.addOnCompleteListener(new OnCompleteListener<Void>() {
#Override
public void onComplete(#NonNull Task<Void> task) {
if (task.isSuccessful()){
Log.d(TAG, "new user has been added");
}else {
Log.d(TAG, "Failed to add new user"+Objects.requireNonNull(task.getException()).getMessage());
}
}
});
Looks like you're trying to update a document (different from writing one). Try to add "update" in your firestore security rules like so:
allow read, get, update: if true;
Firstly, I found numerous answeres to this question proposing to remove autentication in Firestore rule. Certainly it is not what I want.
My Firestore rule is
rules_version = '2';
service cloud.firestore {
match /databases/{database}/documents {
match /{document=**} {
allow read, update, write: if request.auth.uid != null;
}
}
}
And I do want keep some level of authorization.
My Androoid code is:
import android.os.Bundle
import android.util.Log
import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.firestore.FirebaseFirestore
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val db = FirebaseFirestore.getInstance()
val TAG = "MainActivity"
db.collection("transfer")
.get()
.addOnSuccessListener { result ->
for (document in result) {
Log.d(TAG, "${document.id} => ${document.data}")
}
}
.addOnFailureListener { exception ->
Log.w(TAG, "Error getting documents.", exception)
}
}
}
Android project contains google-service.json download straight from Firestore
{
"project_info": {
"project_number": "7953 *** 50",
"firebase_url": "https://firetestjimis.firebaseio.com",
"project_id": "firetestjimis",
"storage_bucket": "firetestjimis.appspot.com"
},
"client": [
{
"client_info": {
"mobilesdk_app_id": "1:795 *** bb2ec8dc810f",
"android_client_info": {
"package_name": "com.example.demo"
}
},
"oauth_client": [
{
"client_id": "7953 **** sdu68ociuueir.apps.googleusercontent.com",
"client_type": 3
}
],
"api_key": [
{
"current_key": "AIza **** qHqOyrBsr_0cs_uq8"
}
],
"services": {
"appinvite_service": {
"other_platform_oauth_client": [
{
"client_id": "795 **** rkemu101gg0o.apps.googleusercontent.com",
"client_type": 3
}
]
}
}
}
],
"configuration_version": "1"
}
My final goal is achieve the same I have done with Angular but with Android/Kotlin. Just to exemplify, my working Angular code is:
app.component.ts
import { Component } from '#angular/core';
import { Observable } from 'rxjs';
import { AngularFirestore, AngularFirestoreCollection } from '#angular/fire/firestore';
import { HttpClient, HttpHeaders } from '#angular/common/http';
import { map } from 'rxjs/operators';
import 'rxjs/Rx';
import { AngularFireAuth } from '#angular/fire/auth';
#Component({
selector: 'app-root',
templateUrl: './app.component.html'
})
export class AppComponent {
public transfers: Observable<any[]>;
transferCollectionRef: AngularFirestoreCollection<any>;
constructor(public auth: AngularFireAuth, public db: AngularFirestore) {
this.listenSingleTransferWithToken();
}
async listenSingleTransferWithToken() {
await this.auth.signInWithCustomToken("eyJ **** a custom token **** bdaG1Q");
this.transferCollectionRef = this.db.collection<any>('transfer', ref => ref.where("id", "==", "1"));
this.transfers = this.transferCollectionRef.snapshotChanges().map(actions => {
return actions.map(action => {
const data = action.payload.doc.data();
const id = action.payload.doc.id;
return { id, ...data };
});
});
environment.ts
export const environment = {
production: false,
firebaseConfig: {
apiKey: "AI yyy ihK3xs",
authDomain: "firetestjimis.firebaseapp.com",
databaseURL: "https://firetestjimis.firebaseio.com",
projectId: "firetestjimis",
storageBucket: "firetestjimis.appspot.com",
messagingSenderId: "79 www 350",
appId: "1:xxx dc810f"
}
};
}
So my straight question is: what I am missing to make above kotlin code atenticate properly? Maybe it is the same idea with Angular and I just have to add a custom token during call but how do it in Android/kotlin?
*** EDITED
NodeJs server providing a CustomToken suucessfully working in above Angular but I miss how do the same in Android/Kotlin
const admin = require('firebase-admin');
exports.serviceAccount = {
"type": "service_account",
"project_id": "firetestjimis",
"private_key_id": "ecfc6 *** e6661fd05923",
"private_key": "-----BEGIN PRIVATE KEY-----\ *** REMOVED *** ==\n-----END PRIVATE KEY-----\n",
"client_email": "firebase-adminsdk-fg6p9#firetestjimis.iam.gserviceaccount.com",
"client_id": "1024 *** 38150",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-fg6p9%40firetestjimis.iam.gserviceaccount.com"
}
admin.initializeApp({
credential: admin.credential.cert(exports.serviceAccount)
});
var uid = "NSB *** 4DRo2"; //copied from https://console.firebase.google.com/project/firetestjimis/authentication/users
var claim = {
control: true
};
admin.auth().createCustomToken(uid)
.then(function (customToken) {
console.log(customToken)
})
.catch(function (error) {
console.log("Error creating custom token:", error);
});
*** edited
Here is my current solution. Hopefully it can help future readers. Please, take in account it is my first project both in Firestore as in Android/Kotlin. If you see some weird approach bellow please let me know it.
package com.example.demo
//https://developer.android.com/training/basics/firstapp/starting-activity
//https://firebase.google.com/docs/firestore/quickstart#kotlin+ktx
// Tutorial to CustomToken
//https://firebase.google.com/docs/auth/android/custom-auth#kotlin+ktx
//Tutorial to Snatshot Listeners and its queries
//https://firebase.google.com/docs/firestore/query-data/listen#kotlin+ktx
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.google.firebase.auth.FirebaseAuth
import com.google.firebase.firestore.FirebaseFirestore
class MainActivity : AppCompatActivity() {
lateinit var auth: FirebaseAuth
override fun onCreate(savedInstanceState: Bundle?) {
val TAG = "MainActivity"
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
auth = FirebaseAuth.getInstance()
auth.signInWithCustomToken("ey *** uHUrg")
.addOnCompleteListener(this) { task ->
if (task.isSuccessful) {
Log.d(TAG, "*** signInWithCustomToken:success")
} else {
// If sign in fails, display a message to the user.
Log.w(TAG, "signInWithCustomToken:failure", task.exception)
Toast.makeText(
baseContext, "Authentication failed.",
Toast.LENGTH_SHORT
).show()
}
}
//val db = FirebaseFirestore.getInstance()
FirebaseFirestore.getInstance().collection("transfer")
.whereEqualTo("id", "1")
.addSnapshotListener { value, e ->
if (e != null) {
Log.w(TAG, "Listen failed.", e)
return#addSnapshotListener
}
val transfer = ArrayList<String>()
for (doc in value!!) {
doc.getString("status")?.let {
transfer.add(it)
}
}
// for (document in value) {
// Log.d(TAG, "${document.id} => ${document.data}")
// }
Log.d(TAG, "*** transfer: $transfer")
}
}
}
In your Firestore rules change from
allow read, update, write: if request.auth.uid != null;
to
allow read, write: if true;
this will fix the crash and missing permissions
Every time the user retrieves the Firebase Realtime Database messages, he would like only the last 50 messages from the messaging node to be retrieved (read) through the Realtime Database rules. How to do this?
Message node structure:
+ chats
+ regionChat (ex: eua)
+ idChat (ex: 534854923)
+ messages
+ idMessage1
+ idMessage2
+ idMessage3
+ idMessage4
+ idMessage5
...
I saw this in the firebase documentation, but I can't adapt my data structure:
messages: {
".read": "query.orderByKey &&
query.limitToFirst <= 50"
}
At the moment my rules are like this:
{
"rules": {
".read": true,
".write": true
}
}
you need to enhance your "query"
this should work
{
"rules": {
".read": "query.limitToFirst <= 50",
".write": true
}
}
Or, instead of using the rules, you can do it via Query.
DatabaseReference myReference = FirebaseDatabase.getInstance().getReference();
myReference = myReference().child("chats")...child("messages");
Query query = myReference.orderByID().limitToFirst(20).addListenerForSingleValueEvent(new ValueEventListener() {
#Override
public void onDataChange(DataSnapshot dataSnapshot) {
if (dataSnapshot.exists()) {
//
for (DataSnapshot issue : dataSnapshot.getChildren()) {
// do something with the data
}
}
}
#Override
public void onCancelled(DatabaseError databaseError) {
}
});
Performing the get() function in security rules is not working.
It returns permission denied on the client, but passes in simulation.
The config/permissions layout is an array structure:
config/permissions-->
---------------------------> CollectionName1 :
----------------------------------------------------> 0 : UID1
----------------------------------------------------> 1 : UID2
---------------------------> CollectionName2 :
----------------------------------------------------> 0 : UID3
----------------------------------------------------> 1 : UID4
I also tried to use single key/value fields in the config/permissions as so
config/permissions-->
---------------------------> CollectionName1 : UID1
---------------------------> CollectionName2 : UID3
with the rule
allow read: if request.auth.uid == get(/config/permissions).data[c] and this passed simulation and failed on the app. If I hardcode the UID instead of request.auth.uid it gives the same result.
UID is definitely correct on the app. This was tested by using the following rule, where it passed in simulation AND the app.
allow read: if request.auth.uid == 'USER_ID_HERE'
and by comparing the logcat output of the UID to the one above.
Please help. This is the Nth day of trying to find a suitable way to structure and query Firestore. I'm certain this is an issue with either the get() call or the way I am writing the call.
Android Code:
FirebaseFirestore db = FirebaseFirestore.getInstance();
FirebaseFirestoreSettings settings = new FirebaseFirestoreSettings.Builder()
.setTimestampsInSnapshotsEnabled(true)
.build();
db.setFirestoreSettings(settings);
Log.d("UID", FirebaseAuth.getInstance().getCurrentUser().getUid());
DocumentReference docRef = db.collection(collection).document("X153#111");
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
Log.d("FIREBASE", "DocumentSnapshot data: " + document.getData());
} else {
Log.d("FIREBASE", "No such document");
}
} else {
Log.d("FIREBASE", "get failed with ", task.getException());
}
}
});
I have a rule which executes correctly in Firestore Rules simulation as seen below.
The /config/permissions document is many arrays named X153, X154, X155, etc., which contain UIDs:
When I attempt this access in Android, I get a PERMISSION_DENIED response.
Code:
DocumentReference docRef = db.collection("arcs").document("X153");
docRef.get().addOnCompleteListener(new OnCompleteListener<DocumentSnapshot>() {
#Override
public void onComplete(#NonNull Task<DocumentSnapshot> task) {
if (task.isSuccessful()) {
DocumentSnapshot document = task.getResult();
if (document.exists()) {
Log.d("FIREBASE", "DocumentSnapshot data: " + document.getData());
} else {
Log.d("FIREBASE", "No such document");
}
} else {
Log.d("FIREBASE", "get failed with ", task.getException());
}
}
});
The UID used in simulation is the same as in Android:
If I set the rule to authenticate access of the UID directly
Android permission accepted, returns document.
If I flatten out the config/permissions structure to just key/values, like X153 : '9iXQBaG3Ycaey4cFUj8tZjhKMaB3', and change the rule to
match /arcs/{x} {
allow read: if request.auth.uid == get(/config/permissions).data[x];
}
Android returns PERMISSION DENIED.
Why am I receiving this PERMISSION DENIED response using the rule pictured?