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)
}
});
uri
andreference
are, and chances are good that the problem comes from how you initialize those. – Frank van Puffelen