diff --git a/firebase-installations/api.txt b/firebase-installations/api.txt index 4acee522a18..b4fd32edc8a 100644 --- a/firebase-installations/api.txt +++ b/firebase-installations/api.txt @@ -17,9 +17,12 @@ package com.google.firebase.installations { } public enum FirebaseInstallationsException.Status { - enum_constant public static final com.google.firebase.installations.FirebaseInstallationsException.Status AUTHENTICATION_ERROR; - enum_constant public static final com.google.firebase.installations.FirebaseInstallationsException.Status CLIENT_ERROR; - enum_constant public static final com.google.firebase.installations.FirebaseInstallationsException.Status SDK_INTERNAL_ERROR; + enum_constant public static final com.google.firebase.installations.FirebaseInstallationsException.Status BAD_CONFIG; + } + + public class RandomFidGenerator { + ctor public RandomFidGenerator(); + method @NonNull public String createRandomFid(); } } @@ -33,12 +36,13 @@ package com.google.firebase.installations.local { public class PersistedInstallation { ctor public PersistedInstallation(@NonNull FirebaseApp); - method @NonNull public boolean clear(); - method @NonNull public boolean insertOrUpdatePersistedInstallationEntry(@NonNull com.google.firebase.installations.local.PersistedInstallationEntry); + method public void clearForTesting(); + method @NonNull public com.google.firebase.installations.local.PersistedInstallationEntry insertOrUpdatePersistedInstallationEntry(@NonNull com.google.firebase.installations.local.PersistedInstallationEntry); method @NonNull public com.google.firebase.installations.local.PersistedInstallationEntry readPersistedInstallationEntryValue(); } public enum PersistedInstallation.RegistrationStatus { + enum_constant public static final com.google.firebase.installations.local.PersistedInstallation.RegistrationStatus ATTEMPT_MIGRATION; enum_constant public static final com.google.firebase.installations.local.PersistedInstallation.RegistrationStatus NOT_GENERATED; enum_constant public static final com.google.firebase.installations.local.PersistedInstallation.RegistrationStatus REGISTERED; enum_constant public static final com.google.firebase.installations.local.PersistedInstallation.RegistrationStatus REGISTER_ERROR; @@ -59,7 +63,15 @@ package com.google.firebase.installations.local { method public boolean isNotGenerated(); method public boolean isRegistered(); method public boolean isUnregistered(); + method public boolean shouldAttemptMigration(); method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallationEntry.Builder toBuilder(); + method @NonNull public com.google.firebase.installations.local.PersistedInstallationEntry withAuthToken(@NonNull String, long, long); + method @NonNull public com.google.firebase.installations.local.PersistedInstallationEntry withClearedAuthToken(); + method @NonNull public com.google.firebase.installations.local.PersistedInstallationEntry withFisError(@NonNull String); + method @NonNull public com.google.firebase.installations.local.PersistedInstallationEntry withNoGeneratedFid(); + method @NonNull public com.google.firebase.installations.local.PersistedInstallationEntry withRegisteredFid(@NonNull String, @NonNull String, long, @Nullable String, long); + method @NonNull public com.google.firebase.installations.local.PersistedInstallationEntry withUnregisteredFid(@NonNull String); + field @NonNull public static com.google.firebase.installations.local.PersistedInstallationEntry INSTANCE; } public abstract static class PersistedInstallationEntry.Builder { @@ -107,8 +119,8 @@ package com.google.firebase.installations.remote { } public enum InstallationResponse.ResponseCode { + enum_constant public static final com.google.firebase.installations.remote.InstallationResponse.ResponseCode BAD_CONFIG; enum_constant public static final com.google.firebase.installations.remote.InstallationResponse.ResponseCode OK; - enum_constant public static final com.google.firebase.installations.remote.InstallationResponse.ResponseCode SERVER_ERROR; } public abstract class TokenResult { @@ -117,7 +129,6 @@ package com.google.firebase.installations.remote { method @Nullable public abstract com.google.firebase.installations.remote.TokenResult.ResponseCode getResponseCode(); method @Nullable public abstract String getToken(); method @NonNull public abstract long getTokenExpirationTimestamp(); - method public boolean isSuccessful(); method @NonNull public abstract com.google.firebase.installations.remote.TokenResult.Builder toBuilder(); } @@ -130,9 +141,9 @@ package com.google.firebase.installations.remote { } public enum TokenResult.ResponseCode { - enum_constant public static final com.google.firebase.installations.remote.TokenResult.ResponseCode FID_ERROR; + enum_constant public static final com.google.firebase.installations.remote.TokenResult.ResponseCode AUTH_ERROR; + enum_constant public static final com.google.firebase.installations.remote.TokenResult.ResponseCode BAD_CONFIG; enum_constant public static final com.google.firebase.installations.remote.TokenResult.ResponseCode OK; - enum_constant public static final com.google.firebase.installations.remote.TokenResult.ResponseCode REFRESH_TOKEN_ERROR; } } diff --git a/firebase-installations/src/androidTest/java/com/google/firebase/installations/FakeCalendar.java b/firebase-installations/src/androidTest/java/com/google/firebase/installations/FakeCalendar.java new file mode 100644 index 00000000000..3190d5911e8 --- /dev/null +++ b/firebase-installations/src/androidTest/java/com/google/firebase/installations/FakeCalendar.java @@ -0,0 +1,69 @@ +// Copyright 2019 Google LLC +// +// 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.installations; + +import java.util.Calendar; + +public class FakeCalendar extends Calendar { + private long timeInMillis; + + public FakeCalendar(long initialTimeInMillis) { + timeInMillis = initialTimeInMillis; + } + + public long getTimeInMillis() { + return timeInMillis; + } + + public void setTimeInMillis(long timeInMillis) { + this.timeInMillis = timeInMillis; + } + + public void advanceTimeBySeconds(long deltaSeconds) { + timeInMillis += (deltaSeconds * 1000L); + } + + @Override + protected void computeTime() {} + + @Override + protected void computeFields() {} + + @Override + public void add(int i, int i1) {} + + @Override + public void roll(int i, boolean b) {} + + @Override + public int getMinimum(int i) { + return 0; + } + + @Override + public int getMaximum(int i) { + return 0; + } + + @Override + public int getGreatestMinimum(int i) { + return 0; + } + + @Override + public int getLeastMaximum(int i) { + return 0; + } +} diff --git a/firebase-installations/src/androidTest/java/com/google/firebase/installations/FirebaseInstallationsInstrumentedTest.java b/firebase-installations/src/androidTest/java/com/google/firebase/installations/FirebaseInstallationsInstrumentedTest.java index 1f8bb1de203..4fec8c4cce5 100644 --- a/firebase-installations/src/androidTest/java/com/google/firebase/installations/FirebaseInstallationsInstrumentedTest.java +++ b/firebase-installations/src/androidTest/java/com/google/firebase/installations/FirebaseInstallationsInstrumentedTest.java @@ -15,16 +15,12 @@ package com.google.firebase.installations; import static com.google.common.truth.Truth.assertWithMessage; -import static com.google.firebase.installations.FisAndroidTestConstants.DEFAULT_PERSISTED_INSTALLATION_ENTRY; -import static com.google.firebase.installations.FisAndroidTestConstants.INVALID_TEST_FID; -import static com.google.firebase.installations.FisAndroidTestConstants.SERVER_ERROR_INSTALLATION_RESPONSE; import static com.google.firebase.installations.FisAndroidTestConstants.TEST_API_KEY; import static com.google.firebase.installations.FisAndroidTestConstants.TEST_APP_ID_1; import static com.google.firebase.installations.FisAndroidTestConstants.TEST_AUTH_TOKEN; import static com.google.firebase.installations.FisAndroidTestConstants.TEST_AUTH_TOKEN_2; import static com.google.firebase.installations.FisAndroidTestConstants.TEST_AUTH_TOKEN_3; import static com.google.firebase.installations.FisAndroidTestConstants.TEST_AUTH_TOKEN_4; -import static com.google.firebase.installations.FisAndroidTestConstants.TEST_CREATION_TIMESTAMP_1; import static com.google.firebase.installations.FisAndroidTestConstants.TEST_CREATION_TIMESTAMP_2; import static com.google.firebase.installations.FisAndroidTestConstants.TEST_FID_1; import static com.google.firebase.installations.FisAndroidTestConstants.TEST_INSTALLATION_RESPONSE; @@ -33,18 +29,19 @@ import static com.google.firebase.installations.FisAndroidTestConstants.TEST_PROJECT_ID; import static com.google.firebase.installations.FisAndroidTestConstants.TEST_REFRESH_TOKEN; import static com.google.firebase.installations.FisAndroidTestConstants.TEST_TOKEN_EXPIRATION_TIMESTAMP; -import static com.google.firebase.installations.FisAndroidTestConstants.TEST_TOKEN_EXPIRATION_TIMESTAMP_2; import static com.google.firebase.installations.FisAndroidTestConstants.TEST_TOKEN_RESULT; import static com.google.firebase.installations.local.PersistedInstallationEntrySubject.assertThat; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; -import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.matches; import static org.mockito.Mockito.doAnswer; -import static org.mockito.Mockito.doNothing; import static org.mockito.Mockito.doThrow; import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; import androidx.test.core.app.ApplicationProvider; @@ -54,12 +51,16 @@ import com.google.firebase.FirebaseApp; import com.google.firebase.FirebaseException; import com.google.firebase.FirebaseOptions; +import com.google.firebase.installations.FirebaseInstallationsException.Status; import com.google.firebase.installations.local.IidStore; import com.google.firebase.installations.local.PersistedInstallation; import com.google.firebase.installations.local.PersistedInstallation.RegistrationStatus; import com.google.firebase.installations.local.PersistedInstallationEntry; import com.google.firebase.installations.remote.FirebaseInstallationServiceClient; +import com.google.firebase.installations.remote.InstallationResponse; +import com.google.firebase.installations.remote.InstallationResponse.ResponseCode; import com.google.firebase.installations.remote.TokenResult; +import java.io.IOException; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.LinkedBlockingQueue; @@ -86,13 +87,9 @@ public class FirebaseInstallationsInstrumentedTest { private FirebaseApp firebaseApp; private ExecutorService executor; private PersistedInstallation persistedInstallation; - @Mock private FirebaseInstallationServiceClient backendClientReturnsOk; - @Mock private FirebaseInstallationServiceClient backendClientReturnsError; - @Mock private PersistedInstallation persistedInstallationReturnsError; - @Mock private Utils mockUtils; - @Mock private PersistedInstallation mockPersistedInstallation; - @Mock private FirebaseInstallationServiceClient mockClient; + @Mock private FirebaseInstallationServiceClient mockBackend; @Mock private IidStore mockIidStore; + @Mock private RandomFidGenerator mockFidGenerator; private static final PersistedInstallationEntry REGISTERED_INSTALLATION_ENTRY = PersistedInstallationEntry.builder() @@ -104,61 +101,16 @@ public class FirebaseInstallationsInstrumentedTest { .setRegistrationStatus(PersistedInstallation.RegistrationStatus.REGISTERED) .build(); - private static final PersistedInstallationEntry REGISTERED_IID_ENTRY = - PersistedInstallationEntry.builder() - .setFirebaseInstallationId(TEST_INSTANCE_ID_1) - .setAuthToken(TEST_AUTH_TOKEN) - .setRefreshToken(TEST_REFRESH_TOKEN) - .setTokenCreationEpochInSecs(TEST_CREATION_TIMESTAMP_2) - .setExpiresInSecs(TEST_TOKEN_EXPIRATION_TIMESTAMP) - .setRegistrationStatus(PersistedInstallation.RegistrationStatus.REGISTERED) - .build(); - - private static final PersistedInstallationEntry EXPIRED_AUTH_TOKEN_ENTRY = - PersistedInstallationEntry.builder() - .setFirebaseInstallationId(TEST_FID_1) - .setAuthToken(TEST_AUTH_TOKEN) - .setRefreshToken(TEST_REFRESH_TOKEN) - .setTokenCreationEpochInSecs(TEST_CREATION_TIMESTAMP_1) - .setExpiresInSecs(TEST_TOKEN_EXPIRATION_TIMESTAMP_2) - .setRegistrationStatus(PersistedInstallation.RegistrationStatus.REGISTERED) - .build(); - - private static final PersistedInstallationEntry UNREGISTERED_INSTALLATION_ENTRY = - PersistedInstallationEntry.builder() - .setFirebaseInstallationId(TEST_FID_1) - .setAuthToken("") - .setRefreshToken("") - .setTokenCreationEpochInSecs(TEST_CREATION_TIMESTAMP_1) - .setExpiresInSecs(0) - .setRegistrationStatus(PersistedInstallation.RegistrationStatus.UNREGISTERED) - .build(); - - private static final PersistedInstallationEntry INVALID_INSTALLATION_ENTRY = - PersistedInstallationEntry.builder() - .setFirebaseInstallationId(INVALID_TEST_FID) - .setAuthToken("") - .setRefreshToken("") - .setTokenCreationEpochInSecs(TEST_CREATION_TIMESTAMP_1) - .setExpiresInSecs(0) - .setRegistrationStatus(PersistedInstallation.RegistrationStatus.UNREGISTERED) - .build(); - - private static final PersistedInstallationEntry UPDATED_AUTH_TOKEN_ENTRY = - PersistedInstallationEntry.builder() - .setFirebaseInstallationId(TEST_FID_1) - .setAuthToken(TEST_AUTH_TOKEN_2) - .setRefreshToken(TEST_REFRESH_TOKEN) - .setTokenCreationEpochInSecs(TEST_CREATION_TIMESTAMP_2) - .setExpiresInSecs(TEST_TOKEN_EXPIRATION_TIMESTAMP) - .setRegistrationStatus(PersistedInstallation.RegistrationStatus.REGISTERED) - .build(); + private FirebaseInstallations firebaseInstallations; + private Utils utils; + private FakeCalendar fakeCalendar; @Before - public void setUp() throws FirebaseException { + public void setUp() throws FirebaseException, IOException { MockitoAnnotations.initMocks(this); FirebaseApp.clearInstancesForTest(); executor = new ThreadPoolExecutor(0, 1, 30L, TimeUnit.SECONDS, new LinkedBlockingQueue<>()); + fakeCalendar = new FakeCalendar(5000000L); firebaseApp = FirebaseApp.initializeApp( ApplicationProvider.getApplicationContext(), @@ -168,69 +120,188 @@ public void setUp() throws FirebaseException { .setApiKey(TEST_API_KEY) .build()); persistedInstallation = new PersistedInstallation(firebaseApp); + persistedInstallation.clearForTesting(); - when(backendClientReturnsOk.createFirebaseInstallation( - anyString(), anyString(), anyString(), anyString())) - .thenReturn(TEST_INSTALLATION_RESPONSE); - // Mocks successful auth token generation - when(backendClientReturnsOk.generateAuthToken( - anyString(), anyString(), anyString(), anyString())) - .thenReturn(TEST_TOKEN_RESULT); + utils = new Utils(fakeCalendar); + firebaseInstallations = + new FirebaseInstallations( + executor, + firebaseApp, + mockBackend, + persistedInstallation, + utils, + mockIidStore, + mockFidGenerator); - when(persistedInstallationReturnsError.insertOrUpdatePersistedInstallationEntry(any())) - .thenReturn(false); - when(persistedInstallationReturnsError.readPersistedInstallationEntryValue()) - .thenReturn(DEFAULT_PERSISTED_INSTALLATION_ENTRY); + when(mockFidGenerator.createRandomFid()).thenReturn(TEST_FID_1); + } - when(backendClientReturnsError.createFirebaseInstallation( - anyString(), anyString(), anyString(), anyString())) - .thenThrow(new FirebaseException("SDK Error")); + @After + public void cleanUp() { + persistedInstallation.clearForTesting(); + try { + executor.awaitTermination(250, TimeUnit.MILLISECONDS); + } catch (InterruptedException e) { - when(mockUtils.createRandomFid()).thenReturn(TEST_FID_1); - when(mockUtils.currentTimeInSecs()).thenReturn(TEST_CREATION_TIMESTAMP_2); + } + } - // Mocks success on FIS deletion - doNothing() - .when(backendClientReturnsOk) - .deleteFirebaseInstallation(anyString(), anyString(), anyString(), anyString()); - // Mocks server error on FIS deletion - doThrow(new FirebaseException("Server Error")) - .when(backendClientReturnsError) - .deleteFirebaseInstallation(anyString(), anyString(), anyString(), anyString()); + /** + * Check the id generation process when there is no network. There are three cases: + * + *
If a FID does not yet exist it generate a new FID, either from an existing IID or generated
+ * randomly. If an IID exists and this is the first time a FID has been generated for this
+ * installation, the IID will be used as the FID. If the FID is ever cleared then the next time a
+ * FID is generated the IID is ignored and a FID is generated randomly.
+ *
+ * @return a new version of the prefs that includes the new FID. These prefs will have already
+ * been persisted.
+ */
+ private PersistedInstallationEntry getPrefsWithGeneratedIdMultiProcessSafe() {
+ FileLock fileLock = getCrossProcessLock();
+ try {
+ synchronized (lockGenerateFid) {
+ PersistedInstallationEntry prefs =
+ persistedInstallation.readPersistedInstallationEntryValue();
+ // Check if a new FID needs to be created
+ if (prefs.isNotGenerated()) {
+ // For a default firebase installation read the existing iid. For other custom firebase
+ // installations create a new fid
+
+ // Only one single thread from one single process can execute this block
+ // at any given time.
+ String fid = readExistingIidOrCreateFid(prefs);
+ prefs =
+ persistedInstallation.insertOrUpdatePersistedInstallationEntry(
+ prefs.withUnregisteredFid(fid));
}
+ return prefs;
}
- TokenResult tokenResult = null;
- // Refresh Auth token if needed
- if (needRefresh) {
- tokenResult = fetchAuthTokenFromServer(persistedInstallationEntry);
- persistedInstallationEntry = persistedInstallation.readPersistedInstallationEntryValue();
- synchronized (lock) {
- shouldRefreshAuthToken = false;
- }
- }
+ } finally {
+ releaseCrossProcessLock(fileLock);
+ }
+ }
- // If tokenResult is not null and is not successful, it was cleared due to authentication
- // error during auth token generation.
- if (tokenResult != null && !tokenResult.isSuccessful()) {
- triggerOnException(
- persistedInstallationEntry,
- new FirebaseInstallationsException(
- "Failed to generate auth token for this Firebase Installation. Call getId() "
- + "to recreate a new Fid and a valid auth token.",
- FirebaseInstallationsException.Status.AUTHENTICATION_ERROR));
- return;
- }
+ /** Use file locking to acquire a lock that will also block other processes. */
+ private FileLock getCrossProcessLock() {
+ try {
+ File file =
+ new File(firebaseApp.getApplicationContext().getFilesDir(), LOCKFILE_NAME_GENERATE_FID);
+ FileChannel channel = new RandomAccessFile(file, "rw").getChannel();
+ // Use the file channel to create a lock on the file.
+ // This method blocks until it can retrieve the lock.
+ return channel.lock();
+ } catch (IOException e) {
+ throw new IllegalStateException("exception while using file locks, should never happen", e);
+ }
+ }
- triggerOnStateReached(persistedInstallationEntry);
- } catch (Exception e) {
- PersistedInstallationEntry persistedInstallationEntry =
- persistedInstallation.readPersistedInstallationEntryValue();
- PersistedInstallationEntry errorInstallationEntry =
- persistedInstallationEntry
- .toBuilder()
- .setFisError(e.getMessage())
- .setRegistrationStatus(RegistrationStatus.REGISTER_ERROR)
- .build();
- persistedInstallation.insertOrUpdatePersistedInstallationEntry(errorInstallationEntry);
- triggerOnException(errorInstallationEntry, e);
+ /** Release a previously acquired lock. */
+ private void releaseCrossProcessLock(FileLock fileLock) {
+ try {
+ fileLock.release();
+ } catch (IOException e) {
+ throw new IllegalStateException("exception while using file locks, should never happen", e);
}
}
- private String readExistingIidOrCreateFid() {
+ private String readExistingIidOrCreateFid(PersistedInstallationEntry prefs) {
// Check if this firebase app is the default (first initialized) instance
- if (!firebaseApp.equals(FirebaseApp.getInstance())) {
- return utils.createRandomFid();
+ if (!firebaseApp.equals(FirebaseApp.getInstance()) || !prefs.shouldAttemptMigration()) {
+ return fidGenerator.createRandomFid();
}
// For a default firebase installation, read the existing iid from shared prefs
String fid = iidStore.readIid();
if (fid == null) {
- fid = utils.createRandomFid();
+ fid = fidGenerator.createRandomFid();
}
return fid;
}
- private void persistFid(String fid) throws FirebaseInstallationsException {
- boolean firstUpdateCacheResult =
- persistedInstallation.insertOrUpdatePersistedInstallationEntry(
- PersistedInstallationEntry.builder()
- .setFirebaseInstallationId(fid)
- .setRegistrationStatus(RegistrationStatus.UNREGISTERED)
- .build());
-
- if (!firstUpdateCacheResult) {
- throw new FirebaseInstallationsException(
- "Failed to update client side cache.",
- FirebaseInstallationsException.Status.CLIENT_ERROR);
+ /** Registers the created Fid with FIS servers and update the persisted state. */
+ private PersistedInstallationEntry registerFidWithServer(PersistedInstallationEntry prefs)
+ throws IOException {
+ InstallationResponse response =
+ serviceClient.createFirebaseInstallation(
+ /*apiKey= */ firebaseApp.getOptions().getApiKey(),
+ /*fid= */ prefs.getFirebaseInstallationId(),
+ /*projectID= */ firebaseApp.getOptions().getProjectId(),
+ /*appId= */ getApplicationId());
+
+ switch (response.getResponseCode()) {
+ case OK:
+ return prefs.withRegisteredFid(
+ response.getFid(),
+ response.getRefreshToken(),
+ utils.currentTimeInSecs(),
+ response.getAuthToken().getToken(),
+ response.getAuthToken().getTokenExpirationTimestamp());
+ case BAD_CONFIG:
+ return prefs.withFisError("BAD CONFIG");
+ default:
+ throw new IOException();
}
}
- /** Registers the created Fid with FIS servers and update the shared prefs. */
- private Void registerAndSaveFid(PersistedInstallationEntry persistedInstallationEntry)
- throws FirebaseInstallationsException {
- try {
- long creationTime = utils.currentTimeInSecs();
-
- InstallationResponse installationResponse =
- serviceClient.createFirebaseInstallation(
- /*apiKey= */ firebaseApp.getOptions().getApiKey(),
- /*fid= */ persistedInstallationEntry.getFirebaseInstallationId(),
- /*projectID= */ firebaseApp.getOptions().getProjectId(),
- /*appId= */ getApplicationId());
- if (installationResponse.getResponseCode() == ResponseCode.OK) {
- persistedInstallation.insertOrUpdatePersistedInstallationEntry(
- PersistedInstallationEntry.builder()
- .setFirebaseInstallationId(installationResponse.getFid())
- .setRegistrationStatus(RegistrationStatus.REGISTERED)
- .setAuthToken(installationResponse.getAuthToken().getToken())
- .setRefreshToken(installationResponse.getRefreshToken())
- .setExpiresInSecs(installationResponse.getAuthToken().getTokenExpirationTimestamp())
- .setTokenCreationEpochInSecs(creationTime)
- .build());
- }
-
- } catch (FirebaseException exception) {
- throw new FirebaseInstallationsException(
- exception.getMessage(), FirebaseInstallationsException.Status.SDK_INTERNAL_ERROR);
- }
- return null;
- }
-
- /** Calls the FIS servers to generate an auth token for this Firebase installation. */
- private TokenResult fetchAuthTokenFromServer(
- PersistedInstallationEntry persistedInstallationEntry) throws FirebaseInstallationsException {
- try {
- long creationTime = utils.currentTimeInSecs();
- TokenResult tokenResult =
- serviceClient.generateAuthToken(
- /*apiKey= */ firebaseApp.getOptions().getApiKey(),
- /*fid= */ persistedInstallationEntry.getFirebaseInstallationId(),
- /*projectID= */ firebaseApp.getOptions().getProjectId(),
- /*refreshToken= */ persistedInstallationEntry.getRefreshToken());
-
- if (tokenResult.isSuccessful()) {
- persistedInstallation.insertOrUpdatePersistedInstallationEntry(
- persistedInstallationEntry
- .toBuilder()
- .setRegistrationStatus(RegistrationStatus.REGISTERED)
- .setAuthToken(tokenResult.getToken())
- .setExpiresInSecs(tokenResult.getTokenExpirationTimestamp())
- .setTokenCreationEpochInSecs(creationTime)
- .build());
- } else {
- persistedInstallation.clear();
- }
- return tokenResult;
-
- } catch (FirebaseException exception) {
- throw new FirebaseInstallationsException(
- "Failed to generate auth token for a Firebase Installation.",
- FirebaseInstallationsException.Status.SDK_INTERNAL_ERROR);
+ /**
+ * Calls the FIS servers to generate an auth token for this Firebase installation. Returns a
+ * PersistedInstallationEntry with the new authtoken. The authtoken in the returned
+ * PersistedInstallationEntry will be "expired" if the server refuses to generate an auth token
+ * for the fid.
+ */
+ private PersistedInstallationEntry fetchAuthTokenFromServer(
+ @NonNull PersistedInstallationEntry prefs) throws IOException {
+ TokenResult tokenResult =
+ serviceClient.generateAuthToken(
+ /*apiKey= */ firebaseApp.getOptions().getApiKey(),
+ /*fid= */ prefs.getFirebaseInstallationId(),
+ /*projectID= */ firebaseApp.getOptions().getProjectId(),
+ /*refreshToken= */ prefs.getRefreshToken());
+
+ switch (tokenResult.getResponseCode()) {
+ case OK:
+ return prefs.withAuthToken(
+ tokenResult.getToken(),
+ tokenResult.getTokenExpirationTimestamp(),
+ utils.currentTimeInSecs());
+ case BAD_CONFIG:
+ return prefs.withFisError("BAD CONFIG");
+ case AUTH_ERROR:
+ // The the server refused to generate a new auth token due to bad credentials, clear the
+ // FID to force the generation of a new one.
+ return prefs.withNoGeneratedFid();
+ default:
+ throw new IOException();
}
}
@@ -402,28 +433,24 @@ private TokenResult fetchAuthTokenFromServer(
* Deletes the firebase installation id of the {@link FirebaseApp} from FIS servers and local
* storage.
*/
- private Void deleteFirebaseInstallationId() throws FirebaseInstallationsException {
-
- PersistedInstallationEntry persistedInstallationEntry =
- persistedInstallation.readPersistedInstallationEntryValue();
-
- if (persistedInstallationEntry.isRegistered()) {
+ private Void deleteFirebaseInstallationId() throws FirebaseInstallationsException, IOException {
+ PersistedInstallationEntry entry = persistedInstallation.readPersistedInstallationEntryValue();
+ if (entry.isRegistered()) {
// Call the FIS servers to delete this Firebase Installation Id.
try {
serviceClient.deleteFirebaseInstallation(
firebaseApp.getOptions().getApiKey(),
- persistedInstallationEntry.getFirebaseInstallationId(),
+ entry.getFirebaseInstallationId(),
firebaseApp.getOptions().getProjectId(),
- persistedInstallationEntry.getRefreshToken());
+ entry.getRefreshToken());
} catch (FirebaseException exception) {
throw new FirebaseInstallationsException(
- "Failed to delete a Firebase Installation.",
- FirebaseInstallationsException.Status.SDK_INTERNAL_ERROR);
+ "Failed to delete a Firebase Installation.", Status.BAD_CONFIG);
}
}
- persistedInstallation.clear();
+ persistedInstallation.insertOrUpdatePersistedInstallationEntry(entry.withNoGeneratedFid());
return null;
}
}
diff --git a/firebase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallationsException.java b/firebase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallationsException.java
index 4309a782ffd..07683203570 100644
--- a/firebase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallationsException.java
+++ b/firebase-installations/src/main/java/com/google/firebase/installations/FirebaseInstallationsException.java
@@ -19,13 +19,12 @@
/** The class for all Exceptions thrown by {@link FirebaseInstallations}. */
public class FirebaseInstallationsException extends FirebaseException {
- // TODO(ankitagj): Improve exception handling and java doc
public enum Status {
- SDK_INTERNAL_ERROR,
-
- CLIENT_ERROR,
-
- AUTHENTICATION_ERROR
+ /**
+ * Indicates that the caller is misconfigured, usually with a bad or misconfigured API Key or
+ * Project.
+ */
+ BAD_CONFIG,
}
@NonNull private final Status status;
diff --git a/firebase-installations/src/main/java/com/google/firebase/installations/GetAuthTokenListener.java b/firebase-installations/src/main/java/com/google/firebase/installations/GetAuthTokenListener.java
index bc36a20bac6..2c067a30507 100644
--- a/firebase-installations/src/main/java/com/google/firebase/installations/GetAuthTokenListener.java
+++ b/firebase-installations/src/main/java/com/google/firebase/installations/GetAuthTokenListener.java
@@ -28,12 +28,10 @@ public GetAuthTokenListener(
}
@Override
- public boolean onStateReached(
- PersistedInstallationEntry persistedInstallationEntry, boolean shouldRefreshAuthToken) {
+ public boolean onStateReached(PersistedInstallationEntry persistedInstallationEntry) {
// AuthTokenListener state is reached when FID is registered and has a valid auth token
if (persistedInstallationEntry.isRegistered()
- && !utils.isAuthTokenExpired(persistedInstallationEntry)
- && !shouldRefreshAuthToken) {
+ && !utils.isAuthTokenExpired(persistedInstallationEntry)) {
resultTaskCompletionSource.setResult(
InstallationTokenResult.builder()
.setToken(persistedInstallationEntry.getAuthToken())
@@ -48,7 +46,9 @@ public boolean onStateReached(
@Override
public boolean onException(
PersistedInstallationEntry persistedInstallationEntry, Exception exception) {
- if (persistedInstallationEntry.isErrored() || persistedInstallationEntry.isNotGenerated()) {
+ if (persistedInstallationEntry.isErrored()
+ || persistedInstallationEntry.isNotGenerated()
+ || persistedInstallationEntry.isUnregistered()) {
resultTaskCompletionSource.trySetException(exception);
return true;
}
diff --git a/firebase-installations/src/main/java/com/google/firebase/installations/GetIdListener.java b/firebase-installations/src/main/java/com/google/firebase/installations/GetIdListener.java
index 38134cc6217..44f534f7da1 100644
--- a/firebase-installations/src/main/java/com/google/firebase/installations/GetIdListener.java
+++ b/firebase-installations/src/main/java/com/google/firebase/installations/GetIdListener.java
@@ -25,9 +25,10 @@ public GetIdListener(TaskCompletionSource Note: Even though this method does not check with the FIS database if the returned FID is
+ * already in use, the probability of collision is extremely and negligibly small!
+ *
+ * @return random FID value
+ */
+ @NonNull
+ public String createRandomFid() {
+ // A valid FID has exactly 22 base64 characters, which is 132 bits, or 16.5 bytes.
+ byte[] uuidBytes = getBytesFromUUID(UUID.randomUUID(), new byte[17]);
+ uuidBytes[16] = uuidBytes[0];
+ uuidBytes[0] = (byte) ((REMOVE_PREFIX_MASK & uuidBytes[0]) | FID_4BIT_PREFIX);
+ return encodeFidBase64UrlSafe(uuidBytes);
+ }
+
+ /**
+ * Converts a given byte-array (assumed to be an FID value) to base64-url-safe encoded
+ * String-representation.
+ *
+ * Note: The returned String has at most 22 characters, the length of FIDs. Thus, it is
+ * recommended to deliver a byte-array containing at least 16.5 bytes.
+ *
+ * @param rawValue FID value to be encoded
+ * @return (22-character or shorter) String containing the base64-encoded value
+ */
+ private static String encodeFidBase64UrlSafe(byte[] rawValue) {
+ return new String(
+ android.util.Base64.encode(
+ rawValue,
+ android.util.Base64.URL_SAFE
+ | android.util.Base64.NO_PADDING
+ | android.util.Base64.NO_WRAP),
+ Charset.defaultCharset())
+ .substring(0, FID_LENGTH);
+ }
+
+ private static byte[] getBytesFromUUID(UUID uuid, byte[] output) {
+ ByteBuffer bb = ByteBuffer.wrap(output);
+ bb.putLong(uuid.getMostSignificantBits());
+ bb.putLong(uuid.getLeastSignificantBits());
+ return bb.array();
+ }
+}
diff --git a/firebase-installations/src/main/java/com/google/firebase/installations/StateListener.java b/firebase-installations/src/main/java/com/google/firebase/installations/StateListener.java
index d9570835adf..8cd08bdb299 100644
--- a/firebase-installations/src/main/java/com/google/firebase/installations/StateListener.java
+++ b/firebase-installations/src/main/java/com/google/firebase/installations/StateListener.java
@@ -21,8 +21,7 @@ interface StateListener {
* Returns {@code true} if the defined {@link PersistedInstallationEntry} state is reached, {@code
* false} otherwise.
*/
- boolean onStateReached(
- PersistedInstallationEntry persistedInstallationEntry, boolean shouldRefreshAuthToken);
+ boolean onStateReached(PersistedInstallationEntry persistedInstallationEntry);
/**
* Returns {@code true} if an exception is thrown while registering a Firebase Installation,
diff --git a/firebase-installations/src/main/java/com/google/firebase/installations/Utils.java b/firebase-installations/src/main/java/com/google/firebase/installations/Utils.java
index 22bf2576ea5..d2f748ce734 100644
--- a/firebase-installations/src/main/java/com/google/firebase/installations/Utils.java
+++ b/firebase-installations/src/main/java/com/google/firebase/installations/Utils.java
@@ -14,99 +14,37 @@
package com.google.firebase.installations;
-import androidx.annotation.NonNull;
-import com.google.android.gms.common.util.Clock;
+import android.text.TextUtils;
import com.google.firebase.installations.local.PersistedInstallationEntry;
-import java.nio.ByteBuffer;
-import java.nio.charset.Charset;
-import java.util.UUID;
+import java.util.Calendar;
import java.util.concurrent.TimeUnit;
/** Util methods used for {@link FirebaseInstallations} */
class Utils {
+ public static final long AUTH_TOKEN_EXPIRATION_BUFFER_IN_SECS = TimeUnit.HOURS.toSeconds(1);
+ private final Calendar calendar;
- private final Clock clock;
- /**
- * 1 Byte with the first 4 header-bits set to the identifying FID prefix 0111 (0x7). Use this
- * constant to create FIDs or check the first byte of FIDs. This prefix is also used in legacy
- * Instance-IDs
- */
- public static final byte FID_4BIT_PREFIX = Byte.parseByte("01110000", 2);
-
- /**
- * Byte mask to remove the 4 header-bits of a given Byte. Use this constant with Java's Binary AND
- * Operator in order to remove the first 4 bits of a Byte and replacing it with the FID prefix.
- */
- public static final byte REMOVE_PREFIX_MASK = Byte.parseByte("00001111", 2);
-
- /** Length of new-format FIDs as introduced in 2019. */
- public static final int FID_LENGTH = 22;
-
- private static final long AUTH_TOKEN_EXPIRATION_BUFFER_IN_SECS = TimeUnit.HOURS.toSeconds(1);
-
- Utils(Clock clock) {
- this.clock = clock;
+ Utils(Calendar calendar) {
+ this.calendar = calendar;
}
/**
* Checks if the FIS Auth token is expired or going to expire in next 1 hour {@link
* #AUTH_TOKEN_EXPIRATION_BUFFER_IN_SECS}.
*/
- public boolean isAuthTokenExpired(PersistedInstallationEntry persistedInstallationEntry) {
- return persistedInstallationEntry.isRegistered()
- && persistedInstallationEntry.getTokenCreationEpochInSecs()
- + persistedInstallationEntry.getExpiresInSecs()
- < currentTimeInSecs() + AUTH_TOKEN_EXPIRATION_BUFFER_IN_SECS;
+ public boolean isAuthTokenExpired(PersistedInstallationEntry entry) {
+ if (TextUtils.isEmpty(entry.getAuthToken())) {
+ return true;
+ }
+ if ((entry.getTokenCreationEpochInSecs() + entry.getExpiresInSecs())
+ < (currentTimeInSecs() + AUTH_TOKEN_EXPIRATION_BUFFER_IN_SECS)) {
+ return true;
+ }
+ return false;
}
/** Returns current time in seconds. */
public long currentTimeInSecs() {
- return TimeUnit.MILLISECONDS.toSeconds(clock.currentTimeMillis());
- }
-
- /**
- * Creates a random FID of valid format without checking if the FID is already in use by any
- * Firebase Installation.
- *
- * Note: Even though this method does not check with the FIS database if the returned FID is
- * already in use, the probability of collision is extremely and negligibly small!
- *
- * @return random FID value
- */
- @NonNull
- public String createRandomFid() {
- // A valid FID has exactly 22 base64 characters, which is 132 bits, or 16.5 bytes.
- byte[] uuidBytes = getBytesFromUUID(UUID.randomUUID(), new byte[17]);
- uuidBytes[16] = uuidBytes[0];
- uuidBytes[0] = (byte) ((REMOVE_PREFIX_MASK & uuidBytes[0]) | FID_4BIT_PREFIX);
- return encodeFidBase64UrlSafe(uuidBytes);
- }
-
- /**
- * Converts a given byte-array (assumed to be an FID value) to base64-url-safe encoded
- * String-representation.
- *
- * Note: The returned String has at most 22 characters, the length of FIDs. Thus, it is
- * recommended to deliver a byte-array containing at least 16.5 bytes.
- *
- * @param rawValue FID value to be encoded
- * @return (22-character or shorter) String containing the base64-encoded value
- */
- private static String encodeFidBase64UrlSafe(byte[] rawValue) {
- return new String(
- android.util.Base64.encode(
- rawValue,
- android.util.Base64.URL_SAFE
- | android.util.Base64.NO_PADDING
- | android.util.Base64.NO_WRAP),
- Charset.defaultCharset())
- .substring(0, FID_LENGTH);
- }
-
- private static byte[] getBytesFromUUID(UUID uuid, byte[] output) {
- ByteBuffer bb = ByteBuffer.wrap(output);
- bb.putLong(uuid.getMostSignificantBits());
- bb.putLong(uuid.getLeastSignificantBits());
- return bb.array();
+ return TimeUnit.MILLISECONDS.toSeconds(calendar.getTimeInMillis());
}
}
diff --git a/firebase-installations/src/main/java/com/google/firebase/installations/local/PersistedInstallation.java b/firebase-installations/src/main/java/com/google/firebase/installations/local/PersistedInstallation.java
index fdee274945d..d3786c4b6e4 100644
--- a/firebase-installations/src/main/java/com/google/firebase/installations/local/PersistedInstallation.java
+++ b/firebase-installations/src/main/java/com/google/firebase/installations/local/PersistedInstallation.java
@@ -14,23 +14,33 @@
package com.google.firebase.installations.local;
-import android.content.Context;
-import android.content.SharedPreferences;
-import androidx.annotation.GuardedBy;
import androidx.annotation.NonNull;
import com.google.firebase.FirebaseApp;
-import java.util.Arrays;
-import java.util.List;
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import org.json.JSONException;
+import org.json.JSONObject;
/**
* A layer that locally persists a few Firebase Installation attributes on top the Firebase
* Installation API.
*/
public class PersistedInstallation {
+ private final File dataFile;
+ @NonNull private final FirebaseApp firebaseApp;
+
// Registration Status of each persisted fid entry
- // NOTE: never change the ordinal of the enum values because the enum values are stored in shared
- // prefs as their ordinal numbers.
+ // NOTE: never change the ordinal of the enum values because the enum values are written to
+ // local storage as their ordinal numbers.
public enum RegistrationStatus {
+ /**
+ * {@link PersistedInstallationEntry} legacy registration status. Next state: UNREGISTERED - A
+ * new FID is created and persisted locally before registering with FIS servers.
+ */
+ ATTEMPT_MIGRATION,
/**
* {@link PersistedInstallationEntry} default registration status. Next state: UNREGISTERED - A
* new FID is created and persisted locally before registering with FIS servers.
@@ -55,8 +65,7 @@ public enum RegistrationStatus {
REGISTER_ERROR,
}
- private static final String SHARED_PREFS_NAME = "PersistedInstallation";
-
+ private static final String SETTINGS_FILE_NAME_PREFIX = "PersistedInstallation";
private static final String FIREBASE_INSTALLATION_ID_KEY = "Fid";
private static final String AUTH_TOKEN_KEY = "AuthToken";
private static final String REFRESH_TOKEN_KEY = "RefreshToken";
@@ -65,94 +74,103 @@ public enum RegistrationStatus {
private static final String PERSISTED_STATUS_KEY = "Status";
private static final String FIS_ERROR_KEY = "FisError";
- private static final List
+ *
*/
@NonNull
public InstallationResponse createFirebaseInstallation(
@NonNull String apiKey, @NonNull String fid, @NonNull String projectID, @NonNull String appId)
- throws FirebaseException {
+ throws IOException {
String resourceName = String.format(CREATE_REQUEST_RESOURCE_NAME_FORMAT, projectID);
- try {
- int retryCount = 0;
- URL url =
- new URL(
- String.format(
- "https://%s/%s/%s?key=%s",
- FIREBASE_INSTALLATIONS_API_DOMAIN,
- FIREBASE_INSTALLATIONS_API_VERSION,
- resourceName,
- apiKey));
- while (retryCount <= MAX_RETRIES) {
- HttpsURLConnection httpsURLConnection = openHttpsURLConnection(url);
- httpsURLConnection.setRequestMethod("POST");
- httpsURLConnection.setDoOutput(true);
-
- GZIPOutputStream gzipOutputStream =
- new GZIPOutputStream(httpsURLConnection.getOutputStream());
- try {
- gzipOutputStream.write(
- buildCreateFirebaseInstallationRequestBody(fid, appId).toString().getBytes("UTF-8"));
- } catch (JSONException e) {
- throw new IllegalStateException(e);
- } finally {
- gzipOutputStream.close();
- }
+ int retryCount = 0;
+ URL url =
+ new URL(
+ String.format(
+ "https://%s/%s/%s?key=%s",
+ FIREBASE_INSTALLATIONS_API_DOMAIN,
+ FIREBASE_INSTALLATIONS_API_VERSION,
+ resourceName,
+ apiKey));
+ while (retryCount <= MAX_RETRIES) {
+ HttpsURLConnection httpsURLConnection = openHttpsURLConnection(url);
+ httpsURLConnection.setRequestMethod("POST");
+ httpsURLConnection.setDoOutput(true);
+
+ GZIPOutputStream gzipOutputStream =
+ new GZIPOutputStream(httpsURLConnection.getOutputStream());
+ try {
+ gzipOutputStream.write(
+ buildCreateFirebaseInstallationRequestBody(fid, appId).toString().getBytes("UTF-8"));
+ } catch (JSONException e) {
+ throw new IllegalStateException(e);
+ } finally {
+ gzipOutputStream.close();
+ }
- int httpResponseCode = httpsURLConnection.getResponseCode();
+ int httpResponseCode = httpsURLConnection.getResponseCode();
- if (httpResponseCode == 200) {
- return readCreateResponse(httpsURLConnection);
- }
- // Usually the FIS server recovers from errors: retry one time before giving up.
- if (httpResponseCode >= 500 && httpResponseCode < 600) {
- retryCount++;
- continue;
- }
+ if (httpResponseCode == 200) {
+ return readCreateResponse(httpsURLConnection);
+ }
- // Unrecoverable server response or unknown error
- throw new FirebaseException(readErrorResponse(httpsURLConnection));
+ if (httpResponseCode == 429 || (httpResponseCode >= 500 && httpResponseCode < 600)) {
+ retryCount++;
+ continue;
}
- // Return empty installation response with SERVER_ERROR response code after max retries
- return InstallationResponse.builder().setResponseCode(ResponseCode.SERVER_ERROR).build();
- } catch (IOException e) {
- throw new FirebaseException(String.format(NETWORK_ERROR_MESSAGE, e.getMessage()));
+
+ // Return empty installation response with BAD_CONFIG response code after max retries
+ return InstallationResponse.builder().setResponseCode(ResponseCode.BAD_CONFIG).build();
}
+
+ throw new IOException();
}
private static JSONObject buildCreateFirebaseInstallationRequestBody(String fid, String appId)
@@ -161,6 +163,7 @@ private static JSONObject buildCreateFirebaseInstallationRequestBody(String fid,
firebaseInstallationData.put("fid", fid);
firebaseInstallationData.put("appId", appId);
firebaseInstallationData.put("authVersion", FIREBASE_INSTALLATION_AUTH_VERSION);
+ firebaseInstallationData.put("sdkVersion", "t.1.1.0");
return firebaseInstallationData;
}
@@ -178,32 +181,39 @@ public void deleteFirebaseInstallation(
@NonNull String fid,
@NonNull String projectID,
@NonNull String refreshToken)
- throws FirebaseException {
+ throws FirebaseException, IOException {
String resourceName = String.format(DELETE_REQUEST_RESOURCE_NAME_FORMAT, projectID, fid);
- try {
- URL url =
- new URL(
- String.format(
- "https://%s/%s/%s?key=%s",
- FIREBASE_INSTALLATIONS_API_DOMAIN,
- FIREBASE_INSTALLATIONS_API_VERSION,
- resourceName,
- apiKey));
-
+ URL url =
+ new URL(
+ String.format(
+ "https://%s/%s/%s?key=%s",
+ FIREBASE_INSTALLATIONS_API_DOMAIN,
+ FIREBASE_INSTALLATIONS_API_VERSION,
+ resourceName,
+ apiKey));
+
+ int retryCount = 0;
+ while (retryCount <= MAX_RETRIES) {
HttpsURLConnection httpsURLConnection = openHttpsURLConnection(url);
httpsURLConnection.setRequestMethod("DELETE");
httpsURLConnection.addRequestProperty("Authorization", "FIS_v2 " + refreshToken);
int httpResponseCode = httpsURLConnection.getResponseCode();
- switch (httpResponseCode) {
- case 200:
- return;
- default:
- throw new FirebaseException(readErrorResponse(httpsURLConnection));
+
+ if (httpResponseCode == 200 || httpResponseCode == 401 || httpResponseCode == 404) {
+ return;
}
- } catch (IOException e) {
- throw new FirebaseException(String.format(NETWORK_ERROR_MESSAGE, e.getMessage()));
+
+ if (httpResponseCode == 429 || (httpResponseCode >= 500 && httpResponseCode < 600)) {
+ retryCount++;
+ continue;
+ }
+
+ throw new FirebaseInstallationsException(
+ "bad config while trying to delete FID", Status.BAD_CONFIG);
}
+
+ throw new IOException();
}
/**
@@ -214,6 +224,14 @@ public void deleteFirebaseInstallation(
* @param fid Firebase Installation Identifier
* @param projectID Project Id
* @param refreshToken a token used to authenticate FIS requests
+ *
+ *
*/
@NonNull
public TokenResult generateAuthToken(
@@ -221,53 +239,41 @@ public TokenResult generateAuthToken(
@NonNull String fid,
@NonNull String projectID,
@NonNull String refreshToken)
- throws FirebaseException {
+ throws IOException {
String resourceName =
String.format(GENERATE_AUTH_TOKEN_REQUEST_RESOURCE_NAME_FORMAT, projectID, fid);
- try {
- int retryCount = 0;
- URL url =
- new URL(
- String.format(
- "https://%s/%s/%s?key=%s",
- FIREBASE_INSTALLATIONS_API_DOMAIN,
- FIREBASE_INSTALLATIONS_API_VERSION,
- resourceName,
- apiKey));
- while (retryCount <= MAX_RETRIES) {
- HttpsURLConnection httpsURLConnection = openHttpsURLConnection(url);
- httpsURLConnection.setRequestMethod("POST");
- httpsURLConnection.addRequestProperty("Authorization", "FIS_v2 " + refreshToken);
-
- int httpResponseCode = httpsURLConnection.getResponseCode();
-
- if (httpResponseCode == 200) {
- return readGenerateAuthTokenResponse(httpsURLConnection);
- }
+ int retryCount = 0;
+ URL url =
+ new URL(
+ String.format(
+ "https://%s/%s/%s?key=%s",
+ FIREBASE_INSTALLATIONS_API_DOMAIN,
+ FIREBASE_INSTALLATIONS_API_VERSION,
+ resourceName,
+ apiKey));
+ while (retryCount <= MAX_RETRIES) {
+ HttpsURLConnection httpsURLConnection = openHttpsURLConnection(url);
+ httpsURLConnection.setRequestMethod("POST");
+ httpsURLConnection.addRequestProperty("Authorization", "FIS_v2 " + refreshToken);
- if (httpResponseCode == 401) {
- return TokenResult.builder()
- .setResponseCode(TokenResult.ResponseCode.REFRESH_TOKEN_ERROR)
- .build();
- }
+ int httpResponseCode = httpsURLConnection.getResponseCode();
- if (httpResponseCode == 404) {
- return TokenResult.builder().setResponseCode(TokenResult.ResponseCode.FID_ERROR).build();
- }
+ if (httpResponseCode == 200) {
+ return readGenerateAuthTokenResponse(httpsURLConnection);
+ }
- // Usually the FIS server recovers from errors: retry one time before giving up.
- if (httpResponseCode >= 500 && httpResponseCode < 600) {
- retryCount++;
- continue;
- }
+ if (httpResponseCode == 401 || httpResponseCode == 404) {
+ return TokenResult.builder().setResponseCode(TokenResult.ResponseCode.AUTH_ERROR).build();
+ }
- // Unrecoverable server response or unknown error
- throw new FirebaseException(readErrorResponse(httpsURLConnection));
+ if (httpResponseCode == 429 || (httpResponseCode >= 500 && httpResponseCode < 600)) {
+ retryCount++;
+ continue;
}
- throw new FirebaseException(INTERNAL_SERVER_ERROR_MESSAGE);
- } catch (IOException e) {
- throw new FirebaseException(String.format(NETWORK_ERROR_MESSAGE, e.getMessage()));
+
+ return TokenResult.builder().setResponseCode(TokenResult.ResponseCode.BAD_CONFIG).build();
}
+ throw new IOException();
}
private HttpsURLConnection openHttpsURLConnection(URL url) throws IOException {
diff --git a/firebase-installations/src/main/java/com/google/firebase/installations/remote/InstallationResponse.java b/firebase-installations/src/main/java/com/google/firebase/installations/remote/InstallationResponse.java
index 0c0463b6f49..213b4d19416 100644
--- a/firebase-installations/src/main/java/com/google/firebase/installations/remote/InstallationResponse.java
+++ b/firebase-installations/src/main/java/com/google/firebase/installations/remote/InstallationResponse.java
@@ -24,8 +24,9 @@ public abstract class InstallationResponse {
public enum ResponseCode {
// Returned on success
OK,
- // An error occurred on the server while processing this request(temporary)
- SERVER_ERROR
+ // The request is invalid. Do not try again without fixing the request. Usually means
+ // a bad or misconfigured API Key or project is being used.
+ BAD_CONFIG,
}
@Nullable
diff --git a/firebase-installations/src/main/java/com/google/firebase/installations/remote/TokenResult.java b/firebase-installations/src/main/java/com/google/firebase/installations/remote/TokenResult.java
index a120e418ff0..1fd7a5d99fd 100644
--- a/firebase-installations/src/main/java/com/google/firebase/installations/remote/TokenResult.java
+++ b/firebase-installations/src/main/java/com/google/firebase/installations/remote/TokenResult.java
@@ -27,14 +27,10 @@ public enum ResponseCode {
OK,
// Auth token cannot be generated for this FID in the request. Because it is not
// registered/found on the FIS server. Recreate a new fid to fetch a valid auth token.
- FID_ERROR,
+ BAD_CONFIG,
// Refresh token in this request in not accepted by the FIS server. Either it has been blocked
// or changed. Recreate a new fid to fetch a valid auth token.
- REFRESH_TOKEN_ERROR,
- }
-
- public boolean isSuccessful() {
- return getResponseCode() == ResponseCode.OK;
+ AUTH_ERROR,
}
/** A new FIS Auth-Token, created for this Firebase Installation. */