Skip to content

Commit

Permalink
fixup: identity verification improvement
Browse files Browse the repository at this point in the history
  • Loading branch information
jinliu9508 committed Nov 18, 2024
1 parent db78c39 commit 8e9ae06
Show file tree
Hide file tree
Showing 24 changed files with 71 additions and 138 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ public void onCreate() {
// This will reproduce result similar to Kotlin CouroutineScope.launch{}, which may potentially crash the app
ExecutorService executor = Executors.newSingleThreadExecutor();
@SuppressLint({"NewApi", "LocalSuppress"}) CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
//OneSignal.getNotifications().requestPermission(true, Continue.none());
OneSignal.getNotifications().requestPermission(true, Continue.none());
}, executor);
future.join(); // Waits for the task to complete
executor.shutdown();
Expand Down Expand Up @@ -144,9 +144,6 @@ public void onUserStateChange(@NonNull UserChangedState state) {
OneSignal.addUserJwtInvalidatedListner(new IUserJwtInvalidatedListener() {
@Override
public void onUserJwtInvalidated(@NonNull UserJwtInvalidatedEvent event) {
// !!! For manual testing only
String jwt = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIwMTM5YmQ2Zi00NTFmLTQzOGMtODg4Ni00ZTBmMGZlM2EwODUiLCJleHAiOjE3MjczNjkyMjIsImlkZW50aXR5Ijp7ImV4dGVybmFsX2lkIjoiamluIn0sInN1YnNjcmlwdGlvbnMiOlt7InR5cGUiOiJFbWFpbCIsInRva2VuIjoidGVzdEBkb21haW4uY29tIn0seyJ0eXBlIjoiU01TIiwidG9rZW4iOiIrMTIzNDU2NzgifSx7InR5cGUiOiJBbmRyb2lkUHVzaCIsImlkIjoiMTIzZTQ1NjctZTg5Yi0xMmQzLWE0NTYtNDI2NjE0MTc0MDAwIn1dfQ.6XF7wRF4lLOvKr5Gd3MHv9j7U151hcBjmqSyk6nI6JVYUgt6q0YRp2j1aSJcg8VmaejzP1DouN1DpWUT_JTRXA";
OneSignal.updateUserJwt(event.getExternalId(), jwt);
Log.v(Tag.LOG_TAG, "onUserJwtInvalidated fired with ID:" + event.getExternalId());
}
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -411,8 +411,7 @@ private void setupAppLayout() {
@Override
public void onSuccess(String update) {
if (update != null && !update.isEmpty()) {
String jwt = "InitialJWT";
OneSignal.login(update, jwt);
OneSignal.login(update);
refreshState();
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public static boolean exists(Context context, String key) {
}

public static String getOneSignalAppId(Context context) {
return getSharedPreference(context).getString(OS_APP_ID_SHARED_PREF, "0139bd6f-451f-438c-8886-4e0f0fe3a085");
return getSharedPreference(context).getString(OS_APP_ID_SHARED_PREF, "77e32082-ea27-42e3-a898-c72e141824ef");
}

public static boolean getUserPrivacyConsent(Context context) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,6 @@ interface IOneSignal {
*/
val isInitialized: Boolean

/**
* Whether the security feature to authenticate your external user ids is enabled
*/
val useIdentityVerification: Boolean

/**
* The user manager for accessing user-scoped
* management.
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.onesignal

/** TODO: complete the comment part for this listener
/**
* Implement this interface and provide an instance to [OneSignal.addUserJwtInvalidatedListner]
* in order to receive control when the JWT for the current user is invalidated.
*
* @see [User JWT Invalidated Event | OneSignal Docs](https://documentation.onesignal.com/docs/)
* @see [User JWT Invalidated Event | OneSignal Docs](https://documentation.onesignal.com/docs/identity-verification)
*/
interface IUserJwtInvalidatedListener {
/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,6 @@ object OneSignal {
val isInitialized: Boolean
get() = oneSignal.isInitialized

/**
* Whether the security feature to authenticate your external user ids is enabled
*/
@JvmStatic
val useIdentityVerification: Boolean
get() = oneSignal.useIdentityVerification

/**
* The current SDK version as a string.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
package com.onesignal

/** TODO: jwt documentation
/**
* The event passed into [IUserJwtInvalidatedListener.onUserJwtInvalidated], it provides access
* to the external ID whose JWT has just been invalidated.
*
* For more information https://documentation.onesignal.com/docs/identity-verification#4-handle-jwt-lifecycle-events
*/
class UserJwtInvalidatedEvent(
val externalId: String,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,39 +46,29 @@ internal class HttpClient(
url: String,
body: JSONObject,
headers: OptionalHeaders?,
): HttpResponse {
return makeRequest(url, "POST", body, _configModelStore.model.httpTimeout, headers)
}
): HttpResponse = makeRequest(url, "POST", body, _configModelStore.model.httpTimeout, headers)

override suspend fun get(
url: String,
headers: OptionalHeaders?,
): HttpResponse {
return makeRequest(url, null, null, _configModelStore.model.httpGetTimeout, headers)
}
): HttpResponse = makeRequest(url, null, null, _configModelStore.model.httpGetTimeout, headers)

override suspend fun put(
url: String,
body: JSONObject,
headers: OptionalHeaders?,
): HttpResponse {
return makeRequest(url, "PUT", body, _configModelStore.model.httpTimeout, headers)
}
): HttpResponse = makeRequest(url, "PUT", body, _configModelStore.model.httpTimeout, headers)

override suspend fun patch(
url: String,
body: JSONObject,
headers: OptionalHeaders?,
): HttpResponse {
return makeRequest(url, "PATCH", body, _configModelStore.model.httpTimeout, headers)
}
): HttpResponse = makeRequest(url, "PATCH", body, _configModelStore.model.httpTimeout, headers)

override suspend fun delete(
url: String,
headers: OptionalHeaders?,
): HttpResponse {
return makeRequest(url, "DELETE", null, _configModelStore.model.httpTimeout, headers)
}
): HttpResponse = makeRequest(url, "DELETE", null, _configModelStore.model.httpTimeout, headers)

private suspend fun makeRequest(
url: String,
Expand Down Expand Up @@ -151,15 +141,10 @@ internal class HttpClient(
con.setRequestProperty("SDK-Version", "onesignal/android/" + OneSignalUtils.SDK_VERSION)

val jwt = headers?.jwt
if (!jwt.isNullOrEmpty()) {
if (jwt != null) {
con.setRequestProperty("Authorization", "Bearer $jwt")
}

val deviceAuthPushToken = headers?.deviceAuthPushToken
if (_configModelStore.model.useIdentityVerification && !deviceAuthPushToken.isNullOrEmpty()) {
con.setRequestProperty("Device-Auth-Push-Token", "Basic $deviceAuthPushToken")
}

if (OneSignalWrapper.sdkType != null && OneSignalWrapper.sdkVersion != null) {
con.setRequestProperty("SDK-Wrapper", "onesignal/${OneSignalWrapper.sdkType}/${OneSignalWrapper.sdkVersion}")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@ internal class HttpConnectionFactory(
) : IHttpConnectionFactory {
@Throws(IOException::class)
override fun newHttpURLConnection(url: String): HttpURLConnection {
return URL("https://staging.onesignal.com/api/v1/" + url).openConnection() as HttpURLConnection
return URL(_configModelStore.model.apiUrl + url).openConnection() as HttpURLConnection
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,4 @@ data class OptionalHeaders(
*/
val sessionDuration: Long? = null,
val jwt: String? = null,
val deviceAuthPushToken: String? = null,
)
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,6 @@ internal class OperationRepo(
coroutineScope.launch {
// load saved operations first then start processing the queue to ensure correct operation order
loadSavedOperations()
paused = false
processQueueForever()
}
}
Expand Down Expand Up @@ -270,6 +269,8 @@ internal class OperationRepo(
}
ExecutionResult.FAIL_UNAUTHORIZED -> {
Logging.error("Operation execution failed with invalid jwt")
_identityModelStore.invalidateJwt()

// add back all operations to the front of the queue to be re-executed.
synchronized(queue) {
ops.reversed().forEach { queue.add(0, it) }
Expand Down Expand Up @@ -359,31 +360,20 @@ internal class OperationRepo(

internal fun getNextOps(bucketFilter: Int): List<OperationQueueItem>? {
return synchronized(queue) {
var startingOp: OperationQueueItem? = null
// Search for the first operation that is qualified to execute
for (queueItem in queue) {
val operation = queueItem.operation

// Ensure the operation is in an executable state
if (!operation.canStartExecute ||
!_newRecordState.canAccess(
operation.applyToRecordId,
) || queueItem.bucket > bucketFilter
) {
continue
}
// Ensure the operation does not have empty JWT if identity verification is on
if (_configModelStore.model.useIdentityVerification &&
_identityModelStore.model.jwtToken != null
) {
null
}

// Ensure the operation does not have empty JWT if identity verification is on
if (_configModelStore.model.useIdentityVerification &&
_identityModelStore.model.jwtToken.isNullOrEmpty()
) {
continue
val startingOp =
queue.firstOrNull {
it.operation.canStartExecute &&
_newRecordState.canAccess(it.operation.applyToRecordId) &&
it.bucket <= bucketFilter
}

startingOp = queueItem
break
}

if (startingOp != null) {
queue.remove(startingOp)
getGroupableOperations(startingOp)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,8 +60,6 @@ import org.json.JSONObject
internal class OneSignalImp : IOneSignal, IServiceProvider {
override val sdkVersion: String = OneSignalUtils.SDK_VERSION
override var isInitialized: Boolean = false
override val useIdentityVerification: Boolean
get() = configModel?.useIdentityVerification ?: true

override var consentRequired: Boolean
get() = configModel?.consentRequired ?: (_consentRequired == true)
Expand Down Expand Up @@ -153,6 +151,8 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
private var _disableGMSMissingPrompt: Boolean? = null
private val initLock: Any = Any()
private val loginLock: Any = Any()
private val useIdentityVerification: Boolean
get() = configModel?.useIdentityVerification ?: true

private val listOfModules =
listOf(
Expand Down Expand Up @@ -313,7 +313,8 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {

createAndSwitchToNewUser(suppressBackendOperation = true)

// ** No longer allowed when identity verification is on
// This operation will be dropped if identity verification is on at the time the operation
// is being processed
operationRepo!!.enqueue(
LoginUserFromSubscriptionOperation(
configModel!!.appId,
Expand Down Expand Up @@ -365,7 +366,6 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
return
}

// TODO: Set JWT Token for all future requests.
createAndSwitchToNewUser(suppressBackendOperation = false) { identityModel, _ ->
identityModel.externalId = externalId
identityModel.jwtToken = jwtBearerToken
Expand All @@ -383,30 +383,16 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
// provide a callback to the caller when we can absolutely say the user is logged
// in, so they may take action on their own backend.

val result =
when (useIdentityVerification) {
true -> {
operationRepo!!.enqueue(
LoginUserOperation(
configModel!!.appId,
identityModelStore!!.model.onesignalId,
identityModelStore!!.model.externalId,
),
)
}
else -> {
operationRepo!!.enqueueAndWait(
LoginUserOperation(
configModel!!.appId,
newIdentityOneSignalId,
externalId,
if (currentIdentityExternalId == null) currentIdentityOneSignalId else null,
),
)
}
}
val operation =
LoginUserOperation(
configModel!!.appId,
identityModelStore?.model?.onesignalId ?: newIdentityOneSignalId,
identityModelStore?.model?.externalId ?: externalId,
if (!useIdentityVerification && currentIdentityExternalId == null) currentIdentityOneSignalId else null,
)

if (result == false) {
val result = operationRepo!!.enqueueAndWait(operation)
if (!result) {
Logging.log(LogLevel.ERROR, "Could not login user")
}
}
Expand Down Expand Up @@ -492,7 +478,7 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
modify(identityModel, propertiesModel)
}

if (!identityModel.jwtToken.isNullOrEmpty()) {
if (identityModel.jwtToken != null) {
setupNewSubscription(identityModel, propertiesModel, suppressBackendOperation, sdkId)
}

Expand Down Expand Up @@ -542,13 +528,8 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {

if (suppressBackendOperation) {
subscriptionModelStore!!.replaceAll(subscriptions, ModelChangeTags.NO_PROPOGATE)
} else if (currentPushSubscription != null && (
!useIdentityVerification || useIdentityVerification &&
!IDManager.isLocalId(
currentPushSubscription.id,
)
)
) {
} else if (currentPushSubscription != null && (!useIdentityVerification || !IDManager.isLocalId(currentPushSubscription.id))) {
//
operationRepo!!.enqueue(TransferSubscriptionOperation(configModel!!.appId, currentPushSubscription.id, sdkId))
subscriptionModelStore!!.replaceAll(subscriptions, ModelChangeTags.NO_PROPOGATE)
} else {
Expand All @@ -560,18 +541,19 @@ internal class OneSignalImp : IOneSignal, IServiceProvider {
configModel.addFetchParamsObserver(
object : FetchParamsObserver {
override fun onParamsFetched(params: ParamsObject) {
// resume operations if identity verification is turned off or a jwt is cached
if (params.useIdentityVerification == false || identityModelStore!!.model.jwtToken != null) {
operationRepo!!.enqueue(
LoginUserOperation(
configModel!!.appId,
identityModelStore!!.model.onesignalId,
identityModelStore!!.model.externalId,
),
)
} else {
Logging.log(LogLevel.ERROR, "A valid JWT is required for user ${identityModelStore!!.model.externalId}.")
if (params.useIdentityVerification == true && identityModelStore.model.jwtToken == null) {
Logging.log(LogLevel.INFO, "A valid JWT is required for user ${identityModelStore!!.model.externalId}.")
return
}

// resume operations either identity verification is turned off or a jwt is cached
operationRepo!!.enqueue(
LoginUserOperation(
configModel!!.appId,
identityModelStore!!.model.onesignalId,
identityModelStore!!.model.externalId,
),
)
}
},
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.onesignal.user.internal

import com.onesignal.IUserJwtInvalidatedListener
import com.onesignal.OneSignal
import com.onesignal.UserJwtInvalidatedEvent
import com.onesignal.common.IDManager
import com.onesignal.common.OneSignalUtils
Expand Down Expand Up @@ -252,12 +251,12 @@ internal open class UserManager(
}

override fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
Logging.debug("OneSignal.addClickListener(listener: $listener)")
Logging.debug("OneSignal.addUserJwtInvalidatedListener(listener: $listener)")
jwtInvalidatedCallback.subscribe(listener)
}

override fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) {
Logging.debug("OneSignal.removeClickListener(listener: $listener)")
Logging.debug("OneSignal.removeUserJwtInvalidatedListener(listener: $listener)")
jwtInvalidatedCallback.unsubscribe(listener)
}

Expand All @@ -282,8 +281,9 @@ internal open class UserManager(
val oldJwt = args.oldValue.toString()
val newJwt = args.newValue.toString()

// prevent same JWT from being invalidated twice in a row
if (OneSignal.useIdentityVerification && jwtTokenInvalidated != oldJwt && newJwt.isEmpty()) {
// When newJwt is equals to null, we are invalidating JWT for the current user.
// We need to prevent same JWT from being invalidated twice in a row.
if (jwtTokenInvalidated != oldJwt && newJwt == null) {
jwtInvalidatedCallback.fire {
it.onUserJwtInvalidated(UserJwtInvalidatedEvent(externalId))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,5 @@ interface ISubscriptionBackendService {
suspend fun getIdentityFromSubscription(
appId: String,
subscriptionId: String,
jwt: String? = null,
): Map<String, String>
}
Loading

0 comments on commit 8e9ae06

Please sign in to comment.