-
Notifications
You must be signed in to change notification settings - Fork 615
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
Changes from 1 commit
fc000d2
ce83622
ca75ec8
c1ee9b6
d8492fe
44a28bf
bcf34d3
dcd6c28
f7d9ebf
d72bd7d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
|
@@ -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; | ||
|
@@ -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); | ||
|
@@ -588,6 +589,41 @@ 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(true, false); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can you please add comments here to make it easier to understand what these values are: then Return( true /* isExpired /, false / isValid */) |
||
|
||
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.CLIENT_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()) | ||
|
@@ -662,19 +698,19 @@ 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()); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -31,6 +31,7 @@ | |
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; | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
import java.util.ArrayList; | ||
import java.util.Iterator; | ||
import java.util.List; | ||
|
@@ -270,6 +271,19 @@ private final void doRegistration() { | |
} | ||
} | ||
|
||
// If persisted fid is in AUTH_TOKEN_ERROR state, clear the stored fid and update the | ||
// listener with an exception. | ||
if (persistedInstallationEntry.isAuthTokenErrored()) { | ||
persistedInstallation.clear(); | ||
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.", | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
FirebaseInstallationsException.Status.CLIENT_ERROR)); | ||
return; | ||
} | ||
|
||
triggerOnStateReached(persistedInstallationEntry); | ||
} catch (Exception e) { | ||
PersistedInstallationEntry persistedInstallationEntry = | ||
|
@@ -345,28 +359,37 @@ private Void registerAndSaveFid(PersistedInstallationEntry persistedInstallation | |
} | ||
|
||
/** Calls the FIS servers to generate an auth token for this Firebase installation. */ | ||
private InstallationTokenResult fetchAuthTokenFromServer( | ||
PersistedInstallationEntry persistedInstallationEntry) throws FirebaseInstallationsException { | ||
private void fetchAuthTokenFromServer(PersistedInstallationEntry persistedInstallationEntry) | ||
throws FirebaseInstallationsException { | ||
try { | ||
long creationTime = utils.currentTimeInSecs(); | ||
InstallationTokenResult tokenResult = | ||
TokenResult tokenResult = | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
serviceClient.generateAuthToken( | ||
/*apiKey= */ firebaseApp.getOptions().getApiKey(), | ||
/*fid= */ persistedInstallationEntry.getFirebaseInstallationId(), | ||
/*projectID= */ firebaseApp.getOptions().getProjectId(), | ||
/*refreshToken= */ persistedInstallationEntry.getRefreshToken()); | ||
|
||
persistedInstallation.insertOrUpdatePersistedInstallationEntry( | ||
PersistedInstallationEntry.builder() | ||
.setFirebaseInstallationId(persistedInstallationEntry.getFirebaseInstallationId()) | ||
.setRegistrationStatus(RegistrationStatus.REGISTERED) | ||
.setAuthToken(tokenResult.getToken()) | ||
.setRefreshToken(persistedInstallationEntry.getRefreshToken()) | ||
.setExpiresInSecs(tokenResult.getTokenExpirationTimestamp()) | ||
.setTokenCreationEpochInSecs(creationTime) | ||
.build()); | ||
|
||
return tokenResult; | ||
if (tokenResult.getResponseCode() == TokenResult.ResponseCode.FID_ERROR | ||
|| tokenResult.getResponseCode() == TokenResult.ResponseCode.REFRESH_TOKEN_ERROR) { | ||
persistedInstallation.insertOrUpdatePersistedInstallationEntry( | ||
persistedInstallationEntry | ||
.toBuilder() | ||
.setRegistrationStatus(RegistrationStatus.AUTHENTICATION_TOKEN_ERROR) | ||
.build()); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In the auth error case, if anyways you will clear the cache, why do you update it to AUTHENTICATION_TOKEN_ERROR and then clear it in doRegistration()? An alternative is to clear it here, and like your old code, still return TokenResult from this method. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense. Changed it. However, I retained the return type as void because it is unused by the caller of the method. |
||
} | ||
|
||
if (tokenResult.getResponseCode() == TokenResult.ResponseCode.OK) { | ||
persistedInstallation.insertOrUpdatePersistedInstallationEntry( | ||
persistedInstallationEntry | ||
.toBuilder() | ||
.setRegistrationStatus(RegistrationStatus.REGISTERED) | ||
.setAuthToken(tokenResult.getToken()) | ||
.setExpiresInSecs(tokenResult.getTokenExpirationTimestamp()) | ||
.setTokenCreationEpochInSecs(creationTime) | ||
.build()); | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Pls use for completeness
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
} catch (FirebaseException exception) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Which line does throw the FirebaseException? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Added a TODO to improve Exception handling in general. |
||
throw new FirebaseInstallationsException( | ||
"Failed to generate auth token for a Firebase Installation.", | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -200,7 +200,7 @@ public void deleteFirebaseInstallation( | |
* @param refreshToken a token used to authenticate FIS requests | ||
*/ | ||
@NonNull | ||
public InstallationTokenResult generateAuthToken( | ||
public TokenResult generateAuthToken( | ||
@NonNull String apiKey, | ||
@NonNull String fid, | ||
@NonNull String projectID, | ||
|
@@ -228,6 +228,17 @@ public InstallationTokenResult generateAuthToken( | |
if (httpResponseCode == 200) { | ||
return readGenerateAuthTokenResponse(httpsURLConnection); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there a reason to not just return the response? |
||
} | ||
|
||
if (httpResponseCode == 401) { | ||
return TokenResult.builder() | ||
.setResponseCode(TokenResult.ResponseCode.REFRESH_TOKEN_ERROR) | ||
.build(); | ||
} | ||
Comment on lines
+231
to
+235
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So, the request was not authenticated, what is happening next? |
||
|
||
if (httpResponseCode == 404) { | ||
return TokenResult.builder().setResponseCode(TokenResult.ResponseCode.FID_ERROR).build(); | ||
} | ||
Comment on lines
+237
to
+239
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. question: why does this not trigger a deletion of the PersistedInstallationEntry? |
||
|
||
// Usually the FIS server recovers from errors: retry one time before giving up. | ||
if (httpResponseCode >= 500 && httpResponseCode < 600) { | ||
retryCount++; | ||
|
@@ -295,10 +306,9 @@ private InstallationResponse readCreateResponse(HttpsURLConnection conn) throws | |
} | ||
|
||
// Read the response from the generateAuthToken FirebaseInstallation API. | ||
private InstallationTokenResult readGenerateAuthTokenResponse(HttpsURLConnection conn) | ||
throws IOException { | ||
private TokenResult readGenerateAuthTokenResponse(HttpsURLConnection conn) throws IOException { | ||
JsonReader reader = new JsonReader(new InputStreamReader(conn.getInputStream(), UTF_8)); | ||
InstallationTokenResult.Builder builder = InstallationTokenResult.builder(); | ||
TokenResult.Builder builder = TokenResult.builder(); | ||
reader.beginObject(); | ||
while (reader.hasNext()) { | ||
String name = reader.nextName(); | ||
|
@@ -312,7 +322,7 @@ private InstallationTokenResult readGenerateAuthTokenResponse(HttpsURLConnection | |
} | ||
reader.endObject(); | ||
|
||
return builder.build(); | ||
return builder.setResponseCode(TokenResult.ResponseCode.OK).build(); | ||
} | ||
|
||
// Read the error message from the response. | ||
|
Uh oh!
There was an error while loading. Please reload this page.