0
votes

My write access is denied when trying to upload new photo:

User does not have permission to access this object.

My security rules:

service firebase.storage {
  match /b/{bucket}/o {
    match /user_photo/{imageid} {
      allow read, write: if request.auth != null && imageid==request.auth.uid;
    }
  }
}

The storage reference of the image I want to "update" (upload new photo with same name which is uid, below uid is changed for simplicity)

/user_photo/123456

123456 is the uid of the current authorized user in my app.

At the method reference.putfile(uri), reference is:

gs://my-app-name.appspot.com/user_photo/123456

uri is:

content://com.android.providers.media.documents/document/image%3A38830

Photo upload process in my app is working fine (tested with rules set so that all reads and writes are accepted). With security rules, in the console simulator, if I check authenticated checkbox && write 123456 as userid, the simulator allows all operations. But from my app, I get the error I posted above.

Any suggestions on what might cause this?

EDIT1:

The whole "operation" is to upload a new photo with firebaseuser uid as the photoname, in the folder "/user_photo", then update the photourl in firebaseuser to the newly added photo.

Before changing the security rules, this process "worked" and I could upload the photo accordingly, and get download url from firebaseuser.getPhotoUrl(), and download correct photo. After I added the new rules, I get denied from application (and not from simulator in console)-

MyInteractor.class:

public Completable changeProfilePhoto(Uri uri){
        return authRepo.getCurrentUser()
                .take(1)
                .flatMapCompletable(firebaseUser -> storageRepo.uploadPhoto(firebaseUser.getUid(), uri)
                        .flatMap(taskSnapshot -> storageRepo.getDownloadUrl(taskSnapshot.getMetadata().getReference()))
                        .flatMapCompletable(uploadedUri -> authRepo.changeUserPhoto(firebaseUser, uploadedUri)))
                .observeOn(AndroidSchedulers.mainThread());
    }

FirebaseAuthRepository (authRepo above) methods:

@Override
public Observable<FirebaseUser> getCurrentUser() {
    return FirebaseAuthWrapper.observeUserAuthState(firebaseAuth)
            .map(FirebaseAuth::getCurrentUser)
            .switchIfEmpty(observer -> {
                observer.onError(new RxWrapperNullException(RxWrapperNullException.NO_CURRENT_USER));
            })
            .subscribeOn(Schedulers.io());
}

@Override
public Completable changeUserPhoto(FirebaseUser firebaseUser, Uri uri){
    UserProfileChangeRequest request = new UserProfileChangeRequest.Builder()
            .setPhotoUri(uri)
            .build();
    return FirebaseUserWrapper.updateUserProfile(firebaseUser,request);
}

FirebaseStorageRepository (storageRepo above) methods:

@Override
public Single<UploadTask.TaskSnapshot> uploadPhoto(String user, Uri uri) {
    StorageReference thisRef = userPhoto.child(String.format("/%S", user));
    return FirebaseStorageWrapper.putFile(thisRef, uri)
            .subscribeOn(Schedulers.io());
}

@Override
public Single<Uri> getDownloadUrl(StorageReference reference) {
    return FirebaseStorageWrapper.getDownloadUrl(reference)
            .toSingle()
            .subscribeOn(Schedulers.io());
}

From my Viewmodel.class, I call the myInteractor.changeProfilePhoto(uri) where uri is path to image on the android device as chosen by user from intent

EDIT 2:

I noticed I forgot to add wrapper class methods, which may be of importance:

FirebaseStorageWrapper.class:

public static Single<UploadTask.TaskSnapshot> putFile(StorageReference reference, Uri uri){
    return Single.create(emitter -> {
        StorageTask<UploadTask.TaskSnapshot> task = reference.putFile(uri)
                .addOnSuccessListener(emitter::onSuccess)
                .addOnFailureListener(e -> {
                    if(!emitter.isDisposed()){
                        emitter.onError(e);
                    }
        });
        emitter.setCancellable(task::cancel);
    });
}

public static Maybe<Uri> getDownloadUrl(StorageReference ref) {
    return Maybe.create(emitter -> MaybeTask.assign(emitter, ref.getDownloadUrl()));
}

FirebaseUserWrapper.class:

public static Completable updateUserProfile(FirebaseUser firebaseUser, UserProfileChangeRequest request){
    return Completable.create(emitter -> CompletableTask.assign(emitter, firebaseUser.updateProfile(request)));
}

assign() method in both CompletableTask and MaybeTask just sets listeners to Task, and in each listener when invoked by events, sends data to the emitter for it to emit.

EDIT 3:

FirebaseAuth firebaseAuth = FirebaseAuth.getInstance();
FirebaseUser firebaseUser = firebaseAuth.getCurrentUser();
StorageReference storageReference = FirebaseStorage.getInstance().getReference().child("/user_photo").child(String.format("/%S", firebaseUser.getUid()))
storageReference.putFile(uri).addOnSuccessListener(new OnSuccessListener<UploadTask.TaskSnapshot>() {
    @Override
    public void onSuccess(UploadTask.TaskSnapshot taskSnapshot) {
        taskSnapshot.getMetadata().getReference().getDownloadUrl().addOnSuccessListener(new OnSuccessListener<Uri>() {
            @Override
            public void onSuccess(Uri newUri) {
                UserProfileChangeRequest request = new UserProfileChangeRequest.Builder()
                        .setPhotoUri(newUri)
                        .build();
                firebaseUser.updateProfile(request).addOnSuccessListener(new OnSuccessListener<Void>() {
                    @Override
                    public void onSuccess(Void aVoid) {
                        //if it gets here it means operation was successful
                    }
                }).addOnFailureListener(new OnFailureListener() {
                    @Override
                    public void onFailure(@NonNull Exception e) {

                    }
                });
            }
        }).addOnFailureListener(new OnFailureListener() {
            @Override
            public void onFailure(@NonNull Exception e) {
            }
        });

    }
}).addOnFailureListener(new OnFailureListener() {
    @Override
    public void onFailure(@NonNull Exception e) {
        //it fails here == storageReference.putFile(uri)
    }
});
1
Please show the minimal, complete/standalone code that reproduces the error message. As it stands we can't see what uri and reference are, and chances are good that the problem comes from how you initialize those.Frank van Puffelen
@FrankvanPuffelen I added code under "Edit 1" in my question, if I missed anything please let me know but I added all the methods in the "upload photo process"Carlton
Is there any way you can reduce all of this to a single snippet? All the wrappers make it harder to follow (for me at least). See how to create a minimal, complete, verifiable example.Frank van Puffelen
@FrankvanPuffelen Okay, now I have added it under EDIT 3. I removed all wrappers & rxjava related stuffCarlton
@FrankvanPuffelen Would a better way to implement (that user may only read&write to his profile pic) this to have a folder with uid as name, like: /user_photos/{uid}/imagename? instead of what I am doing right now which is /user_photo/{imagename} where image name in the latter is uidCarlton

1 Answers

0
votes

Add Storage Rules... in your firebase "Storage Rules"

Add this : allow read, write;

and remove this : if request.auth != null && imageid==request.auth.uid;

enter image description here