Skip to content

Implement GetAppCheckToken API #2757

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 7 commits into from
Jun 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import com.google.firebase.FirebaseApp;
import com.google.firebase.appcheck.AppCheckToken;
import com.google.firebase.appcheck.AppCheckTokenResult;
import com.google.firebase.appcheck.FirebaseAppCheck;
import com.google.firebase.storage.FirebaseStorage;
Expand Down Expand Up @@ -52,7 +53,7 @@ public void tearDown() {
}

@Test
public void exchangeDebugSecretForAppCheckToken() throws Exception {
public void exchangeDebugSecretForAppCheckToken_interopApi() throws Exception {
debugAppCheckTestHelper.withDebugProvider(
() -> {
Task<AppCheckTokenResult> tokenResultTask = firebaseAppCheck.getToken(true);
Expand All @@ -63,6 +64,17 @@ public void exchangeDebugSecretForAppCheckToken() throws Exception {
});
}

@Test
public void exchangeDebugSecretForAppCheckToken_publicApi() throws Exception {
debugAppCheckTestHelper.withDebugProvider(
() -> {
Task<AppCheckToken> tokenTask = firebaseAppCheck.getAppCheckToken(true);
Tasks.await(tokenTask);
AppCheckToken result = tokenTask.getResult();
assertThat(result.getToken()).isNotEmpty();
});
}

@Test
@Ignore("TODO: Enable once we have a project with enforcement enabled in CI.")
public void firebaseStorageListFiles_withValidAppCheckToken_success() throws Exception {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

import android.annotation.SuppressLint;
import androidx.annotation.NonNull;
import com.google.android.gms.tasks.Task;
import com.google.firebase.FirebaseApp;
import com.google.firebase.appcheck.interop.AppCheckTokenListener;
import com.google.firebase.appcheck.interop.InternalAppCheckTokenProvider;
Expand Down Expand Up @@ -69,4 +70,30 @@ public abstract void installAppCheckProviderFactory(

/** Sets the {@code isTokenAutoRefreshEnabled} flag. */
public abstract void setTokenAutoRefreshEnabled(boolean isTokenAutoRefreshEnabled);

/**
* Requests a Firebase App Check token. This method should be used ONLY if you need to authorize
* requests to a non-Firebase backend. Requests to Firebase backends are authorized automatically
* if configured.
*/
@NonNull
public abstract Task<AppCheckToken> getAppCheckToken(boolean forceRefresh);

/**
* Registers an {@link AppCheckListener} to changes in the token state. This method should be used
* ONLY if you need to authorize requests to a non-Firebase backend. Requests to Firebase backends
* are authorized automatically if configured.
*/
public abstract void addAppCheckListener(@NonNull AppCheckListener listener);

/** Unregisters an {@link AppCheckListener} to changes in the token state. */
public abstract void removeAppCheckListener(@NonNull AppCheckListener listener);

public interface AppCheckListener {
/**
* This method gets invoked on the UI thread on changes to the token state. Does not trigger on
* token expiry.
*/
void onAppCheckTokenChanged(@NonNull AppCheckToken token);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ public class DefaultFirebaseAppCheck extends FirebaseAppCheck {
private final Provider<UserAgentPublisher> userAgentPublisherProvider;
private final Provider<HeartBeatInfo> heartBeatInfoProvider;
private final List<AppCheckTokenListener> appCheckTokenListenerList;
private final List<AppCheckListener> appCheckListenerList;
private final StorageHelper storageHelper;
private final TokenRefreshManager tokenRefreshManager;
private final Clock clock;
Expand All @@ -65,6 +66,7 @@ public DefaultFirebaseAppCheck(
this.userAgentPublisherProvider = userAgentPublisherProvider;
this.heartBeatInfoProvider = heartBeatInfoProvider;
this.appCheckTokenListenerList = new ArrayList<>();
this.appCheckListenerList = new ArrayList<>();
this.storageHelper =
new StorageHelper(firebaseApp.getApplicationContext(), firebaseApp.getPersistenceKey());
this.tokenRefreshManager =
Expand Down Expand Up @@ -110,7 +112,8 @@ public void resetAppCheckState() {
public void addAppCheckTokenListener(@NonNull AppCheckTokenListener listener) {
checkNotNull(listener);
appCheckTokenListenerList.add(listener);
tokenRefreshManager.onListenerCountChanged(appCheckTokenListenerList.size());
tokenRefreshManager.onListenerCountChanged(
appCheckTokenListenerList.size() + appCheckListenerList.size());
// If there is a token available, trigger the listener with the current token.
if (hasValidToken()) {
listener.onAppCheckTokenChanged(
Expand All @@ -122,7 +125,28 @@ public void addAppCheckTokenListener(@NonNull AppCheckTokenListener listener) {
public void removeAppCheckTokenListener(@NonNull AppCheckTokenListener listener) {
checkNotNull(listener);
appCheckTokenListenerList.remove(listener);
tokenRefreshManager.onListenerCountChanged(appCheckTokenListenerList.size());
tokenRefreshManager.onListenerCountChanged(
appCheckTokenListenerList.size() + appCheckListenerList.size());
}

@Override
public void addAppCheckListener(@NonNull AppCheckListener listener) {
checkNotNull(listener);
appCheckListenerList.add(listener);
tokenRefreshManager.onListenerCountChanged(
appCheckTokenListenerList.size() + appCheckListenerList.size());
// If there is a token available, trigger the listener with the current token.
if (hasValidToken()) {
listener.onAppCheckTokenChanged(cachedToken);
}
}

@Override
public void removeAppCheckListener(@NonNull AppCheckListener listener) {
checkNotNull(listener);
appCheckListenerList.remove(listener);
tokenRefreshManager.onListenerCountChanged(
appCheckTokenListenerList.size() + appCheckListenerList.size());
}

@NonNull
Expand All @@ -137,33 +161,58 @@ public Task<AppCheckTokenResult> getToken(boolean forceRefresh) {
new FirebaseException("No AppCheckProvider installed.")));
}
// TODO: Cache the in-flight task.
return fetchTokenFromProvider()
.continueWithTask(
new Continuation<AppCheckToken, Task<AppCheckTokenResult>>() {
@Override
public Task<AppCheckTokenResult> then(@NonNull Task<AppCheckToken> task) {
if (task.isSuccessful()) {
return Tasks.forResult(
DefaultAppCheckTokenResult.constructFromAppCheckToken(task.getResult()));
}
// If the token exchange failed, return a dummy token for integrators to attach in
// their headers.
return Tasks.forResult(
DefaultAppCheckTokenResult.constructFromError(
new FirebaseException(
task.getException().getMessage(), task.getException())));
}
});
}

@NonNull
@Override
public Task<AppCheckToken> getAppCheckToken(boolean forceRefresh) {
if (!forceRefresh && hasValidToken()) {
return Tasks.forResult(cachedToken);
}
if (appCheckProvider == null) {
return Tasks.forException(new FirebaseException("No AppCheckProvider installed."));
}
return fetchTokenFromProvider();
}

/** Fetches an {@link AppCheckTokenResult} via the installed {@link AppCheckProvider}. */
Task<AppCheckTokenResult> fetchTokenFromProvider() {
/** Fetches an {@link AppCheckToken} via the installed {@link AppCheckProvider}. */
Task<AppCheckToken> fetchTokenFromProvider() {
return appCheckProvider
.getToken()
.continueWithTask(
new Continuation<AppCheckToken, Task<AppCheckTokenResult>>() {
new Continuation<AppCheckToken, Task<AppCheckToken>>() {
@Override
public Task<AppCheckTokenResult> then(@NonNull Task<AppCheckToken> task) {
public Task<AppCheckToken> then(@NonNull Task<AppCheckToken> task) {
if (task.isSuccessful()) {
AppCheckToken token = task.getResult();
updateStoredToken(token);
for (AppCheckListener listener : appCheckListenerList) {
listener.onAppCheckTokenChanged(token);
}
AppCheckTokenResult tokenResult =
DefaultAppCheckTokenResult.constructFromAppCheckToken(token);
for (AppCheckTokenListener listener : appCheckTokenListenerList) {
listener.onAppCheckTokenChanged(tokenResult);
}
return Tasks.forResult(tokenResult);
}
// If the token exchange failed, return a dummy token for integrators to attach in
// their headers.
return Tasks.forResult(
DefaultAppCheckTokenResult.constructFromError(
new FirebaseException(
task.getException().getMessage(), task.getException())));
return task;
}
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,7 @@

import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import com.google.firebase.appcheck.AppCheckTokenResult;
import com.google.firebase.appcheck.internal.util.Logger;
import com.google.android.gms.tasks.OnFailureListener;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
Expand Down Expand Up @@ -94,22 +91,15 @@ private long getNextRefreshMillis() {
}

private void onRefresh() {
Task<AppCheckTokenResult> task = firebaseAppCheck.fetchTokenFromProvider();
task.addOnCompleteListener(
new OnCompleteListener<AppCheckTokenResult>() {
@Override
public void onComplete(@NonNull Task<AppCheckTokenResult> task) {
if (task.isSuccessful()) {
AppCheckTokenResult tokenResult = task.getResult();
if (tokenResult.getError() != null) {
firebaseAppCheck
.fetchTokenFromProvider()
.addOnFailureListener(
new OnFailureListener() {
@Override
public void onFailure(@NonNull Exception e) {
scheduleRefreshAfterFailure();
}
} else {
// Task was not successful; this should not happen.
Logger.getLogger().e("Unexpected failure while fetching token.");
}
}
});
});
}

/** Cancels the in-flight scheduled refresh. */
Expand Down
Loading