Issue is effectively identical to this issue which was left unresolved and I'm new to SO so couldn't comment to ask if a resolution was ever found (apologies for posting a redundant question). My specific case:
match /profilePhotos/{pathAsUserUid}/{photoFileName} {
allow create: if request.resource.metadata.userUid == pathAsUserUid;
}
returns false when I believe it should return true. The metadata has been written correctly to Storage and appears in https://console.cloud.google.com/storage as expected e.g., (key 2) userUid has a value of LjCZ3ONBTqPEIhNTXt2pxMDWkvJ2.
custom metadata as it appears in the console
Forcing a result via specific test cases clearly shows request.resource.metadata.userUid simply isn't working as expected. For example, (for testing only)
allow create: if 'LjCZ3ONBTqPEIhNTXt2pxMDWkvJ2' == pathAsUserUid;
works as expected (returns true) however,
allow create: if request.resource.metadata.userUid == 'LjCZ3ONBTqPEIhNTXt2pxMDWkvJ2';
doesn't work as expected (returns false).
ADDITION 1
Screen snapshot of console including path to image
ADDITION 2 Here's the code for uploading the profile photo. PUser is a straightforward user-data class with a uid set (not shown below but thoroughly tested) from the FirebaseAuth.instance.currentUser.uid.
void onSubmitted(String? value) async {
bool success = true;
// pUSer contains everything *except* the profile photo (held by pUser.profilePhotoXFile)
// upload the photo to cloud storage then get the photo's url
XFile profilePhotoXFile = widget.pUser.profilePhotoXFile as XFile;
final profilePhotoMetadata = SettableMetadata(
contentType: profilePhotoXFile.mimeType,
customMetadata: {
'user': widget.pUser.fullName!,
'userUid': widget.pUser.uid! // <-- @Dharmaraj this is where the userUid metadata is set
},
);
final storageRef = FirebaseStorage.instance.ref();
final profilePhotoRef = storageRef
.child('profilePhotos/${widget.pUser.uid}/${profilePhotoXFile.name}');
setState(() {
screenMode = ScreenMode.wait;
screenMessage = 'Uploading profile photo...';
});
try {
if (kIsWeb) {
Uint8List profilePhotoBytes = await profilePhotoXFile.readAsBytes();
TaskSnapshot upload = await profilePhotoRef.putData(
profilePhotoBytes,
profilePhotoMetadata,
);
widget.pUser.profilePhotoURL = await upload.ref.getDownloadURL();
} else {
throw const PlainliException('Only web is (currently) supported for image upload.');
}
} catch (e) {
success = false;
plainliBanner(
context: context,
message: 'Error uploading profile photo. error = $e');
}
if (!success) {
setState(() {
screenMode = ScreenMode.message;
screenMessage = 'There was an error uploading your photo.';
});
return;
}
// *merge* data{} into Firestore
setState(() {
screenMode = ScreenMode.wait;
screenMessage = 'Registering...';
});
Map<String, dynamic> data = widget.pUser.toMap();
CollectionReference<Map<String, dynamic>> collectionRef =
FirebaseFirestore.instance.collection('users');
try {
await collectionRef
.doc(widget.pUser.uid)
.set(data, SetOptions(merge: true));
} catch (e) {
success = false;
plainliBanner(
context: context,
message: 'Error merging data into Firestore. Error: $e');
}
if (!success) {
setState(() {
screenMode = ScreenMode.message;
screenMessage =
'There was an error uploading your profile information.';
});
return;
}
if (!mounted) return;
plainliSnackbar(context: context, message: 'Registration complete');
Navigator.of(context).popUntil((route) => route.isFirst);
// this allows the wrapper to get notified that all the user data has
// been successfully collected
Provider.of<PUserProvider>(context, listen: false)
.allRequiredUserDataExists = true;
}
And here are the Cloud Storage rules.
rules_version = '2';
service firebase.storage {
match /b/{bucket}/o {
match /{allPaths=**} {
allow read, write: if false;
}
match /profilePhotos/{pathAsUserUid}/{photo} {
allow read, write: if isAdmin();
allow get, create, update: if profilePhotoBelongsToUser(pathAsUserUid);
}
function isSignedin() {
return request.auth != null;
}
function isAdmin() {
let isUserSignedin = isSignedin();
let isUserRoleAdmin = request.auth.token.isAdmin;
return isUserSignedin && isUserRoleAdmin;
}
function profilePhotoBelongsToUser(pathAsUserUid) {
let isUserSignedin = isSignedin();
//let metadataUidMatchesPath = request.resource.metadata.userUid == pathAsUserUid;
let metadataUidMatchesPath = true; // <-- @Dharmaraj this is a temp get-around until we can sort out why the line above fails
let authUidMatchesPath = request.auth.uid == pathAsUserUid;
return isUserSignedin && metadataUidMatchesPath && authUidMatchesPath;
}
}
}