Vinay Nagaraj Vinay Nagaraj - 2 months ago 10
Android Question

Firebase permission denied randomly

I had my security rules set up and everything was working fine yesterday. I've changed nothing since but today I keep getting "Permission denied" when I try to run this transaction on "styles/$styleId/likeInfo":

09-24 11:50:36.491 25355-25379/in.nyuyu.android.debug D/RepoOperation: onDataUpdate: /styles/46/likeInfo
09-24 11:50:36.491 25355-25379/in.nyuyu.android.debug D/RepoOperation: onDataUpdate: /styles/46/likeInfo {likeCount=0, filters={1=medium_casual_male/46, 0=all_casual_male/46}, likeModified=0}
09-24 11:50:36.491 25355-25379/in.nyuyu.android.debug D/WebSocket: ws_1 - Reset keepAlive. Remaining: 44997
09-24 11:50:36.491 25355-25379/in.nyuyu.android.debug D/WebSocket: ws_1 - HandleNewFrameCount: 1
09-24 11:50:36.491 25355-25379/in.nyuyu.android.debug D/WebSocket: ws_1 - handleIncomingFrame complete frame: {d={b={s=ok, d={}}, r=9}, t=d}
09-24 11:50:36.491 25355-25379/in.nyuyu.android.debug D/Connection: conn_1 - received data message: {b={s=ok, d={}}, r=9}
09-24 11:50:36.491 25355-25379/in.nyuyu.android.debug D/WebSocket: ws_1 - Reset keepAlive. Remaining: 44998
09-24 11:50:36.491 25355-25379/in.nyuyu.android.debug D/WebSocket: ws_1 - HandleNewFrameCount: 1
09-24 11:50:36.491 25355-25379/in.nyuyu.android.debug D/WebSocket: ws_1 - handleIncomingFrame complete frame: {d={b={s=datastale, d=Transaction hash does not match}, r=10}, t=d}
09-24 11:50:36.491 25355-25379/in.nyuyu.android.debug D/Connection: conn_1 - received data message: {b={s=datastale, d=Transaction hash does not match}, r=10}
09-24 11:50:36.491 25355-25379/in.nyuyu.android.debug D/PersistentConnection: pc_0 - p response: {s=datastale, d=Transaction hash does not match}
09-24 11:50:36.491 25355-25379/in.nyuyu.android.debug D/EventRaiser: Raising 1 event(s)
09-24 11:50:36.491 25355-25355/in.nyuyu.android.debug D/EventRaiser: Raising /styles/46/likeInfo: VALUE: {likeCount=1, likeModified=1474732236378, filters={1=medium_casual_male/46, 0=all_casual_male/46}}
09-24 11:50:36.491 25355-25379/in.nyuyu.android.debug D/Connection: conn_1 - Sending data: {d={b={h=ngNr+btv0R3q08ZQFhaa7gdToL4=, d={likeCount=1, likeModified={.sv=timestamp}, filters={1=medium_casual_male/46, 0=all_casual_male/46}}, p=styles/46/likeInfo}, r=11, a=p}, t=d}
09-24 11:50:36.491 25355-25379/in.nyuyu.android.debug D/WebSocket: ws_1 - Reset keepAlive. Remaining: 44996
09-24 11:50:36.781 25355-25424/in.nyuyu.android.debug D/WebSocket: ws_1 - ws message: {"t":"d","d":{"r":11,"b":{"s":"permission_denied","d":"Permission denied"}}}
09-24 11:50:36.781 25355-25379/in.nyuyu.android.debug D/WebSocket: ws_1 - Reset keepAlive. Remaining: 44711
09-24 11:50:36.781 25355-25379/in.nyuyu.android.debug D/WebSocket: ws_1 - HandleNewFrameCount: 1
09-24 11:50:36.781 25355-25379/in.nyuyu.android.debug D/WebSocket: ws_1 - handleIncomingFrame complete frame: {d={b={s=permission_denied, d=Permission denied}, r=11}, t=d}
09-24 11:50:36.781 25355-25379/in.nyuyu.android.debug D/Connection: conn_1 - received data message: {b={s=permission_denied, d=Permission denied}, r=11}
09-24 11:50:36.781 25355-25379/in.nyuyu.android.debug D/PersistentConnection: pc_0 - p response: {s=permission_denied, d=Permission denied}
09-24 11:50:36.781 25355-25379/in.nyuyu.android.debug W/RepoOperation: Transaction at /styles/46/likeInfo failed: DatabaseError: Permission denied
09-24 11:50:36.781 25355-25379/in.nyuyu.android.debug D/EventRaiser: Raising 1 event(s)
09-24 11:50:36.781 25355-25355/in.nyuyu.android.debug D/EventRaiser: Raising /styles/46/likeInfo: VALUE: {likeCount=0, likeModified=0, filters={1=medium_casual_male/46, 0=all_casual_male/46}}
09-24 11:50:36.781 25355-25379/in.nyuyu.android.debug D/PersistentConnection: pc_0 - unlistening on styles/46/likeInfo (params: {})
09-24 11:50:36.781 25355-25379/in.nyuyu.android.debug D/PersistentConnection: pc_0 - removing query styles/46/likeInfo (params: {})
09-24 11:50:36.781 25355-25379/in.nyuyu.android.debug D/Connection: conn_1 - Sending data: {d={b={p=styles/46/likeInfo}, r=12, a=n}, t=d}
09-24 11:50:36.781 25355-25379/in.nyuyu.android.debug D/WebSocket: ws_1 - Reset keepAlive. Remaining: 44997
09-24 11:50:36.781 25355-25355/in.nyuyu.android.debug D/SwipeListener: User: com.google.android.gms.internal.zzafu@a507f090
09-24 11:50:36.781 25355-25355/in.nyuyu.android.debug E/SwipeListener: Firebase Database error: Permission denied
com.google.firebase.database.DatabaseException: Firebase Database error: Permission denied
at com.google.firebase.database.DatabaseError.toException(Unknown)
at in.nyuyu.android.commons.Rx$4.onComplete(Rx.java:88)
at com.google.android.gms.internal.zzahq$11.run(Unknown)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5017)
at java.lang.reflect.Method.invokeNative(Native Method)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:779)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:595)
at dalvik.system.NativeStart.main(Native Method)
09-24 11:50:37.021 25355-25424/in.nyuyu.android.debug D/WebSocket: ws_1 - ws message: {"t":"d","d":{"r":12,"b":{"s":"ok","d":""}}}
09-24 11:50:37.021 25355-25379/in.nyuyu.android.debug D/WebSocket: ws_1 - Reset keepAlive. Remaining: 44762
09-24 11:50:37.021 25355-25379/in.nyuyu.android.debug D/WebSocket: ws_1 - HandleNewFrameCount: 1
09-24 11:50:37.021 25355-25379/in.nyuyu.android.debug D/WebSocket: ws_1 - handleIncomingFrame complete frame: {d={b={s=ok, d=}, r=12}, t=d}
09-24 11:50:37.021 25355-25379/in.nyuyu.android.debug D/Connection: conn_1 - received data message: {b={s=ok, d=}, r=12}


My security rules:

{
"rules": {
".read": "auth != null",
"sessions":{
"$uid": {
".write": "$uid === auth.uid"
}
},
"styles":{
"$styleid":{
"likeInfo": {
".write": "auth!= null",
"filters":{
".validate": "newData.val() == data.val()"
},
"likeCount": {
".validate": "newData.isNumber() && newData.val() == data.val()+1"
},
"likeModified": {
".validate": "newData.val() == now"
}
}
}
},
"stylefilters":{
"$filterCombo":{
"$styleId":{
"likeCount": {
".write": "auth!= null",
".validate": "newData.isNumber() && newData.val() > data.val()"
},
"likeModified":{
".write": "auth!= null",
".validate": "newData.val() > data.val()"
}
}
}
}
}
}


I know the user is authenticated as I'm printing the user object:
D/SwipeListener: User: com.google.android.gms.internal.zzafu@a507f090
and the data being sent seems to fit the validation rules I've specified.

My firebase version:

compile 'com.google.firebase:firebase-auth:9.4.0'
compile 'com.google.firebase:firebase-database:9.4.0'


Update:

This is the java code running the transaction:

public class LikeCountTransaction {

public static final String PATH = "styles/%s/likeInfo";
private final DatabaseReference databaseReference;

@Inject public LikeCountTransaction(DatabaseReference databaseReference) {
this.databaseReference = databaseReference;
}

public Observable<DataSnapshot> execute(Long styleId) {
String path = String.format(PATH, styleId);
Timber.d("Transaction path: %s", path);
return Rx.transact(databaseReference.child(path), mutableData -> {
MutableData likeCount = mutableData.child("likeCount");
Long likeCountValue = likeCount.getValue(Long.class);
if (likeCountValue != null) {
likeCount.setValue(likeCountValue + 1);
mutableData.child("likeModified").setValue(ServerValue.TIMESTAMP);
}
return Transaction.success(mutableData);
}).toObservable();
}
}


This is where the PermissionDenied Error is thrown:

public static Single<DataSnapshot> transact(DatabaseReference databaseReference, TransactionExecutor handler) {
return Observable.<DataSnapshot>fromEmitter(emitter -> databaseReference.runTransaction(new Transaction.Handler() {
@Override public Transaction.Result doTransaction(MutableData mutableData) {
return handler.execute(mutableData);
}

@Override public void onComplete(DatabaseError databaseError, boolean committed, DataSnapshot dataSnapshot) {
if (committed) {
emitter.onNext(dataSnapshot);
emitter.onCompleted();
} else {
if (databaseError != null) {
emitter.onError(databaseError.toException()); <------------------ Permission Denied Error
} else {
emitter.onError(new Throwable("Transaction did not commit"));
}
}
}
}), AsyncEmitter.BackpressureMode.LATEST).toSingle();
}

Answer

I had to change the validation rule on my "filters" to

"filters":{
    "$filterIndex": {
        ".validate": "newData.val() == data.val()"
        }
    }

I guess the rules don't evaluate two arrays to be equal even if they have the same data.