-
Notifications
You must be signed in to change notification settings - Fork 617
FIS getAuthToken implementation. #769
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 7 commits
c54292e
b4b6ba8
b8bfc1a
7a2a112
42c20f6
46d936c
48fd7fe
82fb69e
fe04e15
e91cb7e
8569952
b74b163
58a30e3
96e30c2
9e3e34d
824cb63
90673c3
f5b4f8c
3e29941
75f2581
91e49bc
4578880
7d19b50
fe04884
f1d1d37
ec65040
396d273
81b4aeb
3932d3b
8d4d811
3f44b77
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,6 +29,7 @@ | |
import com.google.firebase.installations.remote.FirebaseInstallationServiceClient; | ||
import com.google.firebase.installations.remote.FirebaseInstallationServiceException; | ||
import com.google.firebase.installations.remote.InstallationResponse; | ||
import com.google.firebase.installations.remote.InstallationTokenResult; | ||
import java.util.concurrent.Executor; | ||
import java.util.concurrent.SynchronousQueue; | ||
import java.util.concurrent.ThreadPoolExecutor; | ||
|
@@ -54,6 +55,8 @@ public class FirebaseInstallations implements FirebaseInstallationsApi { | |
private final Clock clock; | ||
private final Utils utils; | ||
|
||
private static final long TOKEN_EXPIRATION_BUFFER = 3600L; | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
/** package private constructor. */ | ||
FirebaseInstallations(FirebaseApp firebaseApp) { | ||
this( | ||
|
@@ -115,11 +118,17 @@ public Task<String> getId() { | |
.onSuccessTask(this::registerFidIfNecessary); | ||
} | ||
|
||
/** Returns a auth token(public key) of this Firebase app installation. */ | ||
/** | ||
* Returns a valid authentication token for the Firebase installation. Generates a new token if | ||
* one doesn't exist, is expired or about to expire. | ||
* | ||
* <p>Should only be called if the Firebase Installation is registered. | ||
*/ | ||
@NonNull | ||
@Override | ||
public Task<InstallationTokenResult> getAuthToken(boolean forceRefresh) { | ||
return Tasks.forResult(InstallationTokenResult.builder().build()); | ||
public Task<String> getAuthToken(final boolean forceRefresh) { | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return Tasks.call(executor, this::getId) | ||
.continueWith(call(() -> refreshAuthTokenIfNecessary(forceRefresh))); | ||
} | ||
|
||
/** | ||
|
@@ -169,6 +178,16 @@ private static <F, T> Continuation<F, T> orElse(@NonNull Supplier<T> supplier) { | |
}; | ||
} | ||
|
||
@NonNull | ||
private static <F, T> Continuation<F, T> call(@NonNull Supplier<T> supplier) { | ||
return t -> { | ||
if (!t.isComplete()) { | ||
Tasks.await(t); | ||
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. @ciarand Instrumented tests fail sometimes because of this line. Its not strictly waiting for the prev Task (getId) to complete. Ends up calling refreshAuthTokenIfNecessary with empty PersistedFidEntry ( pointed you to that code). Can you please help me to understand what am I missing? 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. Test failure is alternating. 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. I don't think this is correct. You're calling Instead of this: return Tasks.call(executor, this::getId)
.continueWith(call(() -> refreshAuthTokenIfNecessary(forceRefresh))); I think you mean to do something like this: return getId().continueWith((idTask) -> refreshAuthTokenIfNecessary(idTask.getResult(), forceRefresh)); NOTE: The call to You're also not passing an explicit executor into the .continueWith call though, so the refreshAuthTokenIfNecessary method is getting run on the main thread (!). Given you're not allowed to call Tasks.await on the main thread, that seems like a bug. 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. Very good catch. You are right. That was the issue :) Fixed it. About the 'Tasks.await' it wasn't a bug until I called |
||
} | ||
return supplier.get(); | ||
}; | ||
} | ||
|
||
private PersistedFidEntry createAndPersistNewFid() throws FirebaseInstallationsException { | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
String fid = utils.createRandomFid(); | ||
persistFid(fid); | ||
|
@@ -214,7 +233,7 @@ private void updatePersistedFidWithPendingStatus(String fid) { | |
private Void registerAndSaveFid(PersistedFidEntry persistedFidEntry) | ||
throws FirebaseInstallationsException { | ||
try { | ||
long creationTime = TimeUnit.MILLISECONDS.toSeconds(clock.currentTimeMillis()); | ||
long creationTime = currentTime(); | ||
|
||
InstallationResponse installationResponse = | ||
serviceClient.createFirebaseInstallation( | ||
|
@@ -244,6 +263,79 @@ private Void registerAndSaveFid(PersistedFidEntry persistedFidEntry) | |
} | ||
return null; | ||
} | ||
|
||
private String refreshAuthTokenIfNecessary(boolean forceRefresh) | ||
throws FirebaseInstallationsException { | ||
|
||
PersistedFidEntry persistedFidEntry = persistedFid.readPersistedFidEntryValue(); | ||
|
||
if (persistedFidEntry == null) { | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
throw new FirebaseInstallationsException( | ||
"Failed to create Firebase Installation.", | ||
FirebaseInstallationsException.Status.SDK_INTERNAL_ERROR); | ||
} | ||
|
||
return forceRefresh | ||
? fetchAuthTokenFromServer(persistedFidEntry) | ||
: getPersistedAuthToken(persistedFidEntry); | ||
} | ||
|
||
private String getPersistedAuthToken(PersistedFidEntry persistedFidEntry) | ||
throws FirebaseInstallationsException { | ||
if (!isPersistedFidRegistered(persistedFidEntry)) { | ||
throw new FirebaseInstallationsException( | ||
"Firebase Installation is not registered.", | ||
FirebaseInstallationsException.Status.SDK_INTERNAL_ERROR); | ||
} | ||
|
||
return isAuthTokenExpired(persistedFidEntry) | ||
? fetchAuthTokenFromServer(persistedFidEntry) | ||
: persistedFidEntry.getAuthToken(); | ||
} | ||
|
||
private boolean isPersistedFidRegistered(PersistedFidEntry persistedFidEntry) { | ||
return persistedFidEntry != null | ||
&& persistedFidEntry.getRegistrationStatus() == RegistrationStatus.REGISTERED; | ||
} | ||
|
||
/** Calls the FIS servers to generate an auth token for this Firebase installation. */ | ||
private String fetchAuthTokenFromServer(PersistedFidEntry persistedFidEntry) | ||
throws FirebaseInstallationsException { | ||
try { | ||
long creationTime = currentTime(); | ||
InstallationTokenResult tokenResult = | ||
serviceClient.generateAuthToken( | ||
/*apiKey= */ firebaseApp.getOptions().getApiKey(), | ||
/*fid= */ persistedFidEntry.getFirebaseInstallationId(), | ||
/*projectID= */ firebaseApp.getOptions().getProjectId(), | ||
/*refreshToken= */ persistedFidEntry.getRefreshToken()); | ||
|
||
persistedFid.insertOrUpdatePersistedFidEntry( | ||
PersistedFidEntry.builder() | ||
.setFirebaseInstallationId(persistedFidEntry.getFirebaseInstallationId()) | ||
.setRegistrationStatus(RegistrationStatus.REGISTERED) | ||
.setAuthToken(tokenResult.getToken()) | ||
.setRefreshToken(persistedFidEntry.getRefreshToken()) | ||
.setExpiresInSecs(tokenResult.getTokenExpirationTimestampMillis()) | ||
.setTokenCreationEpochInSecs(creationTime) | ||
.build()); | ||
|
||
return tokenResult.getToken(); | ||
} catch (FirebaseInstallationServiceException exception) { | ||
throw new FirebaseInstallationsException( | ||
"Failed to generate auth token for a Firebase Installation.", | ||
FirebaseInstallationsException.Status.SDK_INTERNAL_ERROR); | ||
} | ||
} | ||
|
||
private boolean isAuthTokenExpired(PersistedFidEntry persistedFidEntry) { | ||
return (persistedFidEntry.getTokenCreationEpochInSecs() + persistedFidEntry.getExpiresInSecs() | ||
< currentTime() + TOKEN_EXPIRATION_BUFFER); | ||
} | ||
|
||
private long currentTime() { | ||
ankitaj224 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
return TimeUnit.MILLISECONDS.toSeconds(clock.currentTimeMillis()); | ||
} | ||
} | ||
|
||
interface Supplier<T> { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
// 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.remote; | ||
|
||
import androidx.annotation.NonNull; | ||
import com.google.auto.value.AutoValue; | ||
|
||
/** This class represents a set of values describing a FIS Auth Token Result. */ | ||
@AutoValue | ||
public abstract class InstallationTokenResult { | ||
|
||
/** A new FIS Auth-Token, created for this Firebase Installation. */ | ||
@NonNull | ||
public abstract String getToken(); | ||
/** | ||
* The amount of time, in milliseconds, before the auth-token expires for this Firebase | ||
* Installation. | ||
*/ | ||
@NonNull | ||
public abstract long getTokenExpirationTimestampMillis(); | ||
|
||
@NonNull | ||
public abstract Builder toBuilder(); | ||
|
||
/** Returns a default Builder object to create an InstallationResponse object */ | ||
@NonNull | ||
public static InstallationTokenResult.Builder builder() { | ||
return new AutoValue_InstallationTokenResult.Builder(); | ||
} | ||
|
||
@AutoValue.Builder | ||
public abstract static class Builder { | ||
@NonNull | ||
public abstract Builder setToken(@NonNull String value); | ||
|
||
@NonNull | ||
public abstract Builder setTokenExpirationTimestampMillis(@NonNull long value); | ||
|
||
@NonNull | ||
public abstract InstallationTokenResult build(); | ||
} | ||
} |
Uh oh!
There was an error while loading. Please reload this page.