Skip to content

getAuthToken error handling for 401 & 404 response code #961

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 10 commits into from
Nov 21, 2019
31 changes: 28 additions & 3 deletions firebase-installations/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ 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;
}
Expand Down Expand Up @@ -81,13 +82,13 @@ package com.google.firebase.installations.remote {
ctor public FirebaseInstallationServiceClient(@NonNull Context);
method @NonNull public com.google.firebase.installations.remote.InstallationResponse createFirebaseInstallation(@NonNull String, @NonNull String, @NonNull String, @NonNull String);
method @NonNull public void deleteFirebaseInstallation(@NonNull String, @NonNull String, @NonNull String, @NonNull String);
method @NonNull public InstallationTokenResult generateAuthToken(@NonNull String, @NonNull String, @NonNull String, @NonNull String);
method @NonNull public com.google.firebase.installations.remote.TokenResult generateAuthToken(@NonNull String, @NonNull String, @NonNull String, @NonNull String);
}

public abstract class InstallationResponse {
ctor public InstallationResponse();
method @NonNull public static com.google.firebase.installations.remote.InstallationResponse.Builder builder();
method @Nullable public abstract InstallationTokenResult getAuthToken();
method @Nullable public abstract com.google.firebase.installations.remote.TokenResult getAuthToken();
method @Nullable public abstract String getFid();
method @Nullable public abstract String getRefreshToken();
method @Nullable public abstract com.google.firebase.installations.remote.InstallationResponse.ResponseCode getResponseCode();
Expand All @@ -98,7 +99,7 @@ package com.google.firebase.installations.remote {
public abstract static class InstallationResponse.Builder {
ctor public InstallationResponse.Builder();
method @NonNull public abstract com.google.firebase.installations.remote.InstallationResponse build();
method @NonNull public abstract com.google.firebase.installations.remote.InstallationResponse.Builder setAuthToken(@NonNull InstallationTokenResult);
method @NonNull public abstract com.google.firebase.installations.remote.InstallationResponse.Builder setAuthToken(@NonNull com.google.firebase.installations.remote.TokenResult);
method @NonNull public abstract com.google.firebase.installations.remote.InstallationResponse.Builder setFid(@NonNull String);
method @NonNull public abstract com.google.firebase.installations.remote.InstallationResponse.Builder setRefreshToken(@NonNull String);
method @NonNull public abstract com.google.firebase.installations.remote.InstallationResponse.Builder setResponseCode(@NonNull com.google.firebase.installations.remote.InstallationResponse.ResponseCode);
Expand All @@ -110,5 +111,29 @@ package com.google.firebase.installations.remote {
enum_constant public static final com.google.firebase.installations.remote.InstallationResponse.ResponseCode SERVER_ERROR;
}

public abstract class TokenResult {
ctor public TokenResult();
method @NonNull public static com.google.firebase.installations.remote.TokenResult.Builder builder();
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();
}

public abstract static class TokenResult.Builder {
ctor public TokenResult.Builder();
method @NonNull public abstract com.google.firebase.installations.remote.TokenResult build();
method @NonNull public abstract com.google.firebase.installations.remote.TokenResult.Builder setResponseCode(@NonNull com.google.firebase.installations.remote.TokenResult.ResponseCode);
method @NonNull public abstract com.google.firebase.installations.remote.TokenResult.Builder setToken(@NonNull String);
method @NonNull public abstract com.google.firebase.installations.remote.TokenResult.Builder setTokenExpirationTimestamp(long);
}

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 OK;
enum_constant public static final com.google.firebase.installations.remote.TokenResult.ResponseCode REFRESH_TOKEN_ERROR;
}

}

Original file line number Diff line number Diff line change
Expand Up @@ -29,12 +29,12 @@
import static com.google.firebase.installations.FisAndroidTestConstants.TEST_FID_1;
import static com.google.firebase.installations.FisAndroidTestConstants.TEST_INSTALLATION_RESPONSE;
import static com.google.firebase.installations.FisAndroidTestConstants.TEST_INSTALLATION_RESPONSE_WITH_IID;
import static com.google.firebase.installations.FisAndroidTestConstants.TEST_INSTALLATION_TOKEN_RESULT;
import static com.google.firebase.installations.FisAndroidTestConstants.TEST_INSTANCE_ID_1;
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.fail;
import static org.mockito.ArgumentMatchers.any;
Expand All @@ -59,6 +59,7 @@
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.TokenResult;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
Expand Down Expand Up @@ -174,7 +175,7 @@ public void setUp() throws FirebaseException {
// Mocks successful auth token generation
when(backendClientReturnsOk.generateAuthToken(
anyString(), anyString(), anyString(), anyString()))
.thenReturn(TEST_INSTALLATION_TOKEN_RESULT);
.thenReturn(TEST_TOKEN_RESULT);

when(persistedInstallationReturnsError.insertOrUpdatePersistedInstallationEntry(any()))
.thenReturn(false);
Expand Down Expand Up @@ -215,7 +216,7 @@ private FirebaseInstallations getFirebaseInstallations() {

@Test
public void testGetId_PersistedInstallationOk_BackendOk() throws Exception {
when(mockUtils.isAuthTokenExpired(REGISTERED_IID_ENTRY)).thenReturn(false);
when(mockUtils.isAuthTokenExpired(REGISTERED_IID_ENTRY)).thenReturn(/*isValid*/ false);
FirebaseInstallations firebaseInstallations = getFirebaseInstallations();

// No exception, means success.
Expand All @@ -239,7 +240,7 @@ public void testGetId_PersistedInstallationOk_BackendOk() throws Exception {
@Test
public void testGetId_migrateIid_successful() throws Exception {
when(mockIidStore.readIid()).thenReturn(TEST_INSTANCE_ID_1);
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(false);
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(/*isValid*/ false);
when(backendClientReturnsOk.createFirebaseInstallation(
anyString(), anyString(), anyString(), anyString()))
.thenReturn(TEST_INSTALLATION_RESPONSE_WITH_IID);
Expand All @@ -264,7 +265,7 @@ public void testGetId_migrateIid_successful() throws Exception {

@Test
public void testGetId_multipleCalls_sameFIDReturned() throws Exception {
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(false);
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(/*isValid*/ false);
when(backendClientReturnsOk.createFirebaseInstallation(
anyString(), anyString(), anyString(), anyString()))
.thenReturn(TEST_INSTALLATION_RESPONSE);
Expand Down Expand Up @@ -295,7 +296,7 @@ public void testGetId_multipleCalls_sameFIDReturned() throws Exception {
public void testGetId_invalidFid_storesValidFidFromResponse() throws Exception {
// Update local storage with installation entry that has invalid fid.
persistedInstallation.insertOrUpdatePersistedInstallationEntry(INVALID_INSTALLATION_ENTRY);
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(false);
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(/*isValid*/ false);
FirebaseInstallations firebaseInstallations = getFirebaseInstallations();

// No exception, means success.
Expand Down Expand Up @@ -400,7 +401,7 @@ public void testGetId_fidRegistrationUncheckedException_statusUpdated() throws E
invocation -> {
throw new InterruptedException();
});
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(false);
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(/*isValid*/ false);

FirebaseInstallations firebaseInstallations =
new FirebaseInstallations(
Expand Down Expand Up @@ -432,7 +433,7 @@ public void testGetId_expiredAuthTokenUncheckedException_statusUpdated() throws
invocation -> {
throw new InterruptedException();
});
when(mockUtils.isAuthTokenExpired(EXPIRED_AUTH_TOKEN_ENTRY)).thenReturn(true);
when(mockUtils.isAuthTokenExpired(EXPIRED_AUTH_TOKEN_ENTRY)).thenReturn(/*isExpired*/ true);

FirebaseInstallations firebaseInstallations =
new FirebaseInstallations(
Expand All @@ -459,7 +460,7 @@ public void testGetId_expiredAuthTokenUncheckedException_statusUpdated() throws
public void testGetId_expiredAuthToken_refreshesAuthToken() throws Exception {
// Update local storage with installation entry that has auth token expired.
persistedInstallation.insertOrUpdatePersistedInstallationEntry(EXPIRED_AUTH_TOKEN_ENTRY);
when(mockUtils.isAuthTokenExpired(EXPIRED_AUTH_TOKEN_ENTRY)).thenReturn(true);
when(mockUtils.isAuthTokenExpired(EXPIRED_AUTH_TOKEN_ENTRY)).thenReturn(/*isExpired*/ true);

FirebaseInstallations firebaseInstallations = getFirebaseInstallations();

Expand All @@ -485,7 +486,7 @@ public void testGetId_expiredAuthToken_refreshesAuthToken() throws Exception {

@Test
public void testGetAuthToken_fidDoesNotExist_successful() throws Exception {
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(false);
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(/*isValid*/ false);
FirebaseInstallations firebaseInstallations = getFirebaseInstallations();

Tasks.await(firebaseInstallations.getAuthToken(FirebaseInstallationsApi.DO_NOT_FORCE_REFRESH));
Expand All @@ -497,7 +498,7 @@ public void testGetAuthToken_fidDoesNotExist_successful() throws Exception {

@Test
public void testGetAuthToken_PersistedInstallationError_failure() throws Exception {
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(false);
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(/*isValid*/ false);
FirebaseInstallations firebaseInstallations =
new FirebaseInstallations(
executor,
Expand Down Expand Up @@ -527,7 +528,7 @@ public void testGetAuthToken_PersistedInstallationError_failure() throws Excepti
public void testGetAuthToken_fidExists_successful() throws Exception {
when(mockPersistedInstallation.readPersistedInstallationEntryValue())
.thenReturn(REGISTERED_INSTALLATION_ENTRY);
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(false);
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(/*isValid*/ false);

FirebaseInstallations firebaseInstallations =
new FirebaseInstallations(
Expand All @@ -552,8 +553,8 @@ public void testGetAuthToken_fidExists_successful() throws Exception {
@Test
public void testGetAuthToken_expiredAuthToken_fetchedNewTokenFromFIS() throws Exception {
persistedInstallation.insertOrUpdatePersistedInstallationEntry(EXPIRED_AUTH_TOKEN_ENTRY);
when(mockUtils.isAuthTokenExpired(EXPIRED_AUTH_TOKEN_ENTRY)).thenReturn(true);
when(mockUtils.isAuthTokenExpired(UPDATED_AUTH_TOKEN_ENTRY)).thenReturn(false);
when(mockUtils.isAuthTokenExpired(EXPIRED_AUTH_TOKEN_ENTRY)).thenReturn(/*isExpired*/ true);
when(mockUtils.isAuthTokenExpired(UPDATED_AUTH_TOKEN_ENTRY)).thenReturn(/*isValid*/ false);

FirebaseInstallations firebaseInstallations = getFirebaseInstallations();

Expand All @@ -573,7 +574,7 @@ public void testGetAuthToken_unregisteredFid_fetchedNewTokenFromFIS() throws Exc
// Update local storage with a unregistered installation entry to validate that getAuthToken
// calls getId to ensure FID registration and returns a valid auth token.
persistedInstallation.insertOrUpdatePersistedInstallationEntry(UNREGISTERED_INSTALLATION_ENTRY);
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(false);
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(/*isValid*/ false);

FirebaseInstallations firebaseInstallations = getFirebaseInstallations();

Expand All @@ -588,14 +589,50 @@ public void testGetAuthToken_unregisteredFid_fetchedNewTokenFromFIS() throws Exc
.createFirebaseInstallation(TEST_API_KEY, TEST_FID_1, TEST_PROJECT_ID, TEST_APP_ID_1);
}

@Test
public void testGetAuthToken_fidError_persistedInstallationCleared() throws Exception {
// Update local storage with an expired installation entry to ensure that generate auth token
// is called.
persistedInstallation.insertOrUpdatePersistedInstallationEntry(EXPIRED_AUTH_TOKEN_ENTRY);
// Mocks error during auth token generation
when(backendClientReturnsOk.generateAuthToken(
anyString(), anyString(), anyString(), anyString()))
.thenReturn(
TokenResult.builder().setResponseCode(TokenResult.ResponseCode.FID_ERROR).build());
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY))
.thenReturn(/* isExpired*/ true, /*isValid*/ false);

FirebaseInstallations firebaseInstallations = getFirebaseInstallations();

// Expect exception
try {
Tasks.await(firebaseInstallations.getAuthToken(FirebaseInstallationsApi.FORCE_REFRESH));
fail("getAuthToken() failed due to Server Error.");
} catch (ExecutionException expected) {
assertWithMessage("Exception class doesn't match")
.that(expected)
.hasCauseThat()
.isInstanceOf(FirebaseInstallationsException.class);
assertWithMessage("Exception status doesn't match")
.that(((FirebaseInstallationsException) expected.getCause()).getStatus())
.isEqualTo(FirebaseInstallationsException.Status.AUTHENTICATION_ERROR);
}

verify(backendClientReturnsOk, times(1))
.generateAuthToken(TEST_API_KEY, TEST_FID_1, TEST_PROJECT_ID, TEST_REFRESH_TOKEN);
PersistedInstallationEntry updatedInstallationEntry =
persistedInstallation.readPersistedInstallationEntryValue();
assertThat(updatedInstallationEntry).hasRegistrationStatus(RegistrationStatus.NOT_GENERATED);
}

@Test
public void testGetAuthToken_serverError_failure() throws Exception {
when(mockPersistedInstallation.readPersistedInstallationEntryValue())
.thenReturn(REGISTERED_INSTALLATION_ENTRY);
when(backendClientReturnsError.generateAuthToken(
anyString(), anyString(), anyString(), anyString()))
.thenThrow(new FirebaseException("Server Error"));
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(false);
when(mockUtils.isAuthTokenExpired(REGISTERED_INSTALLATION_ENTRY)).thenReturn(/*isValid*/ false);

FirebaseInstallations firebaseInstallations =
new FirebaseInstallations(
Expand Down Expand Up @@ -628,8 +665,8 @@ public void testGetAuthToken_multipleCallsDoNotForceRefresh_fetchedNewTokenOnce(
// triggered simultaneously. Task2 waits for Task1 to complete. On task1 completion, task2 reads
// the UPDATED_AUTH_TOKEN_FID_ENTRY generated by Task1.
persistedInstallation.insertOrUpdatePersistedInstallationEntry(EXPIRED_AUTH_TOKEN_ENTRY);
when(mockUtils.isAuthTokenExpired(EXPIRED_AUTH_TOKEN_ENTRY)).thenReturn(true);
when(mockUtils.isAuthTokenExpired(UPDATED_AUTH_TOKEN_ENTRY)).thenReturn(false);
when(mockUtils.isAuthTokenExpired(EXPIRED_AUTH_TOKEN_ENTRY)).thenReturn(/*isExpired*/ true);
when(mockUtils.isAuthTokenExpired(UPDATED_AUTH_TOKEN_ENTRY)).thenReturn(/*isValid*/ false);

FirebaseInstallations firebaseInstallations = getFirebaseInstallations();

Expand Down Expand Up @@ -662,23 +699,23 @@ public void testGetAuthToken_multipleCallsForceRefresh_fetchedNewTokenTwice() th
AdditionalAnswers.answersWithDelay(
500,
(unused) ->
InstallationTokenResult.builder()
TokenResult.builder()
.setToken(TEST_AUTH_TOKEN_3)
.setTokenExpirationTimestamp(TEST_TOKEN_EXPIRATION_TIMESTAMP)
.setTokenCreationTimestamp(TEST_CREATION_TIMESTAMP_1)
.setResponseCode(TokenResult.ResponseCode.OK)
.build()))
.doAnswer(
AdditionalAnswers.answersWithDelay(
500,
(unused) ->
InstallationTokenResult.builder()
TokenResult.builder()
.setToken(TEST_AUTH_TOKEN_4)
.setTokenExpirationTimestamp(TEST_TOKEN_EXPIRATION_TIMESTAMP)
.setTokenCreationTimestamp(TEST_CREATION_TIMESTAMP_1)
.setResponseCode(TokenResult.ResponseCode.OK)
.build()))
.when(backendClientReturnsOk)
.generateAuthToken(anyString(), anyString(), anyString(), anyString());
when(mockUtils.isAuthTokenExpired(any())).thenReturn(false);
when(mockUtils.isAuthTokenExpired(any())).thenReturn(/*isValid*/ false);

FirebaseInstallations firebaseInstallations = getFirebaseInstallations();

Expand Down
Loading