Skip to content

Commit 08c3d32

Browse files
authored
Fail fast semantics in the FirebaseCredentials API (firebase#18)
1 parent 245f64d commit 08c3d32

18 files changed

Lines changed: 126 additions & 194 deletions

src/main/java/com/google/firebase/FirebaseOptions.java

Lines changed: 3 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.google.firebase;
22

3+
import static com.google.common.base.Preconditions.checkArgument;
34
import static com.google.common.base.Preconditions.checkNotNull;
45

56
import com.google.common.base.MoreObjects;
@@ -9,7 +10,6 @@
910
import com.google.firebase.internal.NonNull;
1011
import com.google.firebase.internal.Nullable;
1112

12-
import java.io.InputStream;
1313
import java.util.HashMap;
1414
import java.util.Map;
1515

@@ -87,7 +87,6 @@ public static final class Builder {
8787

8888
private String databaseUrl;
8989
private FirebaseCredential firebaseCredential;
90-
private FirebaseCredential serviceAccountCredential;
9190
private Map<String, Object> databaseAuthVariableOverride = new HashMap<>();
9291

9392
/** Constructs an empty builder. */
@@ -119,28 +118,9 @@ public Builder setDatabaseUrl(@Nullable String databaseUrl) {
119118
return this;
120119
}
121120

122-
/**
123-
* Sets the service account to use to authenticate the SDK.
124-
*
125-
* <p>This method is deprecated in favor of the {@link #setCredential} method. Only one of the
126-
* <code>setCredential()</code> and <code>setServiceAccount()</code> methods can be used.
127-
*
128-
* @param stream A stream containing the service account contents as JSON.
129-
* @return This <code>Builder</code> instance is returned so subsequent calls can be chained.
130-
* @deprecated Use {@link #setCredential} instead and obtain credentials via {@link
131-
* FirebaseCredentials}.
132-
*/
133-
@Deprecated
134-
public Builder setServiceAccount(@NonNull InputStream stream) {
135-
serviceAccountCredential = FirebaseCredentials.fromCertificate(stream);
136-
return this;
137-
}
138-
139121
/**
140122
* Sets the <code>FirebaseCredential</code> to use to authenticate the SDK.
141123
*
142-
* <p>This method replaces the deprecated {@link #setServiceAccount} method.
143-
*
144124
* <p>See <a href="/docs/admin/setup#initialize_the_sdk">Initialize the SDK</a> for code samples
145125
* and detailed documentation.
146126
*
@@ -183,18 +163,8 @@ public Builder setDatabaseAuthVariableOverride(
183163
* @return A {@link FirebaseOptions} instance created from the previously set options.
184164
*/
185165
public FirebaseOptions build() {
186-
if (serviceAccountCredential == null && firebaseCredential == null) {
187-
throw new IllegalStateException(
188-
"FirebaseOptions must be initialized with setCredential().");
189-
} else if (serviceAccountCredential != null && firebaseCredential != null) {
190-
throw new IllegalStateException(
191-
"FirebaseOptions cannot be initialized with both "
192-
+ "setCredential() and setServiceAccount().");
193-
}
194-
195-
FirebaseCredential firebaseCredential =
196-
this.firebaseCredential != null ? this.firebaseCredential : serviceAccountCredential;
197-
166+
checkArgument(firebaseCredential != null,
167+
"FirebaseOptions must be initialized with setCredential().");
198168
return new FirebaseOptions(databaseUrl, firebaseCredential, databaseAuthVariableOverride);
199169
}
200170
}

src/main/java/com/google/firebase/auth/FirebaseAuth.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -158,7 +158,7 @@ public Task<FirebaseToken> verifyIdToken(final String token) {
158158
+ "verifyIdToken()"));
159159
}
160160
return ((FirebaseCredentials.CertCredential) credential)
161-
.getProjectId(false)
161+
.getProjectId()
162162
.continueWith(
163163
new Continuation<String, FirebaseToken>() {
164164
@Override

src/main/java/com/google/firebase/auth/FirebaseCredentials.java

Lines changed: 38 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77
import com.google.api.client.http.HttpTransport;
88
import com.google.api.client.json.JsonFactory;
99
import com.google.common.annotations.VisibleForTesting;
10+
import com.google.common.collect.ImmutableList;
11+
import com.google.common.io.CharStreams;
1012
import com.google.firebase.internal.NonNull;
1113
import com.google.firebase.tasks.Continuation;
1214
import com.google.firebase.tasks.Task;
@@ -16,9 +18,7 @@
1618
import java.io.IOException;
1719
import java.io.InputStream;
1820
import java.io.InputStreamReader;
19-
import java.io.Reader;
2021
import java.nio.charset.StandardCharsets;
21-
import java.util.Arrays;
2222
import java.util.List;
2323
import java.util.concurrent.Callable;
2424

@@ -32,21 +32,16 @@
3232
public class FirebaseCredentials {
3333

3434
private static final List<String> FIREBASE_SCOPES =
35-
Arrays.asList(
35+
ImmutableList.of(
3636
"https://www.googleapis.com/auth/firebase.database",
3737
"https://www.googleapis.com/auth/userinfo.email");
3838

39-
private static String streamToString(InputStream inputStream) throws IOException {
40-
StringBuilder stringBuilder = new StringBuilder();
41-
Reader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
42-
char[] buffer = new char[256];
43-
int length;
39+
private FirebaseCredentials() {
40+
}
4441

45-
while ((length = reader.read(buffer)) != -1) {
46-
stringBuilder.append(buffer, 0, length);
47-
}
48-
inputStream.close();
49-
return stringBuilder.toString();
42+
private static String streamToString(InputStream inputStream) throws IOException {
43+
InputStreamReader reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
44+
return CharStreams.toString(reader);
5045
}
5146

5247
/**
@@ -84,16 +79,17 @@ static FirebaseCredential applicationDefault(HttpTransport transport, JsonFactor
8479
* service account certificate.
8580
* @return A {@link FirebaseCredential} generated from the provided service account certificate
8681
* which can be used to authenticate the SDK.
82+
* @throws IOException If an error occurs while parsing the service account certificate.
8783
*/
8884
@NonNull
89-
public static FirebaseCredential fromCertificate(InputStream serviceAccount) {
90-
return fromCertificate(serviceAccount,
91-
Utils.getDefaultTransport(), Utils.getDefaultJsonFactory());
85+
public static FirebaseCredential fromCertificate(InputStream serviceAccount) throws IOException {
86+
return fromCertificate(serviceAccount, Utils.getDefaultTransport(),
87+
Utils.getDefaultJsonFactory());
9288
}
9389

9490
@VisibleForTesting
95-
static FirebaseCredential fromCertificate(
96-
InputStream serviceAccount, HttpTransport transport, JsonFactory jsonFactory) {
91+
static FirebaseCredential fromCertificate(InputStream serviceAccount, HttpTransport transport,
92+
JsonFactory jsonFactory) throws IOException {
9793
return new CertCredential(serviceAccount, transport, jsonFactory);
9894
}
9995

@@ -108,16 +104,17 @@ static FirebaseCredential fromCertificate(
108104
* token.
109105
* @return A {@link FirebaseCredential} generated from the provided service account credential
110106
* which can be used to authenticate the SDK.
107+
* @throws IOException If an error occurs while parsing the refresh token.
111108
*/
112109
@NonNull
113-
public static FirebaseCredential fromRefreshToken(InputStream refreshToken) {
110+
public static FirebaseCredential fromRefreshToken(InputStream refreshToken) throws IOException {
114111
return fromRefreshToken(
115112
refreshToken, Utils.getDefaultTransport(), Utils.getDefaultJsonFactory());
116113
}
117114

118115
@VisibleForTesting
119-
static FirebaseCredential fromRefreshToken(
120-
final InputStream refreshToken, HttpTransport transport, JsonFactory jsonFactory) {
116+
static FirebaseCredential fromRefreshToken(final InputStream refreshToken,
117+
HttpTransport transport, JsonFactory jsonFactory) throws IOException {
121118
return new RefreshTokenCredential(refreshToken, transport, jsonFactory);
122119
}
123120

@@ -145,10 +142,10 @@ abstract static class BaseCredential implements FirebaseCredential {
145142
}
146143

147144
/** Retrieves a GoogleCredential. Should not use caching. */
148-
abstract GoogleCredential fetchCredential() throws Exception;
145+
abstract GoogleCredential fetchCredential() throws IOException;
149146

150147
/** Retrieves an access token from a GoogleCredential. Should not use caching. */
151-
abstract FirebaseAccessToken fetchToken(GoogleCredential credential) throws Exception;
148+
abstract FirebaseAccessToken fetchToken(GoogleCredential credential) throws IOException;
152149

153150
/**
154151
* Returns the associated GoogleCredential for this class. This implementation is cached by
@@ -223,30 +220,23 @@ public String then(@NonNull Task<FirebaseAccessToken> task) throws Exception {
223220

224221
static class CertCredential extends BaseCredential {
225222

226-
private String jsonData;
227-
private String projectId;
228-
private Exception streamException;
223+
private final String jsonData;
224+
private final String projectId;
229225

230-
CertCredential(InputStream inputStream, HttpTransport transport, JsonFactory jsonFactory) {
226+
CertCredential(InputStream inputStream, HttpTransport transport,
227+
JsonFactory jsonFactory) throws IOException {
231228
super(transport, jsonFactory);
229+
jsonData = streamToString(checkNotNull(inputStream));
230+
JSONObject jsonObject = new JSONObject(jsonData);
232231
try {
233-
jsonData = streamToString(checkNotNull(inputStream));
234-
JSONObject jsonObject = new JSONObject(jsonData);
235232
projectId = jsonObject.getString("project_id");
236-
} catch (IOException e) {
237-
streamException = new IOException("Failed to read service account", e);
238233
} catch (JSONException e) {
239-
streamException =
240-
new JSONException("Failed to parse service account: 'project_id' must be set");
234+
throw new IOException("Failed to parse service account: 'project_id' must be set", e);
241235
}
242236
}
243237

244238
@Override
245-
GoogleCredential fetchCredential() throws Exception {
246-
if (streamException != null) {
247-
throw streamException;
248-
}
249-
239+
GoogleCredential fetchCredential() throws IOException {
250240
GoogleCredential firebaseCredential =
251241
GoogleCredential.fromStream(
252242
new ByteArrayInputStream(jsonData.getBytes("UTF-8")), transport, jsonFactory);
@@ -261,20 +251,12 @@ GoogleCredential fetchCredential() throws Exception {
261251
}
262252

263253
@Override
264-
FirebaseAccessToken fetchToken(GoogleCredential credential) throws Exception {
265-
if (streamException != null) {
266-
throw streamException;
267-
}
268-
254+
FirebaseAccessToken fetchToken(GoogleCredential credential) throws IOException {
269255
credential.refreshToken();
270256
return new FirebaseAccessToken(credential, clock);
271257
}
272258

273-
Task<String> getProjectId(boolean forceRefresh) {
274-
if (streamException != null) {
275-
return Tasks.forException(streamException);
276-
}
277-
259+
Task<String> getProjectId() {
278260
return Tasks.forResult(projectId);
279261
}
280262
}
@@ -286,39 +268,30 @@ static class ApplicationDefaultCredential extends BaseCredential {
286268
}
287269

288270
@Override
289-
GoogleCredential fetchCredential() throws Exception {
271+
GoogleCredential fetchCredential() throws IOException {
290272
return GoogleCredential.getApplicationDefault(transport, jsonFactory)
291273
.createScoped(FIREBASE_SCOPES);
292274
}
293275

294276
@Override
295-
FirebaseAccessToken fetchToken(GoogleCredential credential) throws Exception {
277+
FirebaseAccessToken fetchToken(GoogleCredential credential) throws IOException {
296278
credential.refreshToken();
297279
return new FirebaseAccessToken(credential, clock);
298280
}
299281
}
300282

301283
static class RefreshTokenCredential extends BaseCredential {
302284

303-
private String jsonData;
304-
private Exception streamException;
285+
private final String jsonData;
305286

306-
RefreshTokenCredential(
307-
InputStream inputStream, HttpTransport transport, JsonFactory jsonFactory) {
287+
RefreshTokenCredential(InputStream inputStream, HttpTransport transport,
288+
JsonFactory jsonFactory) throws IOException {
308289
super(transport, jsonFactory);
309-
try {
310-
jsonData = streamToString(checkNotNull(inputStream));
311-
} catch (IOException e) {
312-
streamException = new IOException("Failed to read refresh token", e);
313-
}
290+
jsonData = streamToString(checkNotNull(inputStream));
314291
}
315292

316293
@Override
317-
GoogleCredential fetchCredential() throws Exception {
318-
if (streamException != null) {
319-
throw streamException;
320-
}
321-
294+
GoogleCredential fetchCredential() throws IOException {
322295
GoogleCredential credential =
323296
GoogleCredential.fromStream(
324297
new ByteArrayInputStream(jsonData.getBytes("UTF-8")), transport, jsonFactory);
@@ -333,11 +306,7 @@ GoogleCredential fetchCredential() throws Exception {
333306
}
334307

335308
@Override
336-
FirebaseAccessToken fetchToken(GoogleCredential credential) throws Exception {
337-
if (streamException != null) {
338-
throw streamException;
339-
}
340-
309+
FirebaseAccessToken fetchToken(GoogleCredential credential) throws IOException {
341310
credential.refreshToken();
342311
return new FirebaseAccessToken(credential, clock);
343312
}

src/test/java/com/google/firebase/FirebaseAppTest.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import com.google.firebase.tasks.Tasks;
2828
import com.google.firebase.testing.FirebaseAppRule;
2929
import com.google.firebase.testing.ServiceAccount;
30+
import com.google.firebase.testing.TestUtils;
31+
import java.io.IOException;
3032
import java.lang.reflect.InvocationTargetException;
3133
import java.lang.reflect.Method;
3234
import java.lang.reflect.Modifier;
@@ -50,7 +52,7 @@ public class FirebaseAppTest {
5052

5153
private static final FirebaseOptions OPTIONS =
5254
new FirebaseOptions.Builder()
53-
.setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))
55+
.setCredential(TestUtils.getCertCredential(ServiceAccount.EDITOR.asStream()))
5456
.build();
5557
private static final FirebaseOptions MOCK_CREDENTIAL_OPTIONS =
5658
new Builder().setCredential(new MockFirebaseCredential()).build();
@@ -154,7 +156,7 @@ public void testGetNullApp() {
154156
}
155157

156158
@Test
157-
public void testToString() {
159+
public void testToString() throws IOException {
158160
FirebaseOptions options =
159161
new FirebaseOptions.Builder()
160162
.setCredential(FirebaseCredentials.fromCertificate(ServiceAccount.EDITOR.asStream()))

0 commit comments

Comments
 (0)