From 66faa1d9d1b50eb4e058603d96c4994cdc8e6443 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Thu, 11 Jul 2024 10:52:05 +0100 Subject: [PATCH] fix(storage): setup different emulator per bucket --- .github/workflows/scripts/storage.rules | 8 +++- .../ReactNativeFirebaseStorageModule.java | 11 +++-- packages/storage/e2e/StorageReference.e2e.js | 41 +++++++++++++++++++ packages/storage/e2e/helpers.js | 8 +++- .../ios/RNFBStorage/RNFBStorageModule.m | 21 ++++++---- packages/storage/lib/index.js | 2 +- tests/app.js | 4 +- 7 files changed, 80 insertions(+), 15 deletions(-) diff --git a/.github/workflows/scripts/storage.rules b/.github/workflows/scripts/storage.rules index c5a99c3338..2ad3f55053 100644 --- a/.github/workflows/scripts/storage.rules +++ b/.github/workflows/scripts/storage.rules @@ -1,6 +1,6 @@ rules_version = '2'; service firebase.storage { - match /b/{bucket}/o { + match /b/react-native-firebase-testing.appspot.com/o { match /{document=**} { allow read, write: if false; } @@ -18,4 +18,10 @@ service firebase.storage { allow read, write: if true; } } + + match /b/react-native-firebase-testing/o { + match /only-second-bucket/{document=**} { + allow read, write: if true; + } + } } \ No newline at end of file diff --git a/packages/storage/android/src/main/java/io/invertase/firebase/storage/ReactNativeFirebaseStorageModule.java b/packages/storage/android/src/main/java/io/invertase/firebase/storage/ReactNativeFirebaseStorageModule.java index ed9048245a..02d60cf06a 100644 --- a/packages/storage/android/src/main/java/io/invertase/firebase/storage/ReactNativeFirebaseStorageModule.java +++ b/packages/storage/android/src/main/java/io/invertase/firebase/storage/ReactNativeFirebaseStorageModule.java @@ -281,12 +281,15 @@ public void setMaxUploadRetryTime(String appName, double milliseconds, Promise p * @link https://firebase.google.com/docs/reference/js/firebase.storage.Storage#useEmulator */ @ReactMethod - public void useEmulator(String appName, String host, int port, Promise promise) { + public void useEmulator(String appName, String host, int port, String bucketUrl, Promise promise) { FirebaseApp firebaseApp = FirebaseApp.getInstance(appName); - FirebaseStorage firebaseStorage = FirebaseStorage.getInstance(firebaseApp); - if (emulatorConfigs.get(appName) == null) { + + FirebaseStorage firebaseStorage = FirebaseStorage.getInstance(firebaseApp, bucketUrl); + String emulatorKey = appName + ":" + bucketUrl; + + if (emulatorConfigs.get(emulatorKey) == null) { firebaseStorage.useEmulator(host, port); - emulatorConfigs.put(appName, "true"); + emulatorConfigs.put(emulatorKey, "true"); } promise.resolve(null); } diff --git a/packages/storage/e2e/StorageReference.e2e.js b/packages/storage/e2e/StorageReference.e2e.js index 9e118e8b14..6abb889ecc 100644 --- a/packages/storage/e2e/StorageReference.e2e.js +++ b/packages/storage/e2e/StorageReference.e2e.js @@ -23,6 +23,47 @@ describe('storage() -> StorageReference', function () { await seed(PATH); }); + describe('second storage bucket writes to Storage emulator', function () { + let secondStorage; + // Same bucket defined in app.js when setting up emulator + const secondStorageBucket = 'gs://react-native-firebase-testing'; + + before(function () { + const { getStorage } = storageModular; + secondStorage = getStorage(firebase.app(), secondStorageBucket); + }); + + it('should write a file to the second storage bucket', async function () { + const { ref } = storageModular; + + // "only-second-bucket" is not an allowable path on live project for either bucket + const storageReference = ref(secondStorage, 'only-second-bucket/ok.txt'); + + await storageReference.putString('Hello World'); + }); + + it('should throw exception on path not allowed on second bucket security rules', async function () { + const { ref } = storageModular; + + // "react-native-firebase-testing" is not an allowed on second bucket, only "ony-second-bucket" + const storageReference = ref( + secondStorage, + 'react-native-firebase-testing/should-fail.txt', + ); + + try { + await storageReference.putString('Hello World'); + return Promise.reject(new Error('Did not throw')); + } catch (error) { + error.code.should.equal('storage/unauthorized'); + error.message.should.equal( + '[storage/unauthorized] User is not authorized to perform the desired action.', + ); + return Promise.resolve(); + } + }); + }); + describe('firebase v8 compatibility', function () { describe('toString()', function () { it('returns the correct bucket path to the file', function () { diff --git a/packages/storage/e2e/helpers.js b/packages/storage/e2e/helpers.js index a11211a005..0056a96337 100644 --- a/packages/storage/e2e/helpers.js +++ b/packages/storage/e2e/helpers.js @@ -17,7 +17,7 @@ exports.seed = async function seed(path) { storage: { rules: `rules_version = '2'; service firebase.storage { - match /b/{bucket}/o { + match /b/react-native-firebase-testing.appspot.com/o { match /{document=**} { allow read, write: if false; } @@ -35,6 +35,12 @@ exports.seed = async function seed(path) { allow read, write: if true; } } + + match /b/react-native-firebase-testing/o { + match /only-second-bucket/{document=**} { + allow read, write: if true; + } + } }`, host: getE2eEmulatorHost(), port: 9199, diff --git a/packages/storage/ios/RNFBStorage/RNFBStorageModule.m b/packages/storage/ios/RNFBStorage/RNFBStorageModule.m index 18862ba7de..a3695b6ecf 100644 --- a/packages/storage/ios/RNFBStorage/RNFBStorageModule.m +++ b/packages/storage/ios/RNFBStorage/RNFBStorageModule.m @@ -511,12 +511,15 @@ - (void)invalidate { RCT_EXPORT_METHOD(useEmulator : (FIRApp *)firebaseApp : (nonnull NSString *)host - : (NSInteger)port) { + : (NSInteger)port + : (NSString *)bucketUrl) { emulatorHost = host; emulatorPort = port; - if (!emulatorConfigs[firebaseApp.name]) { - [[FIRStorage storageForApp:firebaseApp] useEmulatorWithHost:host port:port]; - emulatorConfigs[firebaseApp.name] = @YES; + NSString *key = [self createEmulatorKey:bucketUrl appName:firebaseApp.name]; + + if (!emulatorConfigs[key]) { + [[FIRStorage storageForApp:firebaseApp URL:bucketUrl] useEmulatorWithHost:host port:port]; + emulatorConfigs[key] = @YES; } } @@ -557,6 +560,10 @@ - (void)invalidate { #pragma mark - #pragma mark Firebase Storage Internals +- (NSString *)createEmulatorKey:(NSString *)bucketUrl appName:(NSString *)appName { + return [NSString stringWithFormat:@"%@-%@", appName, bucketUrl]; +} + - (void)addUploadTaskObservers:(FIRStorageUploadTask *)uploadTask appDisplayName:(NSString *)appDisplayName taskId:(NSNumber *)taskId @@ -675,11 +682,11 @@ - (FIRStorageReference *)getReferenceFromUrl:(NSString *)url app:(FIRApp *)fireb storage = [FIRStorage storageForApp:firebaseApp URL:bucket]; NSLog(@"Setting emulator - host %@ port %ld", emulatorHost, (long)emulatorPort); - if (![emulatorHost isEqual:[NSNull null]] && emulatorHost != nil && - !emulatorConfigs[firebaseApp.name]) { + NSString *key = [self createEmulatorKey:bucket appName:firebaseApp.name]; + if (![emulatorHost isEqual:[NSNull null]] && emulatorHost != nil && !emulatorConfigs[key]) { @try { [storage useEmulatorWithHost:emulatorHost port:emulatorPort]; - emulatorConfigs[firebaseApp.name] = @YES; + emulatorConfigs[key] = @YES; } @catch (NSException *e) { NSLog(@"WARNING: Unable to set the Firebase Storage emulator settings. These must be set " @"before any usages of Firebase Storage. If you see this log after a hot " diff --git a/packages/storage/lib/index.js b/packages/storage/lib/index.js index d6453c74ae..170d0ac316 100644 --- a/packages/storage/lib/index.js +++ b/packages/storage/lib/index.js @@ -203,7 +203,7 @@ class FirebaseStorageModule extends FirebaseModule { } this.emulatorHost = host; this.emulatorPort = port; - this.native.useEmulator(_host, port); + this.native.useEmulator(_host, port, this._customUrlOrRegion); return [_host, port]; // undocumented return, just used to unit test android host remapping } } diff --git a/tests/app.js b/tests/app.js index 0de2338752..9b25258fe6 100644 --- a/tests/app.js +++ b/tests/app.js @@ -82,8 +82,10 @@ function loadTests(_) { // as data from previous runs pollutes following runs until re-install the app. Clear it. firebase.firestore().clearPersistence(); } - if (platformSupportedModules.includes('storage')) + if (platformSupportedModules.includes('storage')) { firebase.storage().useEmulator('localhost', 9199); + firebase.app().storage('gs://react-native-firebase-testing').useEmulator('localhost', 9199); + } }); afterEach(async function afterEachTest() {