Skip to content

Commit a7c396d

Browse files
Unauthenticated endpoints do not require jwt to work
1 parent b691ea2 commit a7c396d

File tree

8 files changed

+275
-71
lines changed

8 files changed

+275
-71
lines changed
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package com.iterable.iterableapi;
2+
3+
import java.util.Arrays;
4+
import java.util.HashSet;
5+
import java.util.Set;
6+
7+
class ApiEndpointClassification {
8+
9+
private static final Set<String> DEFAULT_UNAUTHENTICATED = new HashSet<>(Arrays.asList(
10+
IterableConstants.ENDPOINT_DISABLE_DEVICE,
11+
IterableConstants.ENDPOINT_GET_REMOTE_CONFIGURATION,
12+
IterableConstants.ENDPOINT_MERGE_USER,
13+
IterableConstants.ENDPOINT_CRITERIA_LIST,
14+
IterableConstants.ENDPOINT_TRACK_UNKNOWN_SESSION,
15+
IterableConstants.ENDPOINT_TRACK_CONSENT
16+
));
17+
18+
private volatile Set<String> unauthenticatedPaths = new HashSet<>(DEFAULT_UNAUTHENTICATED);
19+
20+
boolean requiresJwt(String path) {
21+
return !unauthenticatedPaths.contains(path);
22+
}
23+
24+
void updateFromRemoteConfig(Set<String> paths) {
25+
this.unauthenticatedPaths = new HashSet<>(paths);
26+
}
27+
}

iterableapi/src/main/java/com/iterable/iterableapi/IterableApi.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ public class IterableApi {
4848
private IterableHelper.FailureHandler _setUserFailureCallbackHandler;
4949

5050
IterableApiClient apiClient = new IterableApiClient(new IterableApiAuthProvider());
51+
final ApiEndpointClassification apiEndpointClassification = new ApiEndpointClassification();
5152
private static final UnknownUserMerge unknownUserMerge = new UnknownUserMerge();
5253
private @Nullable UnknownUserManager unknownUserManager;
5354
private @Nullable IterableInAppManager inAppManager;

iterableapi/src/main/java/com/iterable/iterableapi/IterableTask.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,10 @@ class IterableTask {
5858
this.taskType = taskType;
5959
}
6060

61+
boolean requiresJwt(ApiEndpointClassification classification) {
62+
return classification.requiresJwt(this.name);
63+
}
64+
6165
}
6266

6367
enum IterableTaskType {

iterableapi/src/main/java/com/iterable/iterableapi/IterableTaskRunner.java

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class IterableTaskRunner implements IterableTaskStorage.TaskCreatedListener, Han
2020
private IterableActivityMonitor activityMonitor;
2121
private IterableNetworkConnectivityManager networkConnectivityManager;
2222
private HealthMonitor healthMonitor;
23+
private ApiEndpointClassification classification;
2324

2425
private static final int RETRY_INTERVAL_SECONDS = 60;
2526

@@ -45,18 +46,28 @@ interface TaskCompletedListener {
4546
IterableTaskRunner(IterableTaskStorage taskStorage,
4647
IterableActivityMonitor activityMonitor,
4748
IterableNetworkConnectivityManager networkConnectivityManager,
48-
HealthMonitor healthMonitor) {
49+
HealthMonitor healthMonitor,
50+
ApiEndpointClassification classification) {
4951
this.taskStorage = taskStorage;
5052
this.activityMonitor = activityMonitor;
5153
this.networkConnectivityManager = networkConnectivityManager;
5254
this.healthMonitor = healthMonitor;
55+
this.classification = classification;
5356
networkThread.start();
5457
handler = new Handler(networkThread.getLooper(), this);
5558
taskStorage.addTaskCreatedListener(this);
5659
networkConnectivityManager.addNetworkListener(this);
5760
activityMonitor.addCallback(this);
5861
}
5962

63+
// Preserved for backward compatibility with existing tests
64+
IterableTaskRunner(IterableTaskStorage taskStorage,
65+
IterableActivityMonitor activityMonitor,
66+
IterableNetworkConnectivityManager networkConnectivityManager,
67+
HealthMonitor healthMonitor) {
68+
this(taskStorage, activityMonitor, networkConnectivityManager, healthMonitor, new ApiEndpointClassification());
69+
}
70+
6071
void addTaskCompletedListener(TaskCompletedListener listener) {
6172
taskCompletedListeners.add(listener);
6273
}
@@ -130,13 +141,7 @@ private void processTasks() {
130141
boolean autoRetry = IterableApi.getInstance().isAutoRetryOnJwtFailure();
131142

132143
while (networkConnectivityManager.isConnected()) {
133-
// [F] When autoRetry is enabled, also check that auth token is ready before processing
134-
if (autoRetry && !IterableApi.getInstance().getAuthManager().isAuthTokenReady()) {
135-
IterableLogger.d(TAG, "Auth token not ready, pausing task processing");
136-
return;
137-
}
138-
139-
IterableTask task = taskStorage.getNextScheduledTask();
144+
IterableTask task = getNextActionableTask(autoRetry);
140145

141146
if (task == null) {
142147
return;
@@ -154,10 +159,21 @@ private void processTasks() {
154159
}
155160
}
156161

162+
private IterableTask getNextActionableTask(boolean autoRetry) {
163+
boolean authBlocked = isPausedForAuth ||
164+
(autoRetry && !IterableApi.getInstance().getAuthManager().isAuthTokenReady());
165+
if (!authBlocked) {
166+
return taskStorage.getNextScheduledTask();
167+
}
168+
return taskStorage.getNextScheduledTaskNotRequiringJwt(classification);
169+
}
170+
171+
void setIsPausedForAuth(boolean paused) {
172+
this.isPausedForAuth = paused;
173+
}
174+
157175
@WorkerThread
158176
private boolean processTask(@NonNull IterableTask task, boolean autoRetry) {
159-
isPausedForAuth = false;
160-
161177
if (task.taskType == IterableTaskType.API) {
162178
IterableApiResponse response = null;
163179
TaskResult result = TaskResult.FAILURE;
@@ -177,7 +193,7 @@ private boolean processTask(@NonNull IterableTask task, boolean autoRetry) {
177193
if (response.success) {
178194
result = TaskResult.SUCCESS;
179195
} else {
180-
// [F] If autoRetry is enabled and response is a 401 JWT error,
196+
// If autoRetry is enabled and response is a 401 JWT error,
181197
// retain the task and pause processing until a valid JWT is obtained.
182198
if (autoRetry && isJwtFailure(response)) {
183199
IterableLogger.d(TAG, "JWT auth failure on task " + task.id + ". Retaining task and pausing processing.");

iterableapi/src/main/java/com/iterable/iterableapi/IterableTaskStorage.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,34 @@ IterableTask getNextScheduledTask() {
287287
return task;
288288
}
289289

290+
/**
291+
* Returns the next scheduled task that does not require JWT authentication.
292+
* Iterates tasks ordered by scheduledAt and returns the first one classified
293+
* as unauthenticated by the given classification.
294+
*
295+
* @param classification the endpoint classification to check against
296+
* @return next unauthenticated {@link IterableTask}, or null if none found
297+
*/
298+
@Nullable
299+
IterableTask getNextScheduledTaskNotRequiringJwt(ApiEndpointClassification classification) {
300+
if (!isDatabaseReady()) {
301+
return null;
302+
}
303+
Cursor cursor = database.rawQuery("select * from OfflineTask order by scheduled", null);
304+
IterableTask task = null;
305+
if (cursor.moveToFirst()) {
306+
do {
307+
IterableTask candidate = createTaskFromCursor(cursor);
308+
if (!candidate.requiresJwt(classification)) {
309+
task = candidate;
310+
break;
311+
}
312+
} while (cursor.moveToNext());
313+
}
314+
cursor.close();
315+
return task;
316+
}
317+
290318
/**
291319
* Deletes all the entries from the OfflineTask table.
292320
*/

iterableapi/src/main/java/com/iterable/iterableapi/OfflineRequestProcessor.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,10 +38,12 @@ class OfflineRequestProcessor implements RequestProcessor {
3838
IterableNetworkConnectivityManager networkConnectivityManager = IterableNetworkConnectivityManager.sharedInstance(context);
3939
taskStorage = IterableTaskStorage.sharedInstance(context);
4040
healthMonitor = new HealthMonitor(taskStorage);
41+
ApiEndpointClassification classification = IterableApi.getInstance().apiEndpointClassification;
4142
taskRunner = new IterableTaskRunner(taskStorage,
4243
IterableActivityMonitor.getInstance(),
4344
networkConnectivityManager,
44-
healthMonitor);
45+
healthMonitor,
46+
classification);
4547
taskScheduler = new TaskScheduler(taskStorage, taskRunner);
4648

4749
// Register task runner as auth token ready listener for JWT auto-retry support
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.iterable.iterableapi;
2+
3+
import org.junit.Before;
4+
import org.junit.Test;
5+
6+
import java.util.Arrays;
7+
import java.util.HashSet;
8+
9+
import static org.junit.Assert.assertFalse;
10+
import static org.junit.Assert.assertTrue;
11+
12+
public class ApiEndpointClassificationTest {
13+
14+
private ApiEndpointClassification classification;
15+
16+
@Before
17+
public void setUp() {
18+
classification = new ApiEndpointClassification();
19+
}
20+
21+
@Test
22+
public void testDefaultUnauthenticatedEndpoints() {
23+
// THIS IS IMPORTANT SO IF WE CHANGE IT FOR TESTING WE WILL HAVE THIS FAILING
24+
assertFalse(classification.requiresJwt(IterableConstants.ENDPOINT_DISABLE_DEVICE));
25+
assertFalse(classification.requiresJwt(IterableConstants.ENDPOINT_GET_REMOTE_CONFIGURATION));
26+
assertFalse(classification.requiresJwt(IterableConstants.ENDPOINT_MERGE_USER));
27+
assertFalse(classification.requiresJwt(IterableConstants.ENDPOINT_CRITERIA_LIST));
28+
assertFalse(classification.requiresJwt(IterableConstants.ENDPOINT_TRACK_UNKNOWN_SESSION));
29+
assertFalse(classification.requiresJwt(IterableConstants.ENDPOINT_TRACK_CONSENT));
30+
}
31+
32+
@Test
33+
public void testUnknownEndpointRequiresJwt() {
34+
assertTrue(classification.requiresJwt("unknown/endpoint"));
35+
}
36+
37+
@Test
38+
public void testUpdateFromRemoteConfigOverridesDefaults() {
39+
// Override: now only "events/track" is unauthenticated
40+
classification.updateFromRemoteConfig(
41+
new HashSet<>(Arrays.asList(IterableConstants.ENDPOINT_TRACK))
42+
);
43+
44+
assertFalse(classification.requiresJwt(IterableConstants.ENDPOINT_TRACK));
45+
// Previously unauthenticated endpoints now require JWT
46+
assertTrue(classification.requiresJwt(IterableConstants.ENDPOINT_DISABLE_DEVICE));
47+
assertTrue(classification.requiresJwt(IterableConstants.ENDPOINT_MERGE_USER));
48+
}
49+
50+
@Test
51+
public void testIterableTaskRequiresJwtDelegation() {
52+
IterableTask authTask = new IterableTask(IterableConstants.ENDPOINT_TRACK, IterableTaskType.API, "{}");
53+
IterableTask unauthTask = new IterableTask(IterableConstants.ENDPOINT_DISABLE_DEVICE, IterableTaskType.API, "{}");
54+
55+
assertTrue(authTask.requiresJwt(classification));
56+
assertFalse(unauthTask.requiresJwt(classification));
57+
}
58+
}

0 commit comments

Comments
 (0)