-
Notifications
You must be signed in to change notification settings - Fork 377
feat: identity verification 5.8 #2599
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
base: main
Are you sure you want to change the base?
Changes from 44 commits
ca6974e
d59aa97
08215b2
6c2a7bb
cd42bf6
c51074d
c6bc9c0
268af36
c49f26d
18d8575
d87089d
1dab8e0
50965e0
0c8abb8
b145834
0416677
4a9291d
632a358
0f1ad82
09a4afa
4316bd2
0585461
b0118ed
5686224
c7bc6b2
b12c74f
e818c0a
d663e43
ab453bb
8cc4812
e4bf212
374528b
8606964
89ca431
12e4794
b7b1edb
8e260bd
d198153
ff1a6b2
6050a4b
6ad204e
fb2353f
13c11ed
3f3dd09
9d3d46f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,17 @@ | ||
| package com.onesignal | ||
|
|
||
| /** | ||
| * Implement this interface and provide an instance to [OneSignal.addUserJwtInvalidatedListener] | ||
| * to be notified when the JWT for a user is invalidated. | ||
| * | ||
| * Callbacks are delivered on a background thread. | ||
| */ | ||
| interface IUserJwtInvalidatedListener { | ||
| /** | ||
| * Called when the JWT is invalidated for [UserJwtInvalidatedEvent.externalId]. | ||
| * Invoked on a background thread; see [IUserJwtInvalidatedListener] class documentation. | ||
| * | ||
| * @param event Describes which user's JWT was invalidated. | ||
| */ | ||
| fun onUserJwtInvalidated(event: UserJwtInvalidatedEvent) | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.onesignal | ||
|
|
||
| /** | ||
| * The event passed into [IUserJwtInvalidatedListener.onUserJwtInvalidated]. Delivery occurs on | ||
| * a background thread; see [IUserJwtInvalidatedListener]. | ||
| */ | ||
| class UserJwtInvalidatedEvent( | ||
| val externalId: String, | ||
| ) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -237,12 +237,15 @@ class ConfigModel : Model() { | |
| } | ||
|
|
||
| /** | ||
| * Whether SMS auth hash should be used. | ||
| * Whether identity verification (JWT) is required for this application. | ||
| * - `null` = unknown (remote params haven't arrived yet; all operations are held) | ||
| * - `false` = explicitly disabled (SDK behaves as today, no JWT gating) | ||
| * - `true` = enabled (operations require a valid JWT, anonymous users are blocked) | ||
| */ | ||
| var useIdentityVerification: Boolean | ||
| get() = getBooleanProperty(::useIdentityVerification.name) { false } | ||
| var useIdentityVerification: Boolean? | ||
|
Contributor
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. should we consider maintaining an Enum state here as opposed to a boolean that signifies three states? |
||
| get() = getOptBooleanProperty(::useIdentityVerification.name) | ||
| set(value) { | ||
| setBooleanProperty(::useIdentityVerification.name, value) | ||
| setOptBooleanProperty(::useIdentityVerification.name, value) | ||
| } | ||
|
|
||
| /** | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| package com.onesignal.core.internal.config.impl | ||
|
|
||
| import com.onesignal.common.modeling.ISingletonModelStoreChangeHandler | ||
| import com.onesignal.common.modeling.ModelChangeTags | ||
| import com.onesignal.common.modeling.ModelChangedArgs | ||
| import com.onesignal.core.internal.config.ConfigModel | ||
| import com.onesignal.core.internal.config.ConfigModelStore | ||
| import com.onesignal.core.internal.operations.IOperationRepo | ||
| import com.onesignal.core.internal.startup.IStartableService | ||
| import com.onesignal.debug.internal.logging.Logging | ||
| import com.onesignal.user.internal.UserManager | ||
| import com.onesignal.user.internal.identity.IdentityModelStore | ||
| import com.onesignal.user.internal.identity.JwtTokenStore | ||
|
|
||
| /** | ||
| * Reacts to the identity-verification remote param arriving via config HYDRATE. | ||
| * | ||
| * - When IV transitions from unknown (null) to true: purges anonymous operations. | ||
| * - When IV transitions from unknown (null) to any value: wakes the operation queue. | ||
| * - On beta migration: if IV=true and the current user has an externalId but no JWT, | ||
| * fires [UserJwtInvalidatedEvent] so the developer provides a fresh token. | ||
| */ | ||
| internal class IdentityVerificationService( | ||
| private val _configModelStore: ConfigModelStore, | ||
| private val _operationRepo: IOperationRepo, | ||
| private val _identityModelStore: IdentityModelStore, | ||
| private val _jwtTokenStore: JwtTokenStore, | ||
| private val _userManager: UserManager, | ||
| ) : IStartableService, ISingletonModelStoreChangeHandler<ConfigModel> { | ||
| override fun start() { | ||
| _configModelStore.subscribe(this) | ||
|
abdulraqeeb33 marked this conversation as resolved.
|
||
| _operationRepo.setJwtInvalidatedHandler { externalId -> | ||
| _userManager.fireJwtInvalidated(externalId) | ||
| } | ||
| } | ||
|
|
||
| override fun onModelReplaced( | ||
| model: ConfigModel, | ||
| tag: String, | ||
| ) { | ||
| if (tag != ModelChangeTags.HYDRATE) return | ||
|
|
||
| val useIV = model.useIdentityVerification | ||
|
|
||
| var jwtInvalidatedExternalId: String? = null | ||
| if (useIV == true) { | ||
| Logging.debug("IdentityVerificationService: IV enabled, purging anonymous operations") | ||
| _operationRepo.removeOperationsWithoutExternalId() | ||
|
|
||
| val externalId = _identityModelStore.model.externalId | ||
| if (externalId != null && _jwtTokenStore.getJwt(externalId) == null) { | ||
| Logging.debug("IdentityVerificationService: IV enabled but no JWT for $externalId, will fire invalidated event after queue wake") | ||
| jwtInvalidatedExternalId = externalId | ||
| } | ||
| } | ||
|
|
||
| _operationRepo.forceExecuteOperations() | ||
|
|
||
| jwtInvalidatedExternalId?.let { _userManager.fireJwtInvalidated(it) } | ||
|
nan-li marked this conversation as resolved.
|
||
| } | ||
|
|
||
| override fun onModelUpdated( | ||
| args: ModelChangedArgs, | ||
| tag: String, | ||
| ) { | ||
| // Individual property updates are not expected for remote params; | ||
| // ConfigModelStoreListener replaces the entire model on HYDRATE. | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -159,18 +159,6 @@ internal class HttpClient( | |
| con.doOutput = true | ||
| } | ||
|
|
||
| logHTTPSent(con.requestMethod, con.url, jsonBody, con.requestProperties) | ||
|
|
||
| if (jsonBody != null) { | ||
| val strJsonBody = JSONUtils.toUnescapedEUIDString(jsonBody) | ||
| val sendBytes = strJsonBody.toByteArray(charset("UTF-8")) | ||
| con.setFixedLengthStreamingMode(sendBytes.size) | ||
| val outputStream = con.outputStream | ||
| outputStream.write(sendBytes) | ||
| } | ||
|
|
||
| // H E A D E R S | ||
|
|
||
| if (headers?.cacheKey != null) { | ||
| val eTag = | ||
| _prefs.getString( | ||
|
|
@@ -195,6 +183,20 @@ internal class HttpClient( | |
| con.setRequestProperty("OneSignal-Session-Duration", headers.sessionDuration.toString()) | ||
| } | ||
|
|
||
| if (headers?.jwt != null) { | ||
|
Contributor
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. can we try using the kotlin version of null check here? |
||
| con.setRequestProperty("Authorization", "Bearer ${headers.jwt}") | ||
| } | ||
|
|
||
| logHTTPSent(con.requestMethod, con.url, jsonBody, con.requestProperties.filterKeys { it != "Authorization" }) | ||
|
|
||
| if (jsonBody != null) { | ||
| val strJsonBody = JSONUtils.toUnescapedEUIDString(jsonBody) | ||
| val sendBytes = strJsonBody.toByteArray(charset("UTF-8")) | ||
| con.setFixedLengthStreamingMode(sendBytes.size) | ||
| val outputStream = con.outputStream | ||
| outputStream.write(sendBytes) | ||
| } | ||
|
|
||
| // Network request is made from getResponseCode() | ||
| httpResponse = con.responseCode | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
like i mentioned elsewhere, we need suspend methods for these
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
added