Skip to content

Commit 2d3b7c2

Browse files
authored
Get the IID token before fetching Remote Config. (#1100)
Remote Config requires an IID token to evaluate certain conditions, but does not currently wait for the token from `FirebaseInstanceId#getToken()` before fetching. This returns the default condition value in some cases (ex. percentage conditions) rather than the evaluated result for an app instance. This change replaces the use of the deprecated `#getToken()` with `#getInstanceId()`, which returns an async Task we can wait to complete before fetching, without otherwise blocking.
1 parent 4236f21 commit 2d3b7c2

File tree

11 files changed

+143
-9
lines changed

11 files changed

+143
-9
lines changed

firebase-config/ktx/ktx.gradle

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ dependencies {
4848
implementation project(':firebase-common:ktx')
4949
implementation project(':firebase-config')
5050
implementation project (':firebase-abt')
51+
implementation ('com.google.firebase:firebase-iid:20.0.1') {
52+
exclude group: "com.google.firebase", module: "firebase-common"
53+
}
5154
implementation 'androidx.annotation:annotation:1.1.0'
5255

5356
testImplementation "org.robolectric:robolectric:$robolectricVersion"

firebase-config/ktx/src/test/kotlin/com/google/firebase/remoteconfig/TestConstructorUtil.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,14 @@ import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler
2222
import com.google.firebase.remoteconfig.internal.ConfigMetadataClient
2323
import java.util.concurrent.Executor
2424
import com.google.firebase.abt.FirebaseABTesting
25+
import com.google.firebase.iid.FirebaseInstanceId
2526

2627
// This method is a workaround for testing. It enable us to create a FirebaseRemoteConfig object
2728
// with mocks using the package-private constructor.
2829
fun createRemoteConfig(
2930
context: Context?,
3031
firebaseApp: FirebaseApp,
32+
firebaseInstanceId: FirebaseInstanceId,
3133
firebaseAbt: FirebaseABTesting?,
3234
executor: Executor,
3335
fetchedConfigsCache: ConfigCacheClient,
@@ -40,6 +42,7 @@ fun createRemoteConfig(
4042
return FirebaseRemoteConfig(
4143
context,
4244
firebaseApp,
45+
firebaseInstanceId,
4346
firebaseAbt,
4447
executor,
4548
fetchedConfigsCache,

firebase-config/ktx/src/test/kotlin/com/google/firebase/remoteconfig/ktx/RemoteConfigTests.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import com.google.firebase.remoteconfig.internal.ConfigFetchHandler
2626
import com.google.firebase.remoteconfig.internal.ConfigGetParameterHandler
2727
import com.google.firebase.remoteconfig.internal.ConfigMetadataClient
2828
import com.google.common.util.concurrent.MoreExecutors
29+
import com.google.firebase.iid.FirebaseInstanceId
2930
import com.google.firebase.ktx.app
3031
import com.google.firebase.ktx.initialize
3132
import org.junit.After
@@ -127,6 +128,7 @@ class ConfigTests : BaseTestCase() {
127128
val remoteConfig = createRemoteConfig(
128129
context = null,
129130
firebaseApp = Firebase.app(EXISTING_APP),
131+
firebaseInstanceId = mock(FirebaseInstanceId::class.java),
130132
firebaseAbt = null,
131133
executor = directExecutor,
132134
fetchedConfigsCache = mock(ConfigCacheClient::class.java),

firebase-config/src/androidTest/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigIntegrationTest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import com.google.firebase.FirebaseApp;
3131
import com.google.firebase.FirebaseOptions;
3232
import com.google.firebase.abt.FirebaseABTesting;
33+
import com.google.firebase.iid.FirebaseInstanceId;
3334
import com.google.firebase.remoteconfig.internal.ConfigCacheClient;
3435
import com.google.firebase.remoteconfig.internal.ConfigContainer;
3536
import com.google.firebase.remoteconfig.internal.ConfigFetchHandler;
@@ -63,6 +64,7 @@ public class FirebaseRemoteConfigIntegrationTest {
6364
@Mock private ConfigCacheClient mockFireperfActivatedCache;
6465

6566
@Mock private FirebaseABTesting mockFirebaseAbt;
67+
@Mock private FirebaseInstanceId mockFirebaseInstanceId;
6668
private FirebaseRemoteConfig frc;
6769

6870
// We use a HashMap so that Mocking is easier.
@@ -94,6 +96,7 @@ public void setUp() {
9496
new FirebaseRemoteConfig(
9597
context,
9698
firebaseApp,
99+
mockFirebaseInstanceId,
97100
mockFirebaseAbt,
98101
directExecutor,
99102
mockFetchedCache,

firebase-config/src/main/java/com/google/firebase/remoteconfig/FirebaseRemoteConfig.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@
2626
import com.google.firebase.FirebaseApp;
2727
import com.google.firebase.abt.AbtException;
2828
import com.google.firebase.abt.FirebaseABTesting;
29+
import com.google.firebase.iid.FirebaseInstanceId;
30+
import com.google.firebase.iid.InstanceIdResult;
2931
import com.google.firebase.remoteconfig.internal.ConfigCacheClient;
3032
import com.google.firebase.remoteconfig.internal.ConfigContainer;
3133
import com.google.firebase.remoteconfig.internal.ConfigFetchHandler;
@@ -147,6 +149,7 @@ public static FirebaseRemoteConfig getInstance(@NonNull FirebaseApp app) {
147149
private final ConfigFetchHandler fetchHandler;
148150
private final ConfigGetParameterHandler getHandler;
149151
private final ConfigMetadataClient frcMetadata;
152+
private final FirebaseInstanceId firebaseInstanceId;
150153

151154
/**
152155
* Firebase Remote Config constructor.
@@ -156,6 +159,7 @@ public static FirebaseRemoteConfig getInstance(@NonNull FirebaseApp app) {
156159
FirebaseRemoteConfig(
157160
Context context,
158161
FirebaseApp firebaseApp,
162+
FirebaseInstanceId firebaseInstanceId,
159163
@Nullable FirebaseABTesting firebaseAbt,
160164
Executor executor,
161165
ConfigCacheClient fetchedConfigsCache,
@@ -166,6 +170,7 @@ public static FirebaseRemoteConfig getInstance(@NonNull FirebaseApp app) {
166170
ConfigMetadataClient frcMetadata) {
167171
this.context = context;
168172
this.firebaseApp = firebaseApp;
173+
this.firebaseInstanceId = firebaseInstanceId;
169174
this.firebaseAbt = firebaseAbt;
170175
this.executor = executor;
171176
this.fetchedConfigsCache = fetchedConfigsCache;
@@ -186,9 +191,14 @@ public Task<FirebaseRemoteConfigInfo> ensureInitialized() {
186191
Task<ConfigContainer> defaultsConfigsTask = defaultConfigsCache.get();
187192
Task<ConfigContainer> fetchedConfigsTask = fetchedConfigsCache.get();
188193
Task<FirebaseRemoteConfigInfo> metadataTask = Tasks.call(executor, this::getInfo);
194+
Task<InstanceIdResult> instanceIdTask = firebaseInstanceId.getInstanceId();
189195

190196
return Tasks.whenAllComplete(
191-
activatedConfigsTask, defaultsConfigsTask, fetchedConfigsTask, metadataTask)
197+
activatedConfigsTask,
198+
defaultsConfigsTask,
199+
fetchedConfigsTask,
200+
metadataTask,
201+
instanceIdTask)
192202
.continueWith(executor, (unusedListOfCompletedTasks) -> metadataTask.getResult());
193203
}
194204

firebase-config/src/main/java/com/google/firebase/remoteconfig/RemoteConfigComponent.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,7 @@ public synchronized FirebaseRemoteConfig get(String namespace) {
156156
return get(
157157
firebaseApp,
158158
namespace,
159+
firebaseInstanceId,
159160
firebaseAbt,
160161
executorService,
161162
fetchedCacheClient,
@@ -170,6 +171,7 @@ public synchronized FirebaseRemoteConfig get(String namespace) {
170171
synchronized FirebaseRemoteConfig get(
171172
FirebaseApp firebaseApp,
172173
String namespace,
174+
FirebaseInstanceId firebaseInstanceId,
173175
FirebaseABTesting firebaseAbt,
174176
Executor executor,
175177
ConfigCacheClient fetchedClient,
@@ -183,6 +185,7 @@ synchronized FirebaseRemoteConfig get(
183185
new FirebaseRemoteConfig(
184186
context,
185187
firebaseApp,
188+
firebaseInstanceId,
186189
isAbtSupported(firebaseApp, namespace) ? firebaseAbt : null,
187190
executor,
188191
fetchedClient,

firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigFetchHandler.java

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
import com.google.android.gms.tasks.Tasks;
3737
import com.google.firebase.analytics.connector.AnalyticsConnector;
3838
import com.google.firebase.iid.FirebaseInstanceId;
39+
import com.google.firebase.iid.InstanceIdResult;
3940
import com.google.firebase.remoteconfig.FirebaseRemoteConfigClientException;
4041
import com.google.firebase.remoteconfig.FirebaseRemoteConfigException;
4142
import com.google.firebase.remoteconfig.FirebaseRemoteConfigFetchThrottledException;
@@ -188,7 +189,21 @@ && areCachedFetchConfigsValid(minimumFetchIntervalInSeconds, currentTime)) {
188189
createThrottledMessage(backoffEndTime.getTime() - currentTime.getTime()),
189190
backoffEndTime.getTime()));
190191
} else {
191-
fetchResponseTask = fetchFromBackendAndCacheResponse(currentTime);
192+
Task<InstanceIdResult> instanceIdTask = firebaseInstanceId.getInstanceId();
193+
fetchResponseTask =
194+
instanceIdTask.continueWithTask(
195+
executor,
196+
(completedIidTask) -> {
197+
if (!completedIidTask.isSuccessful()) {
198+
return Tasks.forException(
199+
new FirebaseRemoteConfigClientException(
200+
"Failed to get Firebase Instance ID token for fetch.",
201+
completedIidTask.getException()));
202+
}
203+
204+
InstanceIdResult instanceIdResult = completedIidTask.getResult();
205+
return fetchFromBackendAndCacheResponse(instanceIdResult, currentTime);
206+
});
192207
}
193208

194209
return fetchResponseTask.continueWithTask(
@@ -246,9 +261,10 @@ private String createThrottledMessage(long throttledDurationInMillis) {
246261
* Fetches configs from the FRC backend. If there are any updates, writes the configs to the
247262
* {@code fetchedConfigsCache}.
248263
*/
249-
private Task<FetchResponse> fetchFromBackendAndCacheResponse(Date fetchTime) {
264+
private Task<FetchResponse> fetchFromBackendAndCacheResponse(
265+
InstanceIdResult instanceId, Date fetchTime) {
250266
try {
251-
FetchResponse fetchResponse = fetchFromBackend(fetchTime);
267+
FetchResponse fetchResponse = fetchFromBackend(instanceId, fetchTime);
252268
if (fetchResponse.getStatus() != Status.BACKEND_UPDATES_FETCHED) {
253269
return Tasks.forResult(fetchResponse);
254270
}
@@ -270,15 +286,16 @@ private Task<FetchResponse> fetchFromBackendAndCacheResponse(Date fetchTime) {
270286
* error connecting to the server.
271287
*/
272288
@WorkerThread
273-
private FetchResponse fetchFromBackend(Date currentTime) throws FirebaseRemoteConfigException {
289+
private FetchResponse fetchFromBackend(InstanceIdResult instanceId, Date currentTime)
290+
throws FirebaseRemoteConfigException {
274291
try {
275292
HttpURLConnection urlConnection = frcBackendApiClient.createHttpURLConnection();
276293

277294
FetchResponse response =
278295
frcBackendApiClient.fetch(
279296
urlConnection,
280-
firebaseInstanceId.getId(),
281-
firebaseInstanceId.getToken(),
297+
instanceId.getId(),
298+
instanceId.getToken(),
282299
getUserProperties(),
283300
frcMetadata.getLastFetchETag(),
284301
customHttpHeaders,
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2018 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
//
6+
// You may obtain a copy of the License at
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.firebase.remoteconfig;
16+
17+
import com.google.firebase.iid.InstanceIdResult;
18+
19+
/**
20+
* Implementation of InstanceIdResult intended for testing.
21+
*
22+
* @author Dana Silver
23+
*/
24+
public class FakeInstanceIdResult implements InstanceIdResult {
25+
private final String id;
26+
private final String token;
27+
28+
public FakeInstanceIdResult(String id, String token) {
29+
this.id = id;
30+
this.token = token;
31+
}
32+
33+
@Override
34+
public String getId() {
35+
return id;
36+
}
37+
38+
@Override
39+
public String getToken() {
40+
return token;
41+
}
42+
}

firebase-config/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
import com.google.firebase.FirebaseOptions;
4646
import com.google.firebase.abt.AbtException;
4747
import com.google.firebase.abt.FirebaseABTesting;
48+
import com.google.firebase.iid.FirebaseInstanceId;
49+
import com.google.firebase.iid.InstanceIdResult;
4850
import com.google.firebase.remoteconfig.internal.ConfigCacheClient;
4951
import com.google.firebase.remoteconfig.internal.ConfigContainer;
5052
import com.google.firebase.remoteconfig.internal.ConfigFetchHandler;
@@ -94,6 +96,11 @@ public final class FirebaseRemoteConfigTest {
9496

9597
private static final String ETAG = "ETag";
9698

99+
private static final String INSTANCE_ID_STRING = "fake instance id";
100+
private static final String INSTANCE_ID_TOKEN_STRING = "fake instance id token";
101+
private static final InstanceIdResult INSTANCE_ID_RESULT =
102+
new FakeInstanceIdResult(INSTANCE_ID_STRING, INSTANCE_ID_TOKEN_STRING);
103+
97104
// We use a HashMap so that Mocking is easier.
98105
private static final HashMap<String, Object> DEFAULTS_MAP = new HashMap<>();
99106
private static final HashMap<String, String> DEFAULTS_STRING_MAP = new HashMap<>();
@@ -114,6 +121,7 @@ public final class FirebaseRemoteConfigTest {
114121
@Mock private FirebaseRemoteConfigInfo mockFrcInfo;
115122

116123
@Mock private FirebaseABTesting mockFirebaseAbt;
124+
@Mock private FirebaseInstanceId mockFirebaseInstanceId;
117125

118126
private FirebaseRemoteConfig frc;
119127
private FirebaseRemoteConfig fireperfFrc;
@@ -150,6 +158,7 @@ public void setUp() throws Exception {
150158
new FirebaseRemoteConfig(
151159
context,
152160
firebaseApp,
161+
mockFirebaseInstanceId,
153162
mockFirebaseAbt,
154163
directExecutor,
155164
mockFetchedCache,
@@ -166,6 +175,7 @@ public void setUp() throws Exception {
166175
.get(
167176
firebaseApp,
168177
FIREPERF_NAMESPACE,
178+
mockFirebaseInstanceId,
169179
/*firebaseAbt=*/ null,
170180
directExecutor,
171181
mockFireperfFetchedCache,
@@ -197,6 +207,7 @@ public void ensureInitialized_notInitialized_isNotComplete() {
197207
loadCacheWithConfig(mockFetchedCache, /*container=*/ null);
198208
loadCacheWithConfig(mockDefaultsCache, /*container=*/ null);
199209
loadActivatedCacheWithIncompleteTask();
210+
loadInstanceIdAndToken();
200211

201212
Task<FirebaseRemoteConfigInfo> initStatus = frc.ensureInitialized();
202213

@@ -210,6 +221,7 @@ public void ensureInitialized_initialized_returnsCorrectFrcInfo() {
210221
loadCacheWithConfig(mockFetchedCache, /*container=*/ null);
211222
loadCacheWithConfig(mockDefaultsCache, /*container=*/ null);
212223
loadCacheWithConfig(mockActivatedCache, /*container=*/ null);
224+
loadInstanceIdAndToken();
213225

214226
Task<FirebaseRemoteConfigInfo> initStatus = frc.ensureInitialized();
215227

@@ -1166,6 +1178,10 @@ private void load2pFetchHandlerWithResponse() {
11661178
.thenReturn(Tasks.forResult(firstFetchedContainerResponse));
11671179
}
11681180

1181+
private void loadInstanceIdAndToken() {
1182+
when(mockFirebaseInstanceId.getInstanceId()).thenReturn(Tasks.forResult(INSTANCE_ID_RESULT));
1183+
}
1184+
11691185
private static int getResourceId(String xmlResourceName) {
11701186
Resources r = RuntimeEnvironment.application.getResources();
11711187
return r.getIdentifier(xmlResourceName, "xml", RuntimeEnvironment.application.getPackageName());

firebase-config/src/test/java/com/google/firebase/remoteconfig/RemoteConfigComponentTest.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -202,6 +202,7 @@ private FirebaseRemoteConfig getFrcInstanceFromComponent(
202202
return frcComponent.get(
203203
mockFirebaseApp,
204204
namespace,
205+
mockFirebaseIid,
205206
mockFirebaseAbt,
206207
directExecutor,
207208
mockFetchedCache,

firebase-config/src/test/java/com/google/firebase/remoteconfig/internal/ConfigFetchHandlerTest.java

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@
6060
import com.google.common.util.concurrent.MoreExecutors;
6161
import com.google.firebase.analytics.connector.AnalyticsConnector;
6262
import com.google.firebase.iid.FirebaseInstanceId;
63+
import com.google.firebase.remoteconfig.FakeInstanceIdResult;
6364
import com.google.firebase.remoteconfig.FirebaseRemoteConfigClientException;
6465
import com.google.firebase.remoteconfig.FirebaseRemoteConfigFetchThrottledException;
6566
import com.google.firebase.remoteconfig.FirebaseRemoteConfigServerException;
@@ -171,6 +172,37 @@ public void fetch_noPreviousSuccessfulFetch_fetchesFromBackend() throws Exceptio
171172
verifyBackendIsCalled();
172173
}
173174

175+
@Test
176+
public void fetch_firstFetch_includesIidToken() throws Exception {
177+
fetchCallToHttpClientReturnsConfigWithCurrentTime(firstFetchedContainer);
178+
179+
assertWithMessage("Fetch() does not include IID token.")
180+
.that(fetchHandler.fetch().isSuccessful())
181+
.isTrue();
182+
183+
verify(mockBackendFetchApiClient)
184+
.fetch(
185+
any(HttpURLConnection.class),
186+
/* instanceId= */ any(),
187+
/* instanceIdToken= */ eq(INSTANCE_ID_TOKEN_STRING),
188+
/* analyticsUserProperties= */ any(),
189+
/* lastFetchETag= */ any(),
190+
/* customHeaders= */ any(),
191+
/* currentTime= */ any());
192+
}
193+
194+
@Test
195+
public void fetch_failToGetIidToken_throwsRemoteConfigException() throws Exception {
196+
when(mockFirebaseInstanceId.getInstanceId())
197+
.thenReturn(Tasks.forException(new IOException("SERVICE_NOT_AVAILABLE")));
198+
fetchCallToHttpClientReturnsConfigWithCurrentTime(firstFetchedContainer);
199+
200+
assertThrowsClientException(
201+
fetchHandler.fetch(), "Failed to get Firebase Instance ID token for fetch.");
202+
203+
verifyBackendIsNeverCalled();
204+
}
205+
174206
@Test
175207
public void fetch_cacheHasNotExpired_doesNotFetchFromBackend() throws Exception {
176208
loadCacheAndClockWithConfig(mockFetchedCache, firstFetchedContainer);
@@ -866,8 +898,10 @@ private void loadETags(String requestETag, String responseETag) {
866898
}
867899

868900
private void loadInstanceIdAndToken() {
869-
when(mockFirebaseInstanceId.getId()).thenReturn(INSTANCE_ID_STRING);
870-
when(mockFirebaseInstanceId.getToken()).thenReturn(INSTANCE_ID_TOKEN_STRING);
901+
when(mockFirebaseInstanceId.getInstanceId())
902+
.thenReturn(
903+
Tasks.forResult(
904+
new FakeInstanceIdResult(INSTANCE_ID_STRING, INSTANCE_ID_TOKEN_STRING)));
871905
}
872906

873907
private void loadCacheAndClockWithConfig(

0 commit comments

Comments
 (0)