AB#268288 Federated Authentication#87
Conversation
7e18f19 to
db27d29
Compare
| } | ||
| result = super.postSync(url, postBody, true); | ||
| String jwt = super.resolveJwt(this.customerId); | ||
| if (AuthJwtResolver.isMissingRequiredJwt(Optimove.getConfig().getAuthTokenProvider(), this.customerId, jwt)) { |
There was a problem hiding this comment.
See line 99, could this cause all visitors to start failing requests? (userId is either userId or visitor here)
|
@graciecooper Can we add some more test coverage for the auth flows (Embedded, grouping etc) or that would require significant refactoring? |
| error != null ? error.getMessage() : "null token"); | ||
| dispatchRequestWaitsForResponse = false; | ||
| scheduleTheNextDispatch(); | ||
| return; |
There was a problem hiding this comment.
Should we abort all the remaining groups if one fails?
|
|
||
| Runnable postOnExecutor = () -> postGroupJson(group, groups, index, null); | ||
|
|
||
| if (authManager != null && !customerKey.isEmpty()) { |
There was a problem hiding this comment.
Can we reduce the indentation here?
if (authManager == null || customerIsEmpty) {
postOnExecutor.run();
return;
}
// rest of auth
|
@graciecooper Is it possible to introduce a single dispatcher layer for RT and optistream like we did in iOS? or it would require a big refactoring? |
| - Added Federated JWT authentication; OptimoveConfig.Builder.enableAuth(AuthTokenProvider) supplies tokens; the SDK adds X-User-JWT for user-identified Optistream, realtime, Preference Center, Embedded Messaging, Optimobile analytics, and in-app network calls. | ||
| - Added Auth-capable signaling; Outbound SDK requests sent through HttpClient and OptimobileHttpClient include X-Optimove-Auth-Capable: 1 so backends can detect JWT-capable SDK versions. | ||
| - Optistream and realtime paths now group events by customer identity so each request can carry a single JWT. Optimobile analytics drains queued events per stored user id (install/visitor-scoped batches do not attach a user JWT). | ||
|
|
| Map<String, List<OptistreamEvent>> map = new LinkedHashMap<>(); | ||
| for (OptistreamEvent ev : events) { | ||
| String key = userKey(ev); | ||
| map.computeIfAbsent(key, k -> new ArrayList<>()).add(ev); |
There was a problem hiding this comment.
Does it require API 24? I think our minimum is 21?
| } | ||
| httpClient.postJson(realtimeConfigs.getRealtimeGateway(), realtimeGson.toJson(group)) | ||
| .userJwt(token) | ||
| .successListener(jsonResponse -> dispatchGroupAtIndex(groups, index + 1, allForFailure)) |
There was a problem hiding this comment.
Should it be allForFailure or just the one group?
ed28f78 to
33558ba
Compare
| } | ||
|
|
||
| @Nullable | ||
| public static String getAssociatedUserIdentifier(@NonNull Context context) { |
There was a problem hiding this comment.
Should we just add a fallback param / or something like boolean excludeFallback as a second param to getCurrentUserIdentifier ?
There was a problem hiding this comment.
i've went with a separate method because think it makes the intent more obvious than a boolean flag, especially around auth vs visitor fallback? but am happy to switch if you prefer the flag approach ? :)
| private InAppMessageView view; | ||
|
|
||
| private boolean interceptionInProgress = false; | ||
| private int lastShownByInterceptorId = -1; |
There was a problem hiding this comment.
Is this safe to delete? I think it was added as an idempotency key that prevented applyMessageInterception from running the second time
There was a problem hiding this comment.
Think I've accidentally committed a prev version of the file stored locally, didn't intend to delete this. Reverted!
| Map<String, List<AnalyticsEventRow>> groups = new LinkedHashMap<>(); | ||
| for (AnalyticsEventRow row : rows) { | ||
| String key = analyticsUserKey(row.event); | ||
| groups.computeIfAbsent(key, k -> new ArrayList<>()).add(row); |
There was a problem hiding this comment.
Seems like computeIfAbsent requires API 24, we are still on 21
There was a problem hiding this comment.
oh yeah !! 😨 replaced with an explicit get/null-check/put
| return ""; | ||
| } | ||
| String u = event.optString("userId", ""); | ||
| return u == null ? "" : u.trim(); |
There was a problem hiding this comment.
can just return u.trim() ?
| deletePersistedEventsByIds(context, ids); | ||
| continue; | ||
| } | ||
| return false; |
There was a problem hiding this comment.
Do we fail all groups?
| } | ||
| } catch (IOException e) { | ||
| e.printStackTrace(); | ||
| return false; |
There was a problem hiding this comment.
Same question here - do we fail all groups in this case?
| .errorListener(e -> onRealtimeRequestFailed(e, groups, index, group)) | ||
| .destination("%s", RealtimeConstants.REPORT_EVENT_REQUEST_ROUTE) | ||
| .send(); | ||
| }); |
There was a problem hiding this comment.
Can we return here to reduce the indentation of the else block? feel free to ignore
There was a problem hiding this comment.
Yeah that's probs more readable, done!
There was a problem hiding this comment.
I think now just indented back? Created a wrong indentation. I meant if we just return on the if (authManager != null && !key.isEmpty()) block. Something like:
if (authManager != null && !key.isEmpty()) {
authManager.getToken(key, (token, error) -> {
if (error != null || token == null) {
dispatchingFailed(error != null ? error : new Exception("null token"), group);
return;
}
httpClient.postJson(realtimeConfigs.getRealtimeGateway(), realtimeGson.toJson(group))
.userJwt(token)
.successListener(jsonResponse -> dispatchGroupAtIndex(groups, index + 1))
.errorListener(e -> onRealtimeRequestFailed(e, groups, index, group))
.destination("%s", RealtimeConstants.REPORT_EVENT_REQUEST_ROUTE)
.send();
});
return;
}
httpClient.postJson(realtimeConfigs.getRealtimeGateway(), realtimeGson.toJson(group))
.userJwt(null)
.successListener(jsonResponse -> dispatchGroupAtIndex(groups, index + 1))
.errorListener(e -> onRealtimeRequestFailed(e, groups, index, group))
.destination("%s", RealtimeConstants.REPORT_EVENT_REQUEST_ROUTE)
.send();
Up to you though
It's a bit footery in that I think it would involve a lot more time retro-fitting tests ...? I think it would overcomplicate this PR but happy to revisit if we feel strongly about completely aligning with iOS. |
| if (authManager != null && !key.isEmpty()) { | ||
| authManager.getToken(key, (token, error) -> { | ||
| if (error != null || token == null) { | ||
| dispatchingFailed(error != null ? error : new Exception("null token"), group); |
There was a problem hiding this comment.
Should we still call dispatchGroupAtIndex(groups, index + 1) here? Otherwise one user's token-fetch failure halts the whole batch (iOS's OptistreamDispatcher always advances on per-group failure). In practice, seeing the big picture, this shouldnt really happen and even if it happens, there are 2 batches - user and visitor so in worst case we might halt the visitor batch.
There was a problem hiding this comment.
Yeah, let's add it so that it advances the non-failing group and bring it in line with iOS
Description of Changes
Adds Federated JWT authentication to the Android SDK. When
OptimoveConfig.Builder.enableAuth(AuthTokenProvider)is used, the SDK asks the app for a JWT per user context and attaches it asX-User-JWTon user-identified outbound traffic (anonymous / install-scoped traffic omits the JWT where applicable).Key changes
enableAuth(AuthTokenProvider)on OptimoveConfig.BuilderuserIdentifierso the JWT matches the batchUsage
Kotlin
Breaking Changes
Release Checklist
Prepare:
Bump versions in:
Integration tests
T&T Only
Mobile Only
Deferred Deep Links
Combined
Release Procedure
devtomaster