Repository: firebase/firebase-admin-java
Pull request #51 (2017-06-29 22:04:50)

Google Cloud Storage API [WIP]
Exposes Google Cloud Storage API via the Java Admin SDK:
* Take a dependency on GCS Java client
* Introducing `StorageClient` to hold an instance of `com.google.cloud.storage.Storage`.
* Expose the GCS buckets via a new `getBucket()` method.
* Expose a new `getDownloadUrl()` method (pending review).
* Implement support for a new `storageBucket` app option.

go/firebase-admin-gcs
go/admin-gcs-download-url [Pending review]


hiranya911: Addressed most of the suggestions, and left a couple of follow up comments.

Patch:
diff --git a/pom.xml b/pom.xml
index 20133de..c718b61 100644
--- a/pom.xml
+++ b/pom.xml
@@ -329,6 +329,11 @@
             <artifactId>guava</artifactId>
             <version>20.0</version>
         </dependency>
+        <dependency>
+            <groupId>com.google.cloud</groupId>
+            <artifactId>google-cloud-storage</artifactId>
+            <version>1.2.1</version>
+        </dependency>
         <dependency>
             <groupId>org.slf4j</groupId>
             <artifactId>slf4j-api</artifactId>
diff --git a/src/main/java/com/google/firebase/FirebaseOptions.java b/src/main/java/com/google/firebase/FirebaseOptions.java
index f37edfa..1c10939 100644
--- a/src/main/java/com/google/firebase/FirebaseOptions.java
+++ b/src/main/java/com/google/firebase/FirebaseOptions.java
@@ -16,12 +16,13 @@
 
 package com.google.firebase;
 
+import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
 
 import com.google.api.client.googleapis.util.Utils;
 import com.google.api.client.http.HttpTransport;
 import com.google.api.client.json.JsonFactory;
-import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
 import com.google.firebase.auth.FirebaseCredential;
 import com.google.firebase.auth.FirebaseCredentials;
 import com.google.firebase.internal.NonNull;
@@ -36,6 +37,7 @@ public final class FirebaseOptions {
   // TODO: deprecate and remove it once we can fetch these from Remote Config.
 
   private final String databaseUrl;
+  private final String storageBucket;
   private final FirebaseCredential firebaseCredential;
   private final Map<String, Object> databaseAuthVariableOverride;
   private final HttpTransport httpTransport;
@@ -46,6 +48,7 @@ public final class FirebaseOptions {
         "FirebaseOptions must be initialized with setCredential().");
     this.databaseUrl = builder.databaseUrl;
     this.databaseAuthVariableOverride = builder.databaseAuthVariableOverride;
+    this.storageBucket = builder.storageBucket;
     this.httpTransport = checkNotNull(builder.httpTransport,
         "FirebaseOptions must be initialized with a non-null HttpTransport.");
     this.jsonFactory = checkNotNull(builder.jsonFactory,
@@ -61,6 +64,15 @@ public final class FirebaseOptions {
     return databaseUrl;
   }
 
+  /**
+   * Returns the name of the Google Cloud Storage bucket used for storing application data.
+   *
+   * @return The cloud storage bucket name set via {@link Builder#setStorageBucket}
+   */
+  public String getStorageBucket() {
+    return storageBucket;
+  }
+
   FirebaseCredential getCredential() {
     return firebaseCredential;
   }
@@ -102,6 +114,7 @@ public final class FirebaseOptions {
   public static final class Builder {
 
     private String databaseUrl;
+    private String storageBucket;
     private FirebaseCredential firebaseCredential;
     private Map<String, Object> databaseAuthVariableOverride = new HashMap<>();
     private HttpTransport httpTransport = Utils.getDefaultTransport();
@@ -118,6 +131,7 @@ public final class FirebaseOptions {
      */
     public Builder(FirebaseOptions options) {
       databaseUrl = options.databaseUrl;
+      storageBucket = options.storageBucket;
       firebaseCredential = options.firebaseCredential;
       databaseAuthVariableOverride = options.databaseAuthVariableOverride;
       httpTransport = options.httpTransport;
@@ -127,8 +141,8 @@ public final class FirebaseOptions {
     /**
      * Sets the Realtime Database URL to use for data storage.
      *
-     * <p>See <a href="/docs/admin/setup#initialize_the_sdk">Initialize the SDK</a> for code samples
-     * and detailed documentation.
+     * <p>See <a href="https://firebase.google.com/docs/admin/setup#initialize_the_sdk">
+     * Initialize the SDK</a> for code samples and detailed documentation.
      *
      * @param databaseUrl The Realtime Database URL to use for data storage.
      * @return This <code>Builder</code> instance is returned so subsequent calls can be chained.
@@ -138,11 +152,29 @@ public final class FirebaseOptions {
       return this;
     }
 
+    /**
+     * Sets the name of the Google Cloud Storage bucket for reading and writing application data.
+     * The same credential used to initialize the SDK (see {@link Builder#setCredential}) will be
+     * used to access the bucket.
+     *
+     * <p>See <a href="https://firebase.google.com/docs/admin/setup#initialize_the_sdk">
+     * Initialize the SDK</a> for code samples and detailed documentation.
+     *
+     * @param storageBucket The name of an existing Google Cloud Storage bucket.
+     * @return This <code>Builder</code> instance is returned so subsequent calls can be chained.
+     */
+    public Builder setStorageBucket(String storageBucket) {
+      checkArgument(!Strings.isNullOrEmpty(storageBucket),
+          "Storage bucket must not be null or empty");
+      this.storageBucket = storageBucket;
+      return this;
+    }
+
     /**
      * Sets the <code>FirebaseCredential</code> to use to authenticate the SDK.
      *
-     * <p>See <a href="/docs/admin/setup#initialize_the_sdk">Initialize the SDK</a> for code samples
-     * and detailed documentation.
+     * <p>See <a href="https://firebase.google.com/docs/admin/setup#initialize_the_sdk">
+     * Initialize the SDK</a> for code samples and detailed documentation.
      *
      * @param credential A <code>FirebaseCredential</code> used to authenticate the SDK. See {@link
      *     FirebaseCredentials} for default implementations.
@@ -164,8 +196,8 @@ public final class FirebaseOptions {
      * instance. If this option is set to <code>null</code>, security rules are evaluated against an
      * unauthenticated user. That is, the <code>auth</code> variable is <code>null</code>.
      *
-     * <p>See <a href="/docs/database/admin/start#authenticate-with-limited-privileges">Authenticate
-     * with limited privileges</a> for code samples and detailed documentation.
+     * <p>See <a href="https://firebase.google.com/docs/database/admin/start#authenticate-with-limited-privileges">
+     * Authenticate with limited privileges</a> for code samples and detailed documentation.
      *
      * @param databaseAuthVariableOverride The value to use for the <code>auth</code> variable in
      *     the security rules for Realtime Database actions.
diff --git a/src/main/java/com/google/firebase/auth/FirebaseCredentials.java b/src/main/java/com/google/firebase/auth/FirebaseCredentials.java
index f5217ea..072fd58 100644
--- a/src/main/java/com/google/firebase/auth/FirebaseCredentials.java
+++ b/src/main/java/com/google/firebase/auth/FirebaseCredentials.java
@@ -48,9 +48,17 @@ public class FirebaseCredentials {
 
   private static final List<String> FIREBASE_SCOPES =
       ImmutableList.of(
+          // Enables access to Firebase Realtime Database.
           "https://www.googleapis.com/auth/firebase.database",
+
+          // Enables access to the email address associated with a project.
           "https://www.googleapis.com/auth/userinfo.email",
-          "https://www.googleapis.com/auth/identitytoolkit");
+
+          // Enables access to Google Identity Toolkit (for user management APIs).
+          "https://www.googleapis.com/auth/identitytoolkit",
+
+          // Enables access to Google Cloud Storage.
+          "https://www.googleapis.com/auth/devstorage.full_control");
 
   private FirebaseCredentials() {
   }
diff --git a/src/main/java/com/google/firebase/cloud/StorageClient.java b/src/main/java/com/google/firebase/cloud/StorageClient.java
new file mode 100644
index 0000000..0c3c67d
--- /dev/null
+++ b/src/main/java/com/google/firebase/cloud/StorageClient.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.firebase.cloud;
+
+import static com.google.common.base.Preconditions.checkArgument;
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.cloud.storage.Bucket;
+import com.google.cloud.storage.Storage;
+import com.google.cloud.storage.StorageOptions;
+import com.google.common.base.Strings;
+import com.google.firebase.FirebaseApp;
+import com.google.firebase.ImplFirebaseTrampolines;
+import com.google.firebase.internal.FirebaseCloudCredentials;
+import com.google.firebase.internal.FirebaseService;
+
+/**
+ * StorageClient provides access to Google Cloud Storage APIs. You can specify a default cloud
+ * storage bucket via {@link com.google.firebase.FirebaseOptions}, and then get a reference to this
+ * default bucket by calling {@link StorageClient#bucket()}. Or you can get a reference to a
+ * specific bucket at any time by calling {@link StorageClient#bucket(String)}.
+ *
+ * <p>This class requires Google Cloud Storage libraries for Java. Make sure the artifact
+ * google-cloud-storage is in the classpath along with its transitive dependencies.
+ */
+public class StorageClient {
+
+  private final FirebaseApp app;
+  private final Storage storage;
+
+  private StorageClient(FirebaseApp app) {
+    this.app = checkNotNull(app, "FirebaseApp must not be null");
+    this.storage = StorageOptions.newBuilder()
+        .setCredentials(new FirebaseCloudCredentials(app))
+        .build()
+        .getService();
+  }
+
+  public static StorageClient getInstance() {
+    return getInstance(FirebaseApp.getInstance());
+  }
+
+  public static synchronized StorageClient getInstance(FirebaseApp app) {
+    StorageClientService service = ImplFirebaseTrampolines.getService(app, SERVICE_ID,
+        StorageClientService.class);
+    if (service == null) {
+      service = ImplFirebaseTrampolines.addService(app, new StorageClientService(app));
+    }
+    return service.getInstance();
+  }
+
+  /**
+   * Returns the default cloud storage bucket associated with the current app. This is the bucket
+   * configured via {@link com.google.firebase.FirebaseOptions} when initializing the app. If
+   * no bucket was configured via options, this method throws an exception.
+   *
+   * @return a cloud storage Bucket instance, or null if the configured bucket does not exist.
+   * @throws IllegalArgumentException If no bucket is configured via <code>FirebaseOptions</code>,
+   *     or if the bucket does not exist.
+   */
+  public Bucket bucket() {
+    return bucket(app.getOptions().getStorageBucket());
+  }
+
+  /**
+   * Returns a cloud storage Bucket instance for the specified bucket name.
+   *
+   * @param name a non-null, non-empty bucket name.
+   * @return a cloud storage Bucket instance, or null if the specified bucket does not exist.
+   * @throws IllegalArgumentException If the bucket name is null, empty, or if the specified
+   *     bucket does not exist.
+   */
+  public Bucket bucket(String name) {
+    checkArgument(!Strings.isNullOrEmpty(name),
+        "Bucket name not specified. Specify the bucket name via the storageBucket "
+            + "option when initializing the app, or specify the bucket name explicitly when "
+            + "calling the getBucket() method.");
+    Bucket bucket = storage.get(name);
+    checkArgument(bucket != null, "Bucket " + name + " does not exist.");
+    return bucket;
+  }
+
+  private static final String SERVICE_ID = StorageClient.class.getName();
+
+  private static class StorageClientService extends FirebaseService<StorageClient> {
+
+    StorageClientService(FirebaseApp app) {
+      super(SERVICE_ID, new StorageClient(app));
+    }
+
+    @Override
+    public void destroy() {
+      // NOTE: We don't explicitly tear down anything here, but public methods of StorageClient
+      // will now fail because calls to getOptions() and getToken() will hit FirebaseApp,
+      // which will throw once the app is deleted.
+    }
+  }
+
+}
diff --git a/src/main/java/com/google/firebase/internal/FirebaseCloudCredentials.java b/src/main/java/com/google/firebase/internal/FirebaseCloudCredentials.java
new file mode 100644
index 0000000..5786342
--- /dev/null
+++ b/src/main/java/com/google/firebase/internal/FirebaseCloudCredentials.java
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.firebase.internal;
+
+import static com.google.common.base.Preconditions.checkNotNull;
+
+import com.google.auth.Credentials;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.firebase.FirebaseApp;
+import com.google.firebase.ImplFirebaseTrampolines;
+import com.google.firebase.tasks.Continuation;
+import com.google.firebase.tasks.Task;
+import com.google.firebase.tasks.Tasks;
+import java.io.IOException;
+import java.net.URI;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ExecutionException;
+
+/**
+ * A Google cloud credential implementation that uses OAuth2 access tokens obtained from a
+ * <code>FirebaseApp</code> to authenticate cloud API calls. This essentially acts as a bridge
+ * between Firebase Admin SDK APIs and the Google cloud <code>Credentials</code> API.
+ */
+public final class FirebaseCloudCredentials extends Credentials {
+
+  private final FirebaseApp app;
+
+  public FirebaseCloudCredentials(FirebaseApp app) {
+    this.app = checkNotNull(app);
+  }
+
+  @Override
+  public String getAuthenticationType() {
+    return "OAuth2";
+  }
+
+  @Override
+  public Map<String, List<String>> getRequestMetadata(URI uri) throws IOException {
+    Task<String> task = ImplFirebaseTrampolines.getToken(app, false).continueWith(
+        new Continuation<GetTokenResult, String>() {
+          @Override
+          public String then(Task<GetTokenResult> task) throws Exception {
+            return task.getResult().getToken();
+          }
+        });
+    try {
+      String authHeader = "Bearer " + Tasks.await(task);
+      return ImmutableMap.<String, List<String>>of(
+          "Authorization", ImmutableList.of(authHeader));
+    } catch (ExecutionException | InterruptedException e) {
+      throw new IOException("Failed to acquire an OAuth token", e);
+    }
+  }
+
+  @Override
+  public boolean hasRequestMetadata() {
+    return true;
+  }
+
+  @Override
+  public boolean hasRequestMetadataOnly() {
+    return true;
+  }
+
+  @Override
+  public void refresh() throws IOException {
+    ImplFirebaseTrampolines.getToken(app, true);
+  }
+
+}


Test patch:
diff --git a/src/test/java/com/google/firebase/FirebaseOptionsTest.java b/src/test/java/com/google/firebase/FirebaseOptionsTest.java
index b59561f..5791947 100644
--- a/src/test/java/com/google/firebase/FirebaseOptionsTest.java
+++ b/src/test/java/com/google/firebase/FirebaseOptionsTest.java
@@ -20,6 +20,7 @@ import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNotSame;
+import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.fail;
 
@@ -45,6 +46,7 @@ import org.junit.Test;
 public class FirebaseOptionsTest {
 
   private static final String FIREBASE_DB_URL = "https://mock-project.firebaseio.com";
+  private static final String FIREBASE_STORAGE_BUCKET = "mock-storage-bucket";
 
   private static final FirebaseOptions ALL_VALUES_OPTIONS =
       new FirebaseOptions.Builder()
@@ -60,11 +62,13 @@ public class FirebaseOptionsTest {
     FirebaseOptions firebaseOptions =
         new FirebaseOptions.Builder()
             .setDatabaseUrl(FIREBASE_DB_URL)
+            .setStorageBucket(FIREBASE_STORAGE_BUCKET)
             .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
             .setJsonFactory(jsonFactory)
             .setHttpTransport(httpTransport)
             .build();
     assertEquals(FIREBASE_DB_URL, firebaseOptions.getDatabaseUrl());
+    assertEquals(FIREBASE_STORAGE_BUCKET, firebaseOptions.getStorageBucket());
     assertSame(jsonFactory, firebaseOptions.getJsonFactory());
     assertSame(httpTransport, firebaseOptions.getHttpTransport());
     TestOnlyImplFirebaseAuthTrampolines.getCertificate(firebaseOptions.getCredential())
@@ -89,6 +93,8 @@ public class FirebaseOptionsTest {
             .build();
     assertNotNull(firebaseOptions.getJsonFactory());
     assertNotNull(firebaseOptions.getHttpTransport());
+    assertNull(firebaseOptions.getDatabaseUrl());
+    assertNull(firebaseOptions.getStorageBucket());
     TestOnlyImplFirebaseAuthTrampolines.getCertificate(firebaseOptions.getCredential())
         .addOnSuccessListener(
             new OnSuccessListener<GoogleCredential>() {
diff --git a/src/test/java/com/google/firebase/cloud/StorageClientIT.java b/src/test/java/com/google/firebase/cloud/StorageClientIT.java
new file mode 100644
index 0000000..400fe26
--- /dev/null
+++ b/src/test/java/com/google/firebase/cloud/StorageClientIT.java
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.firebase.cloud;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+
+import com.google.cloud.storage.Blob;
+import com.google.cloud.storage.Bucket;
+import com.google.firebase.testing.IntegrationTestUtils;
+import org.junit.Test;
+
+public class StorageClientIT {
+
+  @Test
+  public void testCloudStorageDefaultBucket() {
+    StorageClient storage = StorageClient.getInstance(IntegrationTestUtils.ensureDefaultApp());
+    testBucket(storage.bucket());
+  }
+
+  @Test
+  public void testCloudStorageCustomBucket() {
+    StorageClient storage = StorageClient.getInstance(IntegrationTestUtils.ensureDefaultApp());
+    testBucket(storage.bucket(IntegrationTestUtils.getStorageBucket()));
+  }
+
+  @Test
+  public void testCloudStorageNonExistingBucket() {
+    StorageClient storage = StorageClient.getInstance(IntegrationTestUtils.ensureDefaultApp());
+    try {
+      storage.bucket("non-existing");
+      fail("No error thrown for non-existing bucket");
+    } catch (IllegalArgumentException expected) {
+      // ignore
+    }
+  }
+
+  private void testBucket(Bucket bucket) {
+    assertEquals(IntegrationTestUtils.getStorageBucket(), bucket.getName());
+
+    String fileName = "data_" + System.currentTimeMillis() + ".txt";
+    bucket.create(fileName, "Hello World".getBytes(), "text/plain");
+
+    Blob blob = bucket.get(fileName);
+    byte[] content = blob.getContent();
+    assertEquals("Hello World", new String(content));
+
+    assertTrue(blob.delete());
+    assertNull(bucket.get(fileName));
+  }
+
+}
diff --git a/src/test/java/com/google/firebase/cloud/StorageClientTest.java b/src/test/java/com/google/firebase/cloud/StorageClientTest.java
new file mode 100644
index 0000000..3fa1663
--- /dev/null
+++ b/src/test/java/com/google/firebase/cloud/StorageClientTest.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.google.firebase.cloud;
+
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.fail;
+
+import com.google.firebase.FirebaseApp;
+import com.google.firebase.FirebaseOptions;
+import com.google.firebase.TestOnlyImplFirebaseTrampolines;
+import com.google.firebase.auth.FirebaseCredentials;
+import com.google.firebase.testing.ServiceAccount;
+import java.io.IOException;
+import org.junit.After;
+import org.junit.Test;
+
+public class StorageClientTest {
+
+  @After
+  public void tearDown() {
+    TestOnlyImplFirebaseTrampolines.clearInstancesForTest();
+  }
+
+  @Test
+  public void testInvalidConfiguration() throws IOException {
+    FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+        .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
+        .build());
+    try {
+      StorageClient.getInstance(app).bucket();
+      fail("No error thrown for invalid configuration");
+    } catch (IllegalArgumentException expected) {
+      // ignore
+    }
+  }
+
+  @Test
+  public void testInvalidBucketName() throws IOException {
+    FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+        .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
+        .setStorageBucket("mock-bucket-name")
+        .build());
+    try {
+      StorageClient.getInstance(app).bucket(null);
+      fail("No error thrown for invalid configuration");
+    } catch (IllegalArgumentException expected) {
+      // ignore
+    }
+
+    try {
+      StorageClient.getInstance(app).bucket("");
+      fail("No error thrown for invalid configuration");
+    } catch (IllegalArgumentException expected) {
+      // ignore
+    }
+  }
+
+  @Test
+  public void testAppDelete() throws IOException {
+    FirebaseApp app = FirebaseApp.initializeApp(new FirebaseOptions.Builder()
+        .setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
+        .setStorageBucket("mock-bucket-name")
+        .build());
+
+    assertNotNull(StorageClient.getInstance(app));
+    app.delete();
+    try {
+      StorageClient.getInstance(app);
+      fail("No error thrown for deleted app");
+    } catch (IllegalStateException expected) {
+      // ignore
+    }
+  }
+}
diff --git a/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java b/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java
index 296b718..369823d 100644
--- a/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java
+++ b/src/test/java/com/google/firebase/testing/IntegrationTestUtils.java
@@ -78,6 +78,10 @@ public class IntegrationTestUtils {
     return "https://" + getProjectId() + ".firebaseio.com";
   }
 
+  public static String getStorageBucket() {
+    return getProjectId() + ".appspot.com";
+  }
+
   public static String getApiKey() {
     String apiKey = System.getProperty("firebase.it.apikey");
     checkArgument(!Strings.isNullOrEmpty(apiKey), "API key not specified. Set the "
@@ -98,6 +102,7 @@ public class IntegrationTestUtils {
       FirebaseOptions options =
           new FirebaseOptions.Builder()
               .setDatabaseUrl(getDatabaseUrl())
+              .setStorageBucket(getStorageBucket())
               .setCredential(TestUtils.getCertCredential(getServiceAccountCertificate()))
               .build();
       masterApp = FirebaseApp.initializeApp(options);