From a3b34e63de7cc582ec86c4e6861861743ae1dd53 Mon Sep 17 00:00:00 2001 From: jinliu9508 Date: Mon, 12 Aug 2024 21:36:48 -0400 Subject: [PATCH 01/13] Infrastructure change around JWT: models, model stores --- .../onesignal/sdktest/util/ProfileUtil.java | 7 +++++++ .../core/internal/config/ConfigModel.kt | 20 +++++++++++++++++++ .../config/impl/ConfigModelStoreListener.kt | 3 ++- .../user/internal/identity/IdentityModel.kt | 11 ++++++++++ .../internal/identity/IdentityModelStore.kt | 6 +++++- 5 files changed, 45 insertions(+), 2 deletions(-) diff --git a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/util/ProfileUtil.java b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/util/ProfileUtil.java index 9a026c5f47..c48edd4d4f 100644 --- a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/util/ProfileUtil.java +++ b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/util/ProfileUtil.java @@ -13,6 +13,7 @@ public enum FieldType { ALIAS("Alias"), EMAIL("Email"), SMS("SMS"), + JWT("JWT"), EXTERNAL_USER_ID("External User Id"), TAG("Tags"), @@ -97,6 +98,10 @@ public static boolean isSMSValid(TextInputLayout smsTextInputLayout) { return true; } + private static boolean isJWTValid(TextInputLayout jwtTextInputLayout) { + return !jwtTextInputLayout.getEditText().toString().isEmpty(); + } + private static boolean isExternalUserIdValid(TextInputLayout externalUserIdTextInputLayout) { externalUserIdTextInputLayout.setErrorEnabled(false); if (externalUserIdTextInputLayout.getEditText() != null) { @@ -137,6 +142,8 @@ static boolean isContentValid(FieldType field, TextInputLayout alertDialogTextIn return isEmailValid(alertDialogTextInputLayout); case SMS: return isSMSValid(alertDialogTextInputLayout); + case JWT: + return isJWTValid(alertDialogTextInputLayout); case EXTERNAL_USER_ID: return isExternalUserIdValid(alertDialogTextInputLayout); case TAG: diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt index 74d31c4669..0e25c480c3 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/ConfigModel.kt @@ -1,6 +1,8 @@ package com.onesignal.core.internal.config +import com.onesignal.common.events.EventProducer import com.onesignal.common.modeling.Model +import com.onesignal.core.internal.backend.ParamsObject import org.json.JSONArray import org.json.JSONObject @@ -319,6 +321,20 @@ class ConfigModel : Model() { return null } + + var fetchParamsNotifier = EventProducer() + + fun addFetchParamsObserver(observer: FetchParamsObserver) { + fetchParamsNotifier.subscribe(observer) + } + + fun removeFetchParamsObserver(observer: FetchParamsObserver) { + fetchParamsNotifier.unsubscribe(observer) + } + + fun notifyFetchParams(params: ParamsObject) { + fetchParamsNotifier.fire { it.onParamsFetched(params) } + } } /** @@ -425,3 +441,7 @@ class FCMConfigModel(parentModel: Model, parentProperty: String) : Model(parentM setOptStringProperty(::apiKey.name, value) } } + +interface FetchParamsObserver { + fun onParamsFetched(params: ParamsObject) +} diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/ConfigModelStoreListener.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/ConfigModelStoreListener.kt index 87d7eae6b0..8622e05c90 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/ConfigModelStoreListener.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/config/impl/ConfigModelStoreListener.kt @@ -72,7 +72,7 @@ internal class ConfigModelStoreListener( // copy current model into new model, then override with what comes down. val config = ConfigModel() config.initializeFromModel(null, _configModelStore.model) - + config.fetchParamsNotifier = _configModelStore.model.fetchParamsNotifier config.isInitializedWithRemote = true // these are always copied from the backend params @@ -105,6 +105,7 @@ internal class ConfigModelStoreListener( _configModelStore.replace(config, ModelChangeTags.HYDRATE) success = true + config.notifyFetchParams(params) } catch (ex: BackendException) { if (ex.statusCode == HttpURLConnection.HTTP_FORBIDDEN) { Logging.fatal("403 error getting OneSignal params, omitting further retries!") diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModel.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModel.kt index d443fdad60..70a5015f2e 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModel.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModel.kt @@ -1,6 +1,7 @@ package com.onesignal.user.internal.identity import com.onesignal.common.modeling.MapModel +import com.onesignal.common.modeling.ModelChangeTags import com.onesignal.user.internal.backend.IdentityConstants /** @@ -29,4 +30,14 @@ class IdentityModel : MapModel() { set(value) { setOptStringProperty(IdentityConstants.EXTERNAL_ID, value) } + + /** + * A JWT token generated on your server and given to a OneSignal Client SDK so it can manage + * a specific User, their Subscriptions, and Identities (AKA add/remove Aliases). + */ + var jwtToken: String? + get() = getOptStringProperty(IdentityConstants.JWT_TOKEN) + set(value) { + setOptStringProperty(IdentityConstants.JWT_TOKEN, value, ModelChangeTags.NO_PROPOGATE) + } } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModelStore.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModelStore.kt index 911c4ba71b..6e761bbd4d 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModelStore.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModelStore.kt @@ -6,4 +6,8 @@ import com.onesignal.core.internal.preferences.IPreferencesService open class IdentityModelStore(prefs: IPreferencesService) : SingletonModelStore( SimpleModelStore({ IdentityModel() }, "identity", prefs), -) +) { + fun invalidateJwt() { + model.jwtToken = "" + } +} From c41a852a014d069c865a697f0308536a25cf62d1 Mon Sep 17 00:00:00 2001 From: jinliu9508 Date: Mon, 12 Aug 2024 21:40:46 -0400 Subject: [PATCH 02/13] Public API impl for JWT, User Manager, and callbacks --- .../src/main/java/com/onesignal/IOneSignal.kt | 17 ++++++ .../onesignal/IUserJwtInvalidatedListener.kt | 16 ++++++ .../src/main/java/com/onesignal/OneSignal.kt | 25 ++++++++ .../com/onesignal/UserJwtInvalidatedEvent.kt | 10 ++++ .../com/onesignal/internal/OneSignalImp.kt | 57 ++++++++++++++++++- .../java/com/onesignal/user/IUserManager.kt | 8 +++ .../onesignal/user/internal/UserManager.kt | 41 +++++++++++-- 7 files changed, 168 insertions(+), 6 deletions(-) create mode 100644 OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IUserJwtInvalidatedListener.kt create mode 100644 OneSignalSDK/onesignal/core/src/main/java/com/onesignal/UserJwtInvalidatedEvent.kt diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IOneSignal.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IOneSignal.kt index cb707e4fe4..ce60849a6d 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IOneSignal.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IOneSignal.kt @@ -19,6 +19,11 @@ 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. @@ -123,4 +128,16 @@ interface IOneSignal { * data is not cleared. */ fun logout() + + /** + * Update JWT token for a user + */ + fun updateUserJwt( + externalId: String, + token: String, + ) + + fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) + + fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IUserJwtInvalidatedListener.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IUserJwtInvalidatedListener.kt new file mode 100644 index 0000000000..7abdf10849 --- /dev/null +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/IUserJwtInvalidatedListener.kt @@ -0,0 +1,16 @@ +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/) + */ +interface IUserJwtInvalidatedListener { + /** + * Called when the JWT is invalidated + * + * @param event The user JWT that expired. + */ + fun onUserJwtInvalidated(event: UserJwtInvalidatedEvent) +} diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt index 580cd63252..799c50c9c9 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/OneSignal.kt @@ -29,6 +29,13 @@ 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. */ @@ -192,6 +199,24 @@ object OneSignal { @JvmStatic fun logout() = oneSignal.logout() + @JvmStatic + fun updateUserJwt( + externalId: String, + token: String, + ) { + oneSignal.updateUserJwt(externalId, token) + } + + @JvmStatic + fun addUserJwtInvalidatedListner(listener: IUserJwtInvalidatedListener) { + oneSignal.addUserJwtInvalidatedListener(listener) + } + + @JvmStatic + fun removeUserJwtInvalidatedListner(listener: IUserJwtInvalidatedListener) { + oneSignal.removeUserJwtInvalidatedListener(listener) + } + private val oneSignal: IOneSignal by lazy { OneSignalImp() } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/UserJwtInvalidatedEvent.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/UserJwtInvalidatedEvent.kt new file mode 100644 index 0000000000..986d2eb5c9 --- /dev/null +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/UserJwtInvalidatedEvent.kt @@ -0,0 +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. + * + */ +class UserJwtInvalidatedEvent( + val externalId: String, +) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt index 7babf5067b..22b3a31238 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/internal/OneSignalImp.kt @@ -3,6 +3,7 @@ package com.onesignal.internal import android.content.Context import android.os.Build import com.onesignal.IOneSignal +import com.onesignal.IUserJwtInvalidatedListener import com.onesignal.common.AndroidUtils import com.onesignal.common.DeviceUtils import com.onesignal.common.IDManager @@ -18,8 +19,10 @@ import com.onesignal.common.threading.suspendifyOnThread import com.onesignal.core.CoreModule import com.onesignal.core.internal.application.IApplicationService import com.onesignal.core.internal.application.impl.ApplicationService +import com.onesignal.core.internal.backend.ParamsObject import com.onesignal.core.internal.config.ConfigModel import com.onesignal.core.internal.config.ConfigModelStore +import com.onesignal.core.internal.config.FetchParamsObserver import com.onesignal.core.internal.operations.IOperationRepo import com.onesignal.core.internal.preferences.IPreferencesService import com.onesignal.core.internal.preferences.PreferenceOneSignalKeys @@ -56,6 +59,8 @@ 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) @@ -247,6 +252,7 @@ internal class OneSignalImp : IOneSignal, IServiceProvider { // bootstrap services startupService.bootstrap() + resumeOperationRepoAfterFetchParams(configModel!!) if (forceCreateUser || !identityModelStore!!.model.hasProperty(IdentityConstants.ONESIGNAL_ID)) { val legacyPlayerId = preferencesService!!.getString( @@ -284,7 +290,8 @@ internal class OneSignalImp : IOneSignal, IServiceProvider { pushSubscriptionModel.id = legacyPlayerId pushSubscriptionModel.type = SubscriptionType.PUSH pushSubscriptionModel.optedIn = - notificationTypes != SubscriptionStatus.NO_PERMISSION.value && notificationTypes != SubscriptionStatus.UNSUBSCRIBE.value + notificationTypes != SubscriptionStatus.NO_PERMISSION.value && + notificationTypes != SubscriptionStatus.UNSUBSCRIBE.value pushSubscriptionModel.address = legacyUserSyncJSON.safeString("identifier") ?: "" if (notificationTypes != null) { @@ -357,12 +364,16 @@ internal class OneSignalImp : IOneSignal, IServiceProvider { currentIdentityOneSignalId = identityModelStore!!.model.onesignalId if (currentIdentityExternalId == externalId) { + // login is for same user that is already logged in, fetch (refresh) + // the current user. + identityModelStore!!.model.jwtToken = jwtBearerToken return } // TODO: Set JWT Token for all future requests. createAndSwitchToNewUser { identityModel, _ -> identityModel.externalId = externalId + identityModel.jwtToken = jwtBearerToken } newIdentityOneSignalId = identityModelStore!!.model.onesignalId @@ -405,6 +416,7 @@ internal class OneSignalImp : IOneSignal, IServiceProvider { return } + // calling createAndSwitchToNewUser() replaces model with a default empty jwt createAndSwitchToNewUser() operationRepo!!.enqueue( LoginUserOperation( @@ -413,9 +425,33 @@ internal class OneSignalImp : IOneSignal, IServiceProvider { identityModelStore!!.model.externalId, ), ) + } + } - // TODO: remove JWT Token for all future requests. + override fun updateUserJwt( + externalId: String, + token: String, + ) { + // update the model with the given externalId + for (model in identityModelStore!!.store.list()) { + if (externalId == model.externalId) { + identityModelStore!!.model.jwtToken = token + operationRepo!!.setPaused(false) + operationRepo!!.forceExecuteOperations() + Logging.log(LogLevel.DEBUG, "JWT $token is updated for externalId $externalId") + return + } } + + Logging.log(LogLevel.DEBUG, "No identity found for externalId $externalId") + } + + override fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) { + user.addUserJwtInvalidatedListener(listener) + } + + override fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) { + user.removeUserJwtInvalidatedListener(listener) } private fun createAndSwitchToNewUser( @@ -483,6 +519,23 @@ internal class OneSignalImp : IOneSignal, IServiceProvider { } } + private fun resumeOperationRepoAfterFetchParams(configModel: ConfigModel) { + // pause operation repo until useIdentityVerification is determined + operationRepo!!.setPaused(true) + 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!!.setPaused(false) + } else { + Logging.log(LogLevel.ERROR, "A valid JWT is required for user ${identityModelStore!!.model.externalId}.") + } + } + }, + ) + } + override fun hasService(c: Class): Boolean = services.hasService(c) override fun getService(c: Class): T = services.getService(c) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/IUserManager.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/IUserManager.kt index 2b71ca11de..d2076acc91 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/IUserManager.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/IUserManager.kt @@ -1,5 +1,6 @@ package com.onesignal.user +import com.onesignal.IUserJwtInvalidatedListener import com.onesignal.OneSignal import com.onesignal.user.state.IUserStateObserver import com.onesignal.user.subscriptions.IPushSubscription @@ -166,4 +167,11 @@ interface IUserManager { * Remove an observer from the user state. */ fun removeObserver(observer: IUserStateObserver) + + /** + * Add an event listener allowing user to be notified when the JWT is invalidated. + */ + fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) + + fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserManager.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserManager.kt index 934d233183..a9b2084546 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserManager.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/UserManager.kt @@ -1,5 +1,8 @@ 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 import com.onesignal.common.events.EventProducer @@ -41,6 +44,10 @@ internal open class UserManager( val changeHandlersNotifier = EventProducer() + val jwtInvalidatedCallback = EventProducer() + + private var jwtTokenInvalidated: String? = null + override val pushSubscription: IPushSubscription get() = _subscriptionManager.subscriptions.push @@ -244,6 +251,16 @@ internal open class UserManager( changeHandlersNotifier.unsubscribe(observer) } + override fun addUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) { + Logging.debug("OneSignal.addClickListener(listener: $listener)") + jwtInvalidatedCallback.subscribe(listener) + } + + override fun removeUserJwtInvalidatedListener(listener: IUserJwtInvalidatedListener) { + Logging.debug("OneSignal.removeClickListener(listener: $listener)") + jwtInvalidatedCallback.unsubscribe(listener) + } + override fun onModelReplaced( model: IdentityModel, tag: String, @@ -253,10 +270,26 @@ internal open class UserManager( args: ModelChangedArgs, tag: String, ) { - if (args.property == IdentityConstants.ONESIGNAL_ID) { - val newUserState = UserState(args.newValue.toString(), externalId) - this.changeHandlersNotifier.fire { - it.onUserStateChange(UserChangedState(newUserState)) + when (args.property) { + IdentityConstants.ONESIGNAL_ID -> { + val newUserState = UserState(args.newValue.toString(), externalId) + this.changeHandlersNotifier.fire { + it.onUserStateChange(UserChangedState(newUserState)) + } + } + IdentityConstants.JWT_TOKEN -> { + // Fire the event when the JWT has been invalidated. + 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()) { + jwtInvalidatedCallback.fire { + it.onUserJwtInvalidated(UserJwtInvalidatedEvent((externalId))) + } + } + + jwtTokenInvalidated = oldJwt } } } From 23825c1db5f39a8e7dfbd2b25008c3786b14532e Mon Sep 17 00:00:00 2001 From: jinliu9508 Date: Mon, 12 Aug 2024 21:43:46 -0400 Subject: [PATCH 03/13] Add JWT to HttpClient, backend services, and operations fixup --- .../core/internal/http/impl/HttpClient.kt | 24 ++++++++--- .../internal/http/impl/OptionalHeaders.kt | 2 + .../backend/IIdentityBackendService.kt | 7 ++++ .../backend/ISubscriptionBackendService.kt | 4 ++ .../internal/backend/IUserBackendService.kt | 3 ++ .../backend/impl/IdentityBackendService.kt | 6 ++- .../impl/SubscriptionBackendService.kt | 35 +++++++++++++--- .../backend/impl/UserBackendService.kt | 9 ++-- .../internal/identity/IdentityModelStore.kt | 10 +++++ .../operations/UpdateSubscriptionOperation.kt | 14 ++++++- .../executors/RefreshUserOperationExecutor.kt | 17 +++++--- .../SubscriptionOperationExecutor.kt | 13 +++++- .../internal/InAppMessagesManager.kt | 12 +++++- .../internal/backend/IInAppBackendService.kt | 4 ++ .../backend/impl/InAppBackendService.kt | 42 ++++++++++++------- 15 files changed, 162 insertions(+), 40 deletions(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt index f74a76bc2b..2f43e704a7 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/HttpClient.kt @@ -46,29 +46,39 @@ internal class HttpClient( url: String, body: JSONObject, headers: OptionalHeaders?, - ): HttpResponse = makeRequest(url, "POST", body, _configModelStore.model.httpTimeout, headers) + ): HttpResponse { + return makeRequest(url, "POST", body, _configModelStore.model.httpTimeout, headers) + } override suspend fun get( url: String, headers: OptionalHeaders?, - ): HttpResponse = makeRequest(url, null, null, _configModelStore.model.httpGetTimeout, headers) + ): HttpResponse { + return makeRequest(url, null, null, _configModelStore.model.httpGetTimeout, headers) + } override suspend fun put( url: String, body: JSONObject, headers: OptionalHeaders?, - ): HttpResponse = makeRequest(url, "PUT", body, _configModelStore.model.httpTimeout, headers) + ): HttpResponse { + return makeRequest(url, "PUT", body, _configModelStore.model.httpTimeout, headers) + } override suspend fun patch( url: String, body: JSONObject, headers: OptionalHeaders?, - ): HttpResponse = makeRequest(url, "PATCH", body, _configModelStore.model.httpTimeout, headers) + ): HttpResponse { + return makeRequest(url, "PATCH", body, _configModelStore.model.httpTimeout, headers) + } override suspend fun delete( url: String, headers: OptionalHeaders?, - ): HttpResponse = makeRequest(url, "DELETE", null, _configModelStore.model.httpTimeout, headers) + ): HttpResponse { + return makeRequest(url, "DELETE", null, _configModelStore.model.httpTimeout, headers) + } private suspend fun makeRequest( url: String, @@ -137,6 +147,10 @@ internal class HttpClient( con.readTimeout = timeout con.setRequestProperty("SDK-Version", "onesignal/android/" + OneSignalUtils.SDK_VERSION) + if (headers != null && !headers.jwt.isNullOrEmpty()) { + con.setRequestProperty("Authorization", "Bearer ${headers.jwt}") + } + if (OneSignalWrapper.sdkType != null && OneSignalWrapper.sdkVersion != null) { con.setRequestProperty("SDK-Wrapper", "onesignal/${OneSignalWrapper.sdkType}/${OneSignalWrapper.sdkVersion}") } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/OptionalHeaders.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/OptionalHeaders.kt index f566fd04fc..2e1eb8ea04 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/OptionalHeaders.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/http/impl/OptionalHeaders.kt @@ -17,4 +17,6 @@ data class OptionalHeaders( * Used to track delay between session start and request */ val sessionDuration: Long? = null, + val jwt: String? = null, + val deviceAuthPushToken: String? = null, ) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IIdentityBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IIdentityBackendService.kt index 880599871d..28308225d8 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IIdentityBackendService.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IIdentityBackendService.kt @@ -18,6 +18,7 @@ interface IIdentityBackendService { aliasLabel: String, aliasValue: String, identities: Map, + jwt: String? = null, ): Map /** @@ -35,6 +36,7 @@ interface IIdentityBackendService { aliasLabel: String, aliasValue: String, aliasLabelToDelete: String, + jwt: String? = null, ) } @@ -48,4 +50,9 @@ internal object IdentityConstants { * The alias label for the internal onesignal ID alias. */ const val ONESIGNAL_ID = "onesignal_id" + + /** + * The alias label for the jwt token. + */ + const val JWT_TOKEN = "jwt_token" } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/ISubscriptionBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/ISubscriptionBackendService.kt index 133f298e75..b34988c9a6 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/ISubscriptionBackendService.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/ISubscriptionBackendService.kt @@ -21,6 +21,7 @@ interface ISubscriptionBackendService { aliasLabel: String, aliasValue: String, subscription: SubscriptionObject, + jwt: String? = null, ): Pair? /** @@ -45,6 +46,7 @@ interface ISubscriptionBackendService { suspend fun deleteSubscription( appId: String, subscriptionId: String, + jwt: String? = null, ) /** @@ -60,6 +62,7 @@ interface ISubscriptionBackendService { subscriptionId: String, aliasLabel: String, aliasValue: String, + jwt: String? = null, ) /** @@ -73,5 +76,6 @@ interface ISubscriptionBackendService { suspend fun getIdentityFromSubscription( appId: String, subscriptionId: String, + jwt: String? = null, ): Map } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IUserBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IUserBackendService.kt index 62837fb260..be563bba06 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IUserBackendService.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/IUserBackendService.kt @@ -23,6 +23,7 @@ interface IUserBackendService { identities: Map, subscriptions: List, properties: Map, + jwt: String? = null, ): CreateUserResponse // TODO: Change to send only the push subscription, optimally @@ -47,6 +48,7 @@ interface IUserBackendService { properties: PropertiesObject, refreshDeviceMetadata: Boolean, propertyiesDelta: PropertiesDeltasObject, + jwt: String? = null, ): String? /** @@ -64,6 +66,7 @@ interface IUserBackendService { appId: String, aliasLabel: String, aliasValue: String, + jwt: String? = null, ): CreateUserResponse } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/IdentityBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/IdentityBackendService.kt index adfff7bdc9..9addf41eb2 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/IdentityBackendService.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/IdentityBackendService.kt @@ -15,12 +15,13 @@ internal class IdentityBackendService( aliasLabel: String, aliasValue: String, identities: Map, + jwt: String?, ): Map { val requestJSONObject = JSONObject() .put("identity", JSONObject().putMap(identities)) - val response = _httpClient.patch("apps/$appId/users/by/$aliasLabel/$aliasValue/identity", requestJSONObject) + val response = _httpClient.patch("apps/$appId/users/by/$aliasLabel/$aliasValue/identity", requestJSONObject, jwt) if (!response.isSuccess) { throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds) @@ -36,8 +37,9 @@ internal class IdentityBackendService( aliasLabel: String, aliasValue: String, aliasLabelToDelete: String, + jwt: String?, ) { - val response = _httpClient.delete("apps/$appId/users/by/$aliasLabel/$aliasValue/identity/$aliasLabelToDelete") + val response = _httpClient.delete("apps/$appId/users/by/$aliasLabel/$aliasValue/identity/$aliasLabelToDelete", jwt) if (!response.isSuccess) { throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/SubscriptionBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/SubscriptionBackendService.kt index 25d0e0a3e8..cc9b07ce60 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/SubscriptionBackendService.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/SubscriptionBackendService.kt @@ -1,5 +1,6 @@ package com.onesignal.user.internal.backend.impl +import android.util.Base64 import com.onesignal.common.exceptions.BackendException import com.onesignal.common.safeJSONObject import com.onesignal.common.toMap @@ -16,12 +17,23 @@ internal class SubscriptionBackendService( aliasLabel: String, aliasValue: String, subscription: SubscriptionObject, + jwt: String?, ): Pair? { val jsonSubscription = JSONConverter.convertToJSON(subscription) jsonSubscription.remove("id") val requestJSON = JSONObject().put("subscription", jsonSubscription) - val response = _httpClient.post("apps/$appId/users/by/$aliasLabel/$aliasValue/subscriptions", requestJSON) + val base64Token = + Base64.encodeToString( + subscription.token?.toByteArray(charset("UTF-8")), + Base64.NO_WRAP, + ) + val response = + _httpClient.post( + "apps/$appId/users/by/$aliasLabel/$aliasValue/subscriptions", + requestJSON, + OptionalHeaders(jwt = jwt, deviceAuthPushToken = base64Token), + ) if (!response.isSuccess) { throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds) @@ -50,7 +62,17 @@ internal class SubscriptionBackendService( JSONObject() .put("subscription", JSONConverter.convertToJSON(subscription)) - val response = _httpClient.patch("apps/$appId/subscriptions/$subscriptionId", requestJSON) + val base64Token = + Base64.encodeToString( + subscription.token?.toByteArray(charset("UTF-8")), + Base64.NO_WRAP, + ) + val response = + _httpClient.patch( + "apps/$appId/subscriptions/$subscriptionId", + requestJSON, + OptionalHeaders(deviceAuthPushToken = base64Token), + ) if (!response.isSuccess) { throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds) @@ -67,8 +89,9 @@ internal class SubscriptionBackendService( override suspend fun deleteSubscription( appId: String, subscriptionId: String, + jwt: String?, ) { - val response = _httpClient.delete("apps/$appId/subscriptions/$subscriptionId") + val response = _httpClient.delete("apps/$appId/subscriptions/$subscriptionId", jwt) if (!response.isSuccess) { throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds) @@ -80,12 +103,13 @@ internal class SubscriptionBackendService( subscriptionId: String, aliasLabel: String, aliasValue: String, + jwt: String?, ) { val requestJSON = JSONObject() .put("identity", JSONObject().put(aliasLabel, aliasValue)) - val response = _httpClient.patch("apps/$appId/subscriptions/$subscriptionId/owner", requestJSON) + val response = _httpClient.patch("apps/$appId/subscriptions/$subscriptionId/owner", requestJSON, jwt) if (!response.isSuccess) { throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds) @@ -95,8 +119,9 @@ internal class SubscriptionBackendService( override suspend fun getIdentityFromSubscription( appId: String, subscriptionId: String, + jwt: String?, ): Map { - val response = _httpClient.get("apps/$appId/subscriptions/$subscriptionId/user/identity") + val response = _httpClient.get("apps/$appId/subscriptions/$subscriptionId/user/identity", jwt) if (!response.isSuccess) { throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/UserBackendService.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/UserBackendService.kt index 996f637e73..510e3a4177 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/UserBackendService.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/backend/impl/UserBackendService.kt @@ -18,6 +18,7 @@ internal class UserBackendService( identities: Map, subscriptions: List, properties: Map, + jwt: String?, ): CreateUserResponse { val requestJSON = JSONObject() @@ -36,7 +37,7 @@ internal class UserBackendService( requestJSON.put("refresh_device_metadata", true) - val response = _httpClient.post("apps/$appId/users", requestJSON) + val response = _httpClient.post("apps/$appId/users", requestJSON, jwt) if (!response.isSuccess) { throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds) @@ -52,6 +53,7 @@ internal class UserBackendService( properties: PropertiesObject, refreshDeviceMetadata: Boolean, propertyiesDelta: PropertiesDeltasObject, + jwt: String?, ): String? { val jsonObject = JSONObject() @@ -65,7 +67,7 @@ internal class UserBackendService( jsonObject.put("deltas", JSONConverter.convertToJSON(propertyiesDelta)) } - val response = _httpClient.patch("apps/$appId/users/by/$aliasLabel/$aliasValue", jsonObject) + val response = _httpClient.patch("apps/$appId/users/by/$aliasLabel/$aliasValue", jsonObject, jwt) if (!response.isSuccess) { throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds) @@ -83,8 +85,9 @@ internal class UserBackendService( appId: String, aliasLabel: String, aliasValue: String, + jwt: String?, ): CreateUserResponse { - val response = _httpClient.get("apps/$appId/users/by/$aliasLabel/$aliasValue") + val response = _httpClient.get("apps/$appId/users/by/$aliasLabel/$aliasValue", jwt) if (!response.isSuccess) { throw BackendException(response.statusCode, response.payload, response.retryAfterSeconds) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModelStore.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModelStore.kt index 6e761bbd4d..f7e5270ae4 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModelStore.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/identity/IdentityModelStore.kt @@ -3,6 +3,7 @@ package com.onesignal.user.internal.identity import com.onesignal.common.modeling.SimpleModelStore import com.onesignal.common.modeling.SingletonModelStore import com.onesignal.core.internal.preferences.IPreferencesService +import com.onesignal.user.internal.backend.IdentityConstants open class IdentityModelStore(prefs: IPreferencesService) : SingletonModelStore( SimpleModelStore({ IdentityModel() }, "identity", prefs), @@ -10,4 +11,13 @@ open class IdentityModelStore(prefs: IPreferencesService) : SingletonModelStore< fun invalidateJwt() { model.jwtToken = "" } + + // Use externalId instead of onesignalId when a jwt is present + fun getIdentityAlias(): Pair { + if (model.jwtToken.isNullOrEmpty()) { + return Pair(IdentityConstants.ONESIGNAL_ID, model.onesignalId) + } + + return Pair(IdentityConstants.EXTERNAL_ID, model.externalId!!) + } } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/UpdateSubscriptionOperation.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/UpdateSubscriptionOperation.kt index 59a9e1c1b7..f79706c1ff 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/UpdateSubscriptionOperation.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/UpdateSubscriptionOperation.kt @@ -81,13 +81,24 @@ class UpdateSubscriptionOperation() : Operation(SubscriptionOperationExecutor.UP setEnumProperty(::status.name, value) } + /** + * The jwt token used for the operation that updates a subscription. + */ + var jwt: String? + get() = getStringProperty(::jwt.name) + private set(value) { + if (value != null) { + setStringProperty(::jwt.name, value!!) + } + } + override val createComparisonKey: String get() = "$appId.User.$onesignalId" override val modifyComparisonKey: String get() = "$appId.User.$onesignalId.Subscription.$subscriptionId" override val groupComparisonType: GroupComparisonType = GroupComparisonType.ALTER override val canStartExecute: Boolean get() = !IDManager.isLocalId(onesignalId) && !IDManager.isLocalId(onesignalId) override val applyToRecordId: String get() = subscriptionId - constructor(appId: String, onesignalId: String, subscriptionId: String, type: SubscriptionType, enabled: Boolean, address: String, status: SubscriptionStatus) : this() { + constructor(appId: String, onesignalId: String, subscriptionId: String, type: SubscriptionType, enabled: Boolean, address: String, status: SubscriptionStatus, jwt: String? = null) : this() { this.appId = appId this.onesignalId = onesignalId this.subscriptionId = subscriptionId @@ -95,6 +106,7 @@ class UpdateSubscriptionOperation() : Operation(SubscriptionOperationExecutor.UP this.enabled = enabled this.address = address this.status = status + this.jwt = jwt } override fun translateIds(map: Map) { diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt index cec48b05a9..40acaf0bb4 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt @@ -11,7 +11,6 @@ import com.onesignal.core.internal.operations.Operation import com.onesignal.debug.LogLevel import com.onesignal.debug.internal.logging.Logging import com.onesignal.user.internal.backend.IUserBackendService -import com.onesignal.user.internal.backend.IdentityConstants import com.onesignal.user.internal.backend.SubscriptionObjectType import com.onesignal.user.internal.builduser.IRebuildUserService import com.onesignal.user.internal.identity.IdentityModel @@ -54,11 +53,13 @@ internal class RefreshUserOperationExecutor( private suspend fun getUser(op: RefreshUserOperation): ExecutionResponse { try { + val alias = _identityModelStore.getIdentityAlias() val response = _userBackend.getUser( op.appId, - IdentityConstants.ONESIGNAL_ID, - op.onesignalId, + alias.first, + alias.second, + _identityModelStore.model.jwtToken, ) if (op.onesignalId != _identityModelStore.model.onesignalId) { @@ -98,7 +99,9 @@ internal class RefreshUserOperationExecutor( val subscriptionModel = SubscriptionModel() subscriptionModel.id = subscription.id!! subscriptionModel.address = subscription.token ?: "" - subscriptionModel.status = SubscriptionStatus.fromInt(subscription.notificationTypes ?: SubscriptionStatus.SUBSCRIBED.value) ?: SubscriptionStatus.SUBSCRIBED + subscriptionModel.status = SubscriptionStatus.fromInt( + subscription.notificationTypes ?: SubscriptionStatus.SUBSCRIBED.value, + ) ?: SubscriptionStatus.SUBSCRIBED subscriptionModel.type = when (subscription.type!!) { SubscriptionObjectType.EMAIL -> { @@ -147,8 +150,10 @@ internal class RefreshUserOperationExecutor( return when (responseType) { NetworkUtils.ResponseStatusType.RETRYABLE -> ExecutionResponse(ExecutionResult.FAIL_RETRY, retryAfterSeconds = ex.retryAfterSeconds) - NetworkUtils.ResponseStatusType.UNAUTHORIZED -> - ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED, retryAfterSeconds = ex.retryAfterSeconds) + NetworkUtils.ResponseStatusType.UNAUTHORIZED -> { + _identityModelStore.invalidateJwt() + ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED) + } NetworkUtils.ResponseStatusType.MISSING -> { if (ex.statusCode == 404 && _newRecordState.isInMissingRetryWindow(op.onesignalId)) { return ExecutionResponse(ExecutionResult.FAIL_RETRY, retryAfterSeconds = ex.retryAfterSeconds) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt index 1622d55f16..c31cf05ea0 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt @@ -158,7 +158,11 @@ internal class SubscriptionOperationExecutor( if (operations == null) { return ExecutionResponse(ExecutionResult.FAIL_NORETRY) } else { - return ExecutionResponse(ExecutionResult.FAIL_RETRY, operations = operations, retryAfterSeconds = ex.retryAfterSeconds) + return ExecutionResponse( + ExecutionResult.FAIL_RETRY, + operations = operations, + retryAfterSeconds = ex.retryAfterSeconds, + ) } } } @@ -188,7 +192,12 @@ internal class SubscriptionOperationExecutor( AndroidUtils.getAppVersion(_applicationService.appContext), ) - val rywToken = _subscriptionBackend.updateSubscription(lastOperation.appId, lastOperation.subscriptionId, subscription) + val rywToken = + _subscriptionBackend.updateSubscription( + lastOperation.appId, + lastOperation.subscriptionId, + subscription, + ) if (rywToken != null) { _consistencyManager.setRywToken(startingOperation.onesignalId, IamFetchRywTokenKey.SUBSCRIPTION, rywToken) diff --git a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt index 0bf5ff8c77..8540e1c254 100644 --- a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt +++ b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/InAppMessagesManager.kt @@ -42,6 +42,7 @@ import com.onesignal.session.internal.outcomes.IOutcomeEventsController import com.onesignal.session.internal.session.ISessionLifecycleHandler import com.onesignal.session.internal.session.ISessionService import com.onesignal.user.IUserManager +import com.onesignal.user.internal.identity.IdentityModelStore import com.onesignal.user.internal.subscriptions.ISubscriptionChangedHandler import com.onesignal.user.internal.subscriptions.ISubscriptionManager import com.onesignal.user.internal.subscriptions.SubscriptionModel @@ -59,6 +60,7 @@ internal class InAppMessagesManager( private val _influenceManager: IInfluenceManager, private val _configModelStore: ConfigModelStore, private val _userManager: IUserManager, + private val _identityModelStore: IdentityModelStore, private val _subscriptionManager: ISubscriptionManager, private val _outcomeEventsController: IOutcomeEventsController, private val _state: InAppStateService, @@ -278,7 +280,15 @@ internal class InAppMessagesManager( // lambda so that it is updated on each potential retry val sessionDurationProvider = { _time.currentTimeMillis - _sessionService.startTime } - val newMessages = _backend.listInAppMessages(appId, subscriptionId, rywToken, sessionDurationProvider) + val newMessages = + _backend.listInAppMessages( + appId, + subscriptionId, + rywToken, + sessionDurationProvider, + _identityModelStore.model.jwtToken, + _identityModelStore.getIdentityAlias(), + ) if (newMessages != null) { this.messages = newMessages as MutableList diff --git a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/IInAppBackendService.kt b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/IInAppBackendService.kt index 85db9f6896..35b877bafc 100644 --- a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/IInAppBackendService.kt +++ b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/IInAppBackendService.kt @@ -15,6 +15,8 @@ internal interface IInAppBackendService { * @param subscriptionId The specific subscription within the [appId] the IAM will be delivered to. * @param rywToken Used for read your write consistency * @param sessionDurationProvider Lambda to calculate the session duration at the time of the request + * @param jwt The JWT token for the current logged in user. Not used if identity verification is off. + * @param externalId the external ID of current user. Not used if identity verification is off. * * @return The list of IAMs associated to the subscription, or null if the IAMs could not be retrieved. */ @@ -23,6 +25,8 @@ internal interface IInAppBackendService { subscriptionId: String, rywToken: String?, sessionDurationProvider: () -> Long, + jwt: String? = null, + alias: Pair, ): List? /** diff --git a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt index f541f9b321..df7bbc94cb 100644 --- a/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt +++ b/OneSignalSDK/onesignal/in-app-messages/src/main/java/com/onesignal/inAppMessages/internal/backend/impl/InAppBackendService.kt @@ -26,9 +26,16 @@ internal class InAppBackendService( subscriptionId: String, rywToken: String?, sessionDurationProvider: () -> Long, + jwt: String?, + alias: Pair, ): List? { - val baseUrl = "apps/$appId/subscriptions/$subscriptionId/iams" - return attemptFetchWithRetries(baseUrl, rywToken, sessionDurationProvider) + val baseUrl = "apps/$appId/users/by/${alias.first}/${alias.second}/subscriptions/$subscriptionId/iams" + return attemptFetchWithRetries( + baseUrl, + OptionalHeaders(rywToken = rywToken, sessionDuration = sessionDurationProvider(), jwt = jwt), + ) + + return null } override suspend fun getIAMData( @@ -201,8 +208,7 @@ internal class InAppBackendService( private suspend fun attemptFetchWithRetries( baseUrl: String, - rywToken: String?, - sessionDurationProvider: () -> Long, + optionalHeaders: OptionalHeaders, ): List? { var attempts = 0 var retryLimit: Int = 0 // retry limit is remote defined & set dynamically below @@ -211,11 +217,16 @@ internal class InAppBackendService( val retryCount = if (attempts > 0) attempts else null val values = OptionalHeaders( - rywToken = rywToken, - sessionDuration = sessionDurationProvider(), + rywToken = optionalHeaders.rywToken, + sessionDuration = optionalHeaders.sessionDuration, retryCount = retryCount, + jwt = optionalHeaders.jwt, + ) + val response = + _httpClient.get( + baseUrl, + values, ) - val response = _httpClient.get(baseUrl, values) if (response.isSuccess) { val jsonResponse = response.payload?.let { JSONObject(it) } @@ -238,26 +249,27 @@ internal class InAppBackendService( } while (attempts <= retryLimit) // Final attempt without the RYW token if retries fail - return fetchInAppMessagesWithoutRywToken(baseUrl, sessionDurationProvider) + return fetchInAppMessagesWithoutRywToken( + baseUrl, + OptionalHeaders(sessionDuration = optionalHeaders.sessionDuration, jwt = optionalHeaders.jwt), + ) } private suspend fun fetchInAppMessagesWithoutRywToken( url: String, - sessionDurationProvider: () -> Long, + optionalHeaders: OptionalHeaders, ): List? { val response = _httpClient.get( url, - OptionalHeaders( - sessionDuration = sessionDurationProvider(), - ), + optionalHeaders, ) - if (response.isSuccess) { + return if (response.isSuccess) { val jsonResponse = response.payload?.let { JSONObject(it) } - return jsonResponse?.let { hydrateInAppMessages(it) } + jsonResponse?.let { hydrateInAppMessages(it) } } else { - return null + null } } From 86ef1930aebcb40a19cae58022ad0c3a47f53604 Mon Sep 17 00:00:00 2001 From: jinliu9508 Date: Mon, 12 Aug 2024 21:45:33 -0400 Subject: [PATCH 04/13] Add UNAUTHORIZED handling to OperationRepo and operation executors --- .../internal/operations/IOperationRepo.kt | 2 ++ .../internal/operations/impl/OperationRepo.kt | 14 ++++++++++- .../executors/IdentityOperationExecutor.kt | 19 +++++++++----- ...inUserFromSubscriptionOperationExecutor.kt | 5 +++- .../executors/LoginUserOperationExecutor.kt | 10 ++++++-- .../executors/RefreshUserOperationExecutor.kt | 7 +++--- .../SubscriptionOperationExecutor.kt | 15 ++++++++--- .../executors/UpdateUserOperationExecutor.kt | 25 ++++++++++++++----- 8 files changed, 74 insertions(+), 23 deletions(-) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationRepo.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationRepo.kt index d2dceea5c3..3ab6059605 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationRepo.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/IOperationRepo.kt @@ -42,6 +42,8 @@ interface IOperationRepo { suspend fun awaitInitialized() fun forceExecuteOperations() + + fun setPaused(paused: Boolean) } // Extension function so the syntax containsInstanceOf() can be used over diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt index 7ed6336d4e..c033e56099 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/core/internal/operations/impl/OperationRepo.kt @@ -189,6 +189,10 @@ internal class OperationRepo( waiter.wake(LoopWaiterMessage(false)) } + override fun setPaused(paused: Boolean) { + this.paused = paused + } + /** * Waits until a new operation is enqueued, then wait an additional * amount of time afterwards, so operations can be grouped/batched. @@ -262,7 +266,15 @@ internal class OperationRepo( ops.forEach { _operationModelStore.remove(it.operation.id) } ops.forEach { it.waiter?.wake(true) } } - ExecutionResult.FAIL_UNAUTHORIZED, // TODO: Need to provide callback for app to reset JWT. For now, fail with no retry. + ExecutionResult.FAIL_UNAUTHORIZED -> { + Logging.error("Operation execution failed with invalid jwt, pausing the operation repo: $operations") + // keep the failed operation and pause the operation repo from executing + paused = true + // add back all operations to the front of the queue to be re-executed. + synchronized(queue) { + ops.reversed().forEach { queue.add(0, it) } + } + } ExecutionResult.FAIL_NORETRY, ExecutionResult.FAIL_CONFLICT, -> { diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/IdentityOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/IdentityOperationExecutor.kt index 104fe9569f..443b900f7b 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/IdentityOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/IdentityOperationExecutor.kt @@ -45,11 +45,13 @@ internal class IdentityOperationExecutor( if (lastOperation is SetAliasOperation) { try { + val identityAlias = _identityModelStore.getIdentityAlias() _identityBackend.setAlias( lastOperation.appId, - IdentityConstants.ONESIGNAL_ID, - lastOperation.onesignalId, + identityAlias.first, + identityAlias.second, mapOf(lastOperation.label to lastOperation.value), + _identityModelStore.model.jwtToken, ) // ensure the now created alias is in the model as long as the user is still current. @@ -66,8 +68,10 @@ internal class IdentityOperationExecutor( ExecutionResponse(ExecutionResult.FAIL_NORETRY) NetworkUtils.ResponseStatusType.CONFLICT -> ExecutionResponse(ExecutionResult.FAIL_CONFLICT, retryAfterSeconds = ex.retryAfterSeconds) - NetworkUtils.ResponseStatusType.UNAUTHORIZED -> - ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED, retryAfterSeconds = ex.retryAfterSeconds) + NetworkUtils.ResponseStatusType.UNAUTHORIZED -> { + _identityModelStore.invalidateJwt() + return ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED) + } NetworkUtils.ResponseStatusType.MISSING -> { if (ex.statusCode == 404 && _newRecordState.isInMissingRetryWindow(lastOperation.onesignalId)) { return ExecutionResponse(ExecutionResult.FAIL_RETRY, retryAfterSeconds = ex.retryAfterSeconds) @@ -93,6 +97,7 @@ internal class IdentityOperationExecutor( IdentityConstants.ONESIGNAL_ID, lastOperation.onesignalId, lastOperation.label, + _identityModelStore.model.jwtToken, ) // ensure the now deleted alias is not in the model as long as the user is still current. @@ -110,8 +115,10 @@ internal class IdentityOperationExecutor( ExecutionResponse(ExecutionResult.SUCCESS) NetworkUtils.ResponseStatusType.INVALID -> ExecutionResponse(ExecutionResult.FAIL_NORETRY) - NetworkUtils.ResponseStatusType.UNAUTHORIZED -> - ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED, retryAfterSeconds = ex.retryAfterSeconds) + NetworkUtils.ResponseStatusType.UNAUTHORIZED -> { + _identityModelStore.invalidateJwt() + return ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED) + } NetworkUtils.ResponseStatusType.MISSING -> { return if (ex.statusCode == 404 && _newRecordState.isInMissingRetryWindow(lastOperation.onesignalId)) { ExecutionResponse(ExecutionResult.FAIL_RETRY, retryAfterSeconds = ex.retryAfterSeconds) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserFromSubscriptionOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserFromSubscriptionOperationExecutor.kt index 9a1178999d..50e097f87e 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserFromSubscriptionOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserFromSubscriptionOperationExecutor.kt @@ -46,6 +46,7 @@ internal class LoginUserFromSubscriptionOperationExecutor( _subscriptionBackend.getIdentityFromSubscription( loginUserOp.appId, loginUserOp.subscriptionId, + _identityModelStore.model.jwtToken, ) val backendOneSignalId = identities.getOrDefault(IdentityConstants.ONESIGNAL_ID, null) @@ -82,8 +83,10 @@ internal class LoginUserFromSubscriptionOperationExecutor( return when (responseType) { NetworkUtils.ResponseStatusType.RETRYABLE -> ExecutionResponse(ExecutionResult.FAIL_RETRY) - NetworkUtils.ResponseStatusType.UNAUTHORIZED -> + NetworkUtils.ResponseStatusType.UNAUTHORIZED -> { + _identityModelStore.invalidateJwt() ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED) + } else -> ExecutionResponse(ExecutionResult.FAIL_NORETRY) } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt index e96c796513..8845fab3fd 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/LoginUserOperationExecutor.kt @@ -126,6 +126,10 @@ internal class LoginUserOperationExecutor( ) createUser(loginUserOp, operations) } + ExecutionResult.FAIL_UNAUTHORIZED -> { + _identityModelStore.invalidateJwt() + ExecutionResponse(result.result) + } else -> ExecutionResponse(result.result) } } @@ -212,8 +216,10 @@ internal class LoginUserOperationExecutor( return when (responseType) { NetworkUtils.ResponseStatusType.RETRYABLE -> ExecutionResponse(ExecutionResult.FAIL_RETRY, retryAfterSeconds = ex.retryAfterSeconds) - NetworkUtils.ResponseStatusType.UNAUTHORIZED -> - ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED, retryAfterSeconds = ex.retryAfterSeconds) + NetworkUtils.ResponseStatusType.UNAUTHORIZED -> { + _identityModelStore.invalidateJwt() + ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED) + } else -> ExecutionResponse(ExecutionResult.FAIL_PAUSE_OPREPO) } diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt index 40acaf0bb4..5bc703d896 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/RefreshUserOperationExecutor.kt @@ -53,12 +53,12 @@ internal class RefreshUserOperationExecutor( private suspend fun getUser(op: RefreshUserOperation): ExecutionResponse { try { - val alias = _identityModelStore.getIdentityAlias() + val identityAlias = _identityModelStore.getIdentityAlias() val response = _userBackend.getUser( op.appId, - alias.first, - alias.second, + identityAlias.first, + identityAlias.second, _identityModelStore.model.jwtToken, ) @@ -70,6 +70,7 @@ internal class RefreshUserOperationExecutor( for (aliasKVP in response.identities) { identityModel[aliasKVP.key] = aliasKVP.value } + identityModel.jwtToken = _identityModelStore.model.jwtToken val propertiesModel = PropertiesModel() propertiesModel.onesignalId = op.onesignalId diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt index c31cf05ea0..c78b56293c 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/SubscriptionOperationExecutor.kt @@ -25,6 +25,7 @@ import com.onesignal.user.internal.backend.IdentityConstants import com.onesignal.user.internal.backend.SubscriptionObject import com.onesignal.user.internal.backend.SubscriptionObjectType import com.onesignal.user.internal.builduser.IRebuildUserService +import com.onesignal.user.internal.identity.IdentityModelStore import com.onesignal.user.internal.operations.CreateSubscriptionOperation import com.onesignal.user.internal.operations.DeleteSubscriptionOperation import com.onesignal.user.internal.operations.TransferSubscriptionOperation @@ -38,6 +39,7 @@ internal class SubscriptionOperationExecutor( private val _subscriptionBackend: ISubscriptionBackendService, private val _deviceService: IDeviceService, private val _applicationService: IApplicationService, + private val _identityModelStore: IdentityModelStore, private val _subscriptionModelStore: SubscriptionModelStore, private val _configModelStore: ConfigModelStore, private val _buildUserService: IRebuildUserService, @@ -105,12 +107,14 @@ internal class SubscriptionOperationExecutor( AndroidUtils.getAppVersion(_applicationService.appContext), ) + val identityAlias = _identityModelStore.getIdentityAlias() val result = _subscriptionBackend.createSubscription( createOperation.appId, - IdentityConstants.ONESIGNAL_ID, - createOperation.onesignalId, + identityAlias.first, + identityAlias.second, subscription, + _identityModelStore.model.jwtToken, ) ?: return ExecutionResponse(ExecutionResult.SUCCESS) val backendSubscriptionId = result.first @@ -148,8 +152,10 @@ internal class SubscriptionOperationExecutor( NetworkUtils.ResponseStatusType.INVALID, -> ExecutionResponse(ExecutionResult.FAIL_NORETRY) - NetworkUtils.ResponseStatusType.UNAUTHORIZED -> + NetworkUtils.ResponseStatusType.UNAUTHORIZED -> { + _identityModelStore.invalidateJwt() ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED, retryAfterSeconds = ex.retryAfterSeconds) + } NetworkUtils.ResponseStatusType.MISSING -> { if (ex.statusCode == 404 && _newRecordState.isInMissingRetryWindow(createOperation.onesignalId)) { return ExecutionResponse(ExecutionResult.FAIL_RETRY, retryAfterSeconds = ex.retryAfterSeconds) @@ -252,6 +258,7 @@ internal class SubscriptionOperationExecutor( startingOperation.subscriptionId, IdentityConstants.ONESIGNAL_ID, startingOperation.onesignalId, + _identityModelStore.model.jwtToken, ) } catch (ex: BackendException) { val responseType = NetworkUtils.getResponseStatusType(ex.statusCode) @@ -283,7 +290,7 @@ internal class SubscriptionOperationExecutor( private suspend fun deleteSubscription(op: DeleteSubscriptionOperation): ExecutionResponse { try { - _subscriptionBackend.deleteSubscription(op.appId, op.subscriptionId) + _subscriptionBackend.deleteSubscription(op.appId, op.subscriptionId, _identityModelStore.model.jwtToken) // remove the subscription model as a HYDRATE in case for some reason it still exists. _subscriptionModelStore.remove(op.subscriptionId, ModelChangeTags.HYDRATE) diff --git a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt index 0f28ae61d1..7595b67a2a 100644 --- a/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt +++ b/OneSignalSDK/onesignal/core/src/main/java/com/onesignal/user/internal/operations/impl/executors/UpdateUserOperationExecutor.kt @@ -13,7 +13,6 @@ import com.onesignal.core.internal.operations.Operation import com.onesignal.debug.LogLevel import com.onesignal.debug.internal.logging.Logging import com.onesignal.user.internal.backend.IUserBackendService -import com.onesignal.user.internal.backend.IdentityConstants import com.onesignal.user.internal.backend.PropertiesDeltasObject import com.onesignal.user.internal.backend.PropertiesObject import com.onesignal.user.internal.backend.PurchaseObject @@ -88,7 +87,12 @@ internal class UpdateUserOperationExecutor( val sessionCount = if (deltasObject.sessionCount != null) deltasObject.sessionCount!! + 1 else 1 deltasObject = - PropertiesDeltasObject(deltasObject.sessionTime, sessionCount, deltasObject.amountSpent, deltasObject.purchases) + PropertiesDeltasObject( + deltasObject.sessionTime, + sessionCount, + deltasObject.amountSpent, + deltasObject.purchases, + ) refreshDeviceMetadata = true } is TrackSessionEndOperation -> { @@ -107,7 +111,12 @@ internal class UpdateUserOperationExecutor( } deltasObject = - PropertiesDeltasObject(sessionTime, deltasObject.sessionCount, deltasObject.amountSpent, deltasObject.purchases) + PropertiesDeltasObject( + sessionTime, + deltasObject.sessionCount, + deltasObject.amountSpent, + deltasObject.purchases, + ) } is TrackPurchaseOperation -> { if (appId == null) { @@ -138,14 +147,16 @@ internal class UpdateUserOperationExecutor( if (appId != null && onesignalId != null) { try { + val identityAlias = _identityModelStore.getIdentityAlias() val rywToken = _userBackend.updateUser( appId, - IdentityConstants.ONESIGNAL_ID, - onesignalId, + identityAlias.first, + identityAlias.second, propertiesObject, refreshDeviceMetadata, deltasObject, + _identityModelStore.model.jwtToken, ) if (rywToken != null) { @@ -185,8 +196,10 @@ internal class UpdateUserOperationExecutor( return when (responseType) { NetworkUtils.ResponseStatusType.RETRYABLE -> ExecutionResponse(ExecutionResult.FAIL_RETRY, retryAfterSeconds = ex.retryAfterSeconds) - NetworkUtils.ResponseStatusType.UNAUTHORIZED -> + NetworkUtils.ResponseStatusType.UNAUTHORIZED -> { + _identityModelStore.invalidateJwt() ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED, retryAfterSeconds = ex.retryAfterSeconds) + } NetworkUtils.ResponseStatusType.MISSING -> { if (ex.statusCode == 404 && _newRecordState.isInMissingRetryWindow(onesignalId)) { return ExecutionResponse(ExecutionResult.FAIL_RETRY, retryAfterSeconds = ex.retryAfterSeconds) From e75647db83c346df8580328fbf69e3ec9d7ed763 Mon Sep 17 00:00:00 2001 From: jinliu9508 Date: Mon, 12 Aug 2024 21:46:10 -0400 Subject: [PATCH 05/13] Add and adjust some test units for JWT related change --- .../internal/operations/OperationRepoTests.kt | 49 +++++++++++++ .../onesignal/internal/OneSignalImpTests.kt | 14 ++++ .../user/internal/UserManagerTests.kt | 71 ++++++++++++++++++- .../RecoverFromDroppedLoginBugTests.kt | 1 + .../SubscriptionOperationExecutorTests.kt | 32 ++++++++- .../internal/InAppMessagesManagerTests.kt | 2 + .../backend/InAppBackendServiceTests.kt | 36 ++++++++-- 7 files changed, 199 insertions(+), 6 deletions(-) diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/operations/OperationRepoTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/operations/OperationRepoTests.kt index 936e0e5b92..85acc12b93 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/operations/OperationRepoTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/core/internal/operations/OperationRepoTests.kt @@ -34,6 +34,12 @@ import java.util.UUID private class Mocks { val configModelStore = MockHelper.configModelStore() + val identityModelStore = + MockHelper.identityModelStore { + it.jwtToken = "" + it.externalId = "externalId1" + } + val operationModelStore: OperationModelStore = run { val operationStoreList = mutableListOf() @@ -63,6 +69,7 @@ private class Mocks { listOf(executor), operationModelStore, configModelStore, + identityModelStore, Time(), getNewRecordState(configModelStore), ), @@ -685,6 +692,48 @@ class OperationRepoTests : FunSpec({ response2 shouldBe true opRepo.forceExecuteOperations() } + + test("operations that need to be identity verified cannot execute until JWT is provided") { + // Given + val mocks = Mocks() + val waiter = Waiter() + + every { mocks.configModelStore.model.useIdentityVerification } returns true // set identity verification on + every { mocks.identityModelStore.model.jwtToken } returns null // jwt is initially unset + every { mocks.operationModelStore.remove(any()) } answers {} andThenAnswer { waiter.wake() } + + val operation1 = mockOperation("operationId1") + val operation2 = mockOperation("operationId2") + + operation1.setStringProperty("externalId", "externalId1") + operation2.setStringProperty("externalId", "externalId1") + + // When + mocks.operationRepo.enqueue(operation1) + mocks.operationRepo.enqueue(operation2) + mocks.operationRepo.start() + + waiter.waitForWake() + + // Then + coVerifyOrder { + mocks.operationModelStore.add(operation1) + mocks.operationModelStore.add(operation2) + } + + // + coVerify(exactly = 0) { + mocks.executor.execute( + withArg { + it.count() shouldBe 2 + it[0] shouldBe operation1 + it[1] shouldBe operation2 + }, + ) + mocks.operationModelStore.remove("operationId1") + mocks.operationModelStore.remove("operationId2") + } + } }) { companion object { private fun mockOperation( diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OneSignalImpTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OneSignalImpTests.kt index 891139a411..735a0f0570 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OneSignalImpTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/internal/OneSignalImpTests.kt @@ -1,10 +1,12 @@ package com.onesignal.internal +import android.content.Context import com.onesignal.debug.LogLevel import com.onesignal.debug.internal.logging.Logging import io.kotest.assertions.throwables.shouldThrowUnit import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe +import io.mockk.mockk class OneSignalImpTests : FunSpec({ beforeAny { @@ -86,4 +88,16 @@ class OneSignalImpTests : FunSpec({ } } } + + test("When identity verification is on and no user is created, calling initWithContext will create a new user") { + // Given + val os = OneSignalImp() + val appId = "tempAppId" + val context = mockk() + + // When + os.initWithContext(context, appId) + + // TODO + } }) diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/UserManagerTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/UserManagerTests.kt index 41e836eb9b..df32736d4f 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/UserManagerTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/UserManagerTests.kt @@ -1,17 +1,31 @@ package com.onesignal.user.internal +import com.onesignal.IUserJwtInvalidatedListener import com.onesignal.core.internal.language.ILanguageContext +import com.onesignal.core.internal.operations.ExecutionResponse +import com.onesignal.core.internal.operations.ExecutionResult +import com.onesignal.core.internal.operations.Operation import com.onesignal.mocks.MockHelper +import com.onesignal.user.internal.backend.CreateUserResponse +import com.onesignal.user.internal.backend.IUserBackendService +import com.onesignal.user.internal.backend.IdentityConstants +import com.onesignal.user.internal.backend.PropertiesObject +import com.onesignal.user.internal.operations.LoginUserOperation +import com.onesignal.user.internal.operations.impl.executors.IdentityOperationExecutor +import com.onesignal.user.internal.operations.impl.executors.LoginUserOperationExecutor import com.onesignal.user.internal.subscriptions.ISubscriptionManager import com.onesignal.user.internal.subscriptions.SubscriptionList +import com.onesignal.user.internal.subscriptions.SubscriptionModelStore import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe +import io.mockk.coEvery import io.mockk.every import io.mockk.just import io.mockk.mockk import io.mockk.runs import io.mockk.slot +import io.mockk.spyk import io.mockk.verify class UserManagerTests : FunSpec({ @@ -141,7 +155,8 @@ class UserManagerTests : FunSpec({ it.tags["my-tag-key1"] = "my-tag-value1" } - val userManager = UserManager(mockSubscriptionManager, MockHelper.identityModelStore(), propertiesModelStore, MockHelper.languageContext()) + val userManager = + UserManager(mockSubscriptionManager, MockHelper.identityModelStore(), propertiesModelStore, MockHelper.languageContext()) // When val tagSnapshot1 = userManager.getTags() @@ -191,4 +206,58 @@ class UserManagerTests : FunSpec({ verify(exactly = 1) { mockSubscriptionManager.addSmsSubscription("+15558675309") } verify(exactly = 1) { mockSubscriptionManager.removeSmsSubscription("+15558675309") } } + + test("login user with jwt calls onUserJwtInvalidated() when the jwt is unauthorized") { + // Given + val appId = "appId" + val localOneSignalId = "local-onesignalId" + val remoteOneSignalId = "remote-onesignalId" + + // mock components + val mockSubscriptionManager = mockk() + val mockIdentityModelStore = MockHelper.identityModelStore() + val mockPropertiesModelStore = MockHelper.propertiesModelStore() + val mockSubscriptionsModelStore = mockk() + val mockLanguageContext = MockHelper.languageContext() + + // mock backend service + val mockUserBackendService = mockk() + coEvery { mockUserBackendService.createUser(any(), any(), any(), any()) } returns + CreateUserResponse(mapOf(IdentityConstants.ONESIGNAL_ID to remoteOneSignalId), PropertiesObject(), listOf()) + + // mock operation for login user + val mockIdentityOperationExecutor = mockk() + coEvery { mockIdentityOperationExecutor.execute(any()) } returns + ExecutionResponse(ExecutionResult.FAIL_UNAUTHORIZED) + val loginUserOperationExecutor = + LoginUserOperationExecutor( + mockIdentityOperationExecutor, + MockHelper.applicationService(), + MockHelper.deviceService(), + mockUserBackendService, + mockIdentityModelStore, + mockPropertiesModelStore, + mockSubscriptionsModelStore, + MockHelper.configModelStore(), + mockLanguageContext, + ) + val operations = listOf(LoginUserOperation(appId, localOneSignalId, "externalId", "existingOneSignalId")) + + // mock user manager with jwtInvalidatedListener added + val userManager = + UserManager(mockSubscriptionManager, mockIdentityModelStore, mockPropertiesModelStore, mockLanguageContext) + mockIdentityModelStore.subscribe(userManager) + val spyJwtInvalidatedListener = spyk() + userManager.addUserJwtInvalidatedListener(spyJwtInvalidatedListener) + + // When + val response = loginUserOperationExecutor.execute(operations) + + // Then + userManager.jwtInvalidatedCallback.hasSubscribers shouldBe true + response.result shouldBe ExecutionResult.FAIL_UNAUTHORIZED + verify(exactly = 1) { mockIdentityModelStore.invalidateJwt() } + // Note: set the default value of useIdentityVerification in OneSignalImp.kt to pass the test + verify(exactly = 1) { spyJwtInvalidatedListener.onUserJwtInvalidated(any()) } + } }) diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/migrations/RecoverFromDroppedLoginBugTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/migrations/RecoverFromDroppedLoginBugTests.kt index 554c09ac96..27a5c873cb 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/migrations/RecoverFromDroppedLoginBugTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/migrations/RecoverFromDroppedLoginBugTests.kt @@ -36,6 +36,7 @@ private class Mocks { listOf(), operationModelStore, configModelStore, + MockHelper.identityModelStore(), Time(), ExecutorMocks.getNewRecordState(configModelStore), ), diff --git a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt index 87d5f4a0bd..d6f5ae32f6 100644 --- a/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt +++ b/OneSignalSDK/onesignal/core/src/test/java/com/onesignal/user/internal/operations/SubscriptionOperationExecutorTests.kt @@ -51,6 +51,7 @@ class SubscriptionOperationExecutorTests : Pair(remoteSubscriptionId, rywToken) val mockSubscriptionsModelStore = mockk() + val mockIdentityModelStore = MockHelper.identityModelStore() val subscriptionModel1 = SubscriptionModel() subscriptionModel1.id = localSubscriptionId every { mockSubscriptionsModelStore.get(localSubscriptionId) } returns subscriptionModel1 @@ -62,6 +63,7 @@ class SubscriptionOperationExecutorTests : mockSubscriptionBackendService, MockHelper.deviceService(), AndroidMockHelper.applicationService(), + mockIdentityModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), mockBuildUserService, @@ -109,6 +111,7 @@ class SubscriptionOperationExecutorTests : coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } throws BackendException(408, retryAfterSeconds = 10) + val mockIdentityModelStore = MockHelper.identityModelStore() val mockSubscriptionsModelStore = mockk() val mockBuildUserService = mockk() @@ -117,6 +120,7 @@ class SubscriptionOperationExecutorTests : mockSubscriptionBackendService, MockHelper.deviceService(), AndroidMockHelper.applicationService(), + mockIdentityModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), mockBuildUserService, @@ -172,6 +176,7 @@ class SubscriptionOperationExecutorTests : mockSubscriptionBackendService, MockHelper.deviceService(), AndroidMockHelper.applicationService(), + MockHelper.identityModelStore(), mockSubscriptionsModelStore, MockHelper.configModelStore(), mockBuildUserService, @@ -219,14 +224,20 @@ class SubscriptionOperationExecutorTests : val mockSubscriptionsModelStore = mockk() val mockBuildUserService = mockk() + val mockIdentityModelStore = MockHelper.identityModelStore() val mockConfigModelStore = MockHelper.configModelStore().also { it.model.opRepoPostCreateRetryUpTo = 1_000 } val newRecordState = getNewRecordState(mockConfigModelStore).also { it.add(remoteOneSignalId) } + val subscriptionModel1 = SubscriptionModel() + subscriptionModel1.id = remoteSubscriptionId + subscriptionModel1.address = "pushToken1" + every { mockSubscriptionsModelStore.get(remoteSubscriptionId) } returns subscriptionModel1 val subscriptionOperationExecutor = SubscriptionOperationExecutor( mockSubscriptionBackendService, MockHelper.deviceService(), AndroidMockHelper.applicationService(), + mockIdentityModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), mockBuildUserService, @@ -257,7 +268,7 @@ class SubscriptionOperationExecutorTests : test("create subscription then delete subscription is a successful no-op") { // Given val mockSubscriptionBackendService = mockk() - + val mockIdentityModelStore = MockHelper.identityModelStore() val mockSubscriptionsModelStore = mockk() val subscriptionModel1 = SubscriptionModel() subscriptionModel1.id = localSubscriptionId @@ -270,6 +281,7 @@ class SubscriptionOperationExecutorTests : mockSubscriptionBackendService, MockHelper.deviceService(), AndroidMockHelper.applicationService(), + mockIdentityModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), mockBuildUserService, @@ -300,6 +312,7 @@ class SubscriptionOperationExecutorTests : test("create subscription then update subscription successfully creates subscription") { // Given + val mockIdentityModelStore = MockHelper.identityModelStore() val mockSubscriptionBackendService = mockk() coEvery { mockSubscriptionBackendService.createSubscription(any(), any(), any(), any()) } returns Pair(remoteSubscriptionId, rywToken) @@ -316,6 +329,7 @@ class SubscriptionOperationExecutorTests : mockSubscriptionBackendService, MockHelper.deviceService(), AndroidMockHelper.applicationService(), + mockIdentityModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), mockBuildUserService, @@ -368,6 +382,7 @@ class SubscriptionOperationExecutorTests : test("update subscription successfully updates subscription") { // Given + val mockIdentityModelStore = MockHelper.identityModelStore() val mockSubscriptionBackendService = mockk() coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any()) } returns rywToken @@ -386,6 +401,7 @@ class SubscriptionOperationExecutorTests : mockSubscriptionBackendService, MockHelper.deviceService(), AndroidMockHelper.applicationService(), + mockIdentityModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), mockBuildUserService, @@ -436,6 +452,7 @@ class SubscriptionOperationExecutorTests : test("update subscription fails with retry when there is a network condition") { // Given + val mockIdentityModelStore = MockHelper.identityModelStore() val mockSubscriptionBackendService = mockk() coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any()) } throws BackendException(408) @@ -447,6 +464,7 @@ class SubscriptionOperationExecutorTests : mockSubscriptionBackendService, MockHelper.deviceService(), AndroidMockHelper.applicationService(), + mockIdentityModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), mockBuildUserService, @@ -491,6 +509,7 @@ class SubscriptionOperationExecutorTests : val mockSubscriptionBackendService = mockk() coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any()) } throws BackendException(404) + val mockIdentityModelStore = MockHelper.identityModelStore() val mockSubscriptionsModelStore = mockk() val mockBuildUserService = mockk() @@ -499,6 +518,7 @@ class SubscriptionOperationExecutorTests : mockSubscriptionBackendService, MockHelper.deviceService(), AndroidMockHelper.applicationService(), + mockIdentityModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), mockBuildUserService, @@ -543,6 +563,7 @@ class SubscriptionOperationExecutorTests : val mockSubscriptionBackendService = mockk() coEvery { mockSubscriptionBackendService.updateSubscription(any(), any(), any()) } throws BackendException(404) + val mockIdentityModelStore = MockHelper.identityModelStore() val mockSubscriptionsModelStore = mockk() val mockBuildUserService = mockk() val mockConfigModelStore = MockHelper.configModelStore().also { it.model.opRepoPostCreateRetryUpTo = 1_000 } @@ -553,6 +574,7 @@ class SubscriptionOperationExecutorTests : mockSubscriptionBackendService, MockHelper.deviceService(), AndroidMockHelper.applicationService(), + mockIdentityModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), mockBuildUserService, @@ -585,6 +607,7 @@ class SubscriptionOperationExecutorTests : val mockSubscriptionBackendService = mockk() coEvery { mockSubscriptionBackendService.deleteSubscription(any(), any()) } just runs + val mockIdentityModelStore = MockHelper.identityModelStore() val mockSubscriptionsModelStore = mockk() every { mockSubscriptionsModelStore.remove(any(), any()) } just runs @@ -595,6 +618,7 @@ class SubscriptionOperationExecutorTests : mockSubscriptionBackendService, MockHelper.deviceService(), AndroidMockHelper.applicationService(), + mockIdentityModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), mockBuildUserService, @@ -621,6 +645,7 @@ class SubscriptionOperationExecutorTests : val mockSubscriptionBackendService = mockk() coEvery { mockSubscriptionBackendService.deleteSubscription(any(), any()) } throws BackendException(408) + val mockIdentityModelStore = MockHelper.identityModelStore() val mockSubscriptionsModelStore = mockk() val mockBuildUserService = mockk() @@ -629,6 +654,7 @@ class SubscriptionOperationExecutorTests : mockSubscriptionBackendService, MockHelper.deviceService(), AndroidMockHelper.applicationService(), + mockIdentityModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), mockBuildUserService, @@ -656,6 +682,7 @@ class SubscriptionOperationExecutorTests : val mockSubscriptionBackendService = mockk() coEvery { mockSubscriptionBackendService.deleteSubscription(any(), any()) } throws BackendException(404) + val mockIdentityModelStore = MockHelper.identityModelStore() val mockSubscriptionsModelStore = mockk() val mockBuildUserService = mockk() @@ -664,6 +691,7 @@ class SubscriptionOperationExecutorTests : mockSubscriptionBackendService, MockHelper.deviceService(), AndroidMockHelper.applicationService(), + mockIdentityModelStore, mockSubscriptionsModelStore, MockHelper.configModelStore(), mockBuildUserService, @@ -699,6 +727,7 @@ class SubscriptionOperationExecutorTests : mockSubscriptionBackendService, MockHelper.deviceService(), AndroidMockHelper.applicationService(), + MockHelper.identityModelStore(), mockSubscriptionsModelStore, MockHelper.configModelStore(), mockBuildUserService, @@ -740,6 +769,7 @@ class SubscriptionOperationExecutorTests : mockSubscriptionBackendService, MockHelper.deviceService(), AndroidMockHelper.applicationService(), + MockHelper.identityModelStore(), mockSubscriptionsModelStore, MockHelper.configModelStore(), mockBuildUserService, diff --git a/OneSignalSDK/onesignal/in-app-messages/src/test/java/com/onesignal/inAppMessages/internal/InAppMessagesManagerTests.kt b/OneSignalSDK/onesignal/in-app-messages/src/test/java/com/onesignal/inAppMessages/internal/InAppMessagesManagerTests.kt index f2753bcff8..63cea213b4 100644 --- a/OneSignalSDK/onesignal/in-app-messages/src/test/java/com/onesignal/inAppMessages/internal/InAppMessagesManagerTests.kt +++ b/OneSignalSDK/onesignal/in-app-messages/src/test/java/com/onesignal/inAppMessages/internal/InAppMessagesManagerTests.kt @@ -14,6 +14,7 @@ import com.onesignal.session.internal.influence.IInfluenceManager import com.onesignal.session.internal.outcomes.IOutcomeEventsController import com.onesignal.session.internal.session.ISessionService import com.onesignal.user.IUserManager +import com.onesignal.user.internal.identity.IdentityModelStore import com.onesignal.user.internal.subscriptions.ISubscriptionManager import io.kotest.core.spec.style.FunSpec import io.kotest.matchers.shouldBe @@ -41,6 +42,7 @@ class InAppMessagesManagerTests : FunSpec({ mockk(), mockk(), mockk(), + mockk(), mockk(), mockk(), mockk(), diff --git a/OneSignalSDK/onesignal/in-app-messages/src/test/java/com/onesignal/inAppMessages/internal/backend/InAppBackendServiceTests.kt b/OneSignalSDK/onesignal/in-app-messages/src/test/java/com/onesignal/inAppMessages/internal/backend/InAppBackendServiceTests.kt index 379013d7c6..8536c220d1 100644 --- a/OneSignalSDK/onesignal/in-app-messages/src/test/java/com/onesignal/inAppMessages/internal/backend/InAppBackendServiceTests.kt +++ b/OneSignalSDK/onesignal/in-app-messages/src/test/java/com/onesignal/inAppMessages/internal/backend/InAppBackendServiceTests.kt @@ -39,7 +39,14 @@ class InAppBackendServiceTests : val inAppBackendService = InAppBackendService(mockHttpClient, MockHelper.deviceService(), mockHydrator) // When - val response = inAppBackendService.listInAppMessages("appId", "subscriptionId", "123", mockSessionDurationProvider) + val response = + inAppBackendService.listInAppMessages( + "appId", + "subscriptionId", + "123", + mockSessionDurationProvider, + alias = Pair("onesignal_id", "onesignal_id"), + ) // Then response shouldNotBe null @@ -62,7 +69,14 @@ class InAppBackendServiceTests : val inAppBackendService = InAppBackendService(mockHttpClient, MockHelper.deviceService(), mockHydrator) // When - val response = inAppBackendService.listInAppMessages("appId", "subscriptionId", "123", mockSessionDurationProvider) + val response = + inAppBackendService.listInAppMessages( + "appId", + "subscriptionId", + "123", + mockSessionDurationProvider, + alias = Pair("onesignal_id", "onesignal_id"), + ) // Then response shouldNotBe null @@ -95,7 +109,14 @@ class InAppBackendServiceTests : val inAppBackendService = InAppBackendService(mockHttpClient, MockHelper.deviceService(), mockHydrator) // When - val response = inAppBackendService.listInAppMessages("appId", "subscriptionId", "123", mockSessionDurationProvider) + val response = + inAppBackendService.listInAppMessages( + "appId", + "subscriptionId", + "123", + mockSessionDurationProvider, + alias = Pair("onesignal_id", "onesignal_id"), + ) // Then response shouldBe null @@ -124,7 +145,14 @@ class InAppBackendServiceTests : val inAppBackendService = InAppBackendService(mockHttpClient, MockHelper.deviceService(), mockHydrator) // When - val response = inAppBackendService.listInAppMessages("appId", "subscriptionId", "1234", mockSessionDurationProvider) + val response = + inAppBackendService.listInAppMessages( + "appId", + "subscriptionId", + "1234", + mockSessionDurationProvider, + alias = Pair("onesignal_id", "onesignal_id"), + ) // Then response shouldNotBe null From f5640546ddd5960e086d6072ffe7f96ce814ed20 Mon Sep 17 00:00:00 2001 From: jinliu9508 Date: Mon, 12 Aug 2024 21:59:11 -0400 Subject: [PATCH 06/13] Add UI element to DemoApp to manually test JWT impl --- .../sdktest/application/MainApplication.java | 13 +- .../sdktest/model/MainActivityViewModel.java | 36 +++++- .../main/res/layout/main_activity_layout.xml | 115 +++++++++++++++--- .../app/src/main/res/values/strings.xml | 5 + 4 files changed, 151 insertions(+), 18 deletions(-) diff --git a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/application/MainApplication.java b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/application/MainApplication.java index 3050d96fc4..b4683dbca6 100644 --- a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/application/MainApplication.java +++ b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/application/MainApplication.java @@ -6,9 +6,10 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.multidex.MultiDexApplication; - import com.onesignal.Continue; +import com.onesignal.IUserJwtInvalidatedListener; import com.onesignal.OneSignal; +import com.onesignal.UserJwtInvalidatedEvent; import com.onesignal.inAppMessages.IInAppMessageClickListener; import com.onesignal.inAppMessages.IInAppMessageClickEvent; import com.onesignal.inAppMessages.IInAppMessageDidDismissEvent; @@ -140,6 +141,16 @@ public void onUserStateChange(@NonNull UserChangedState state) { } }); + OneSignal.addUserJwtInvalidatedListner(new IUserJwtInvalidatedListener() { + @Override + public void onUserJwtInvalidated(@NonNull UserJwtInvalidatedEvent event) { + // !!! For manual testing only + String jwt = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIxNjg4ZDhmMi1kYTdmLTQ4MTUtOGVlMy05ZDEzNzg4NDgyYzgiLCJpYXQiOjE3MTgzMDk5NzIsImlkZW50aXR5Ijp7ImV4dGVybmFsX2lkIjoiYWxleC0wNjE0Iiwib25lc2lnbmFsX2lkIjoiYTViYjc4NDYtYzExNC00YzdkLTkzMWYtNGQ0NjhiMGE5OWJhIn0sInN1YnNjcmlwdGlvbnMiOlt7InR5cGUiOiJFbWFpbCIsInRva2VuIjoidGVzdEBkb21haW4uY29tIn0seyJpZCI6ImE2YzQxNmY3LTMxMGUtNDgzNi05Yjc4LWZiZmQ5NTgyNWNjNCJ9XX0.HsjsA2qNPwd9qov_8Px01km-dzRug-YKNNG85cMrGYI9Pdb2uoPQSdAN3Uqu7_o4pL8FRxXliYJrC52-9wH3FQ"; + OneSignal.updateUserJwt(event.getExternalId(), jwt); + Log.v(Tag.LOG_TAG, "onUserJwtInvalidated fired with ID:" + event.getExternalId()); + } + }); + OneSignal.getInAppMessages().setPaused(true); OneSignal.getLocation().setShared(false); diff --git a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java index 58069a298c..5a0177f199 100644 --- a/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java +++ b/Examples/OneSignalDemo/app/src/main/java/com/onesignal/sdktest/model/MainActivityViewModel.java @@ -18,6 +18,7 @@ import android.view.View; import android.view.ViewTreeObserver; import android.widget.Button; +import android.widget.EditText; import android.widget.LinearLayout; import android.widget.RelativeLayout; import android.widget.Switch; @@ -83,6 +84,10 @@ public class MainActivityViewModel implements ActivityViewModel, IPushSubscripti private Button loginUserButton; private Button logoutUserButton; + // JWT + private Button invalidateJwtButton; + private Button updateJwtButton; + // Alias private TextView aliasTitleTextView; private RecyclerView aliasesRecyclerView; @@ -211,6 +216,9 @@ public ActivityViewModel onActivityCreated(Context context) { loginUserButton = getActivity().findViewById(R.id.main_activity_login_user_button); logoutUserButton = getActivity().findViewById(R.id.main_activity_logout_user_button); + invalidateJwtButton = getActivity().findViewById(R.id.main_activity_invalidate_jwt_button); + updateJwtButton = getActivity().findViewById(R.id.main_activity_update_jwt_button); + aliasTitleTextView = getActivity().findViewById(R.id.main_activity_aliases_title_text_view); noAliasesTextView = getActivity().findViewById(R.id.main_activity_aliases_no_aliases_text_view); addAliasButton = getActivity().findViewById(R.id.main_activity_add_alias_button); @@ -403,7 +411,8 @@ private void setupAppLayout() { @Override public void onSuccess(String update) { if (update != null && !update.isEmpty()) { - OneSignal.login(update); + String jwt = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIxNjg4ZDhmMi1kYTdmLTQ4MTUtOGVlMy05ZDEzNzg4NDgyYzgiLCJpYXQiOjE3MTU5NzMwNzAsImlkZW50aXR5Ijp7ImV4dGVybmFsX2lkIjoiYWxleC0wNTE3Iiwib25lc2lnbmFsX2lkIjoiMGIzYWMyN2EtYWQ4Yi00MWVjLWJhYTYtMzI0NmNkODIyMjJkIn0sInN1YnNjcmlwdGlvbnMiOlt7InR5cGUiOiJFbWFpbCIsInRva2VuIjoiYWxleHRzYXktMDUxN0BvbmVzaWduYWwuY29tIn0seyJ0eXBlIjoiQW5kcm9pZFB1c2giLCJpZCI6ImFkMTAxY2FjLTA5MWItNDkyYy04OGJiLTgxNmZkNTNjYTBmMSJ9XX0._tlD2X8J16gDkP7__FJ8CwpqCLDwb8T14m2ugJwQvuQqbIn4b8o75cKbffbjVGcKP3YaudLCebit53aR9LTQCw"; + OneSignal.login(update, jwt); refreshState(); } } @@ -422,6 +431,7 @@ public void onFailure() { } private void setupUserLayout() { + setupJWTLayout(); setupAliasLayout(); setupEmailLayout(); setupSMSLayout(); @@ -430,6 +440,30 @@ private void setupUserLayout() { setupTriggersLayout(); } + private void setupJWTLayout() { + invalidateJwtButton.setOnClickListener(v -> { + OneSignal.updateUserJwt(OneSignal.getUser().getExternalId(), ""); + }); + updateJwtButton.setOnClickListener(v -> { + dialog.createUpdateAlertDialog("", Dialog.DialogAction.UPDATE, ProfileUtil.FieldType.JWT, new UpdateAlertDialogCallback() { + @Override + public void onSuccess(String update) { + if (update != null && !update.isEmpty()) { + OneSignal.updateUserJwt(OneSignal.getUser().getExternalId(), update); + //String jwt = "eyJhbGciOiJFUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiIxNjg4ZDhmMi1kYTdmLTQ4MTUtOGVlMy05ZDEzNzg4NDgyYzgiLCJpYXQiOjE3MTQwODA4MTMsImlkZW50aXR5Ijp7ImV4dGVybmFsX2lkIjoiMjAyNDA0MjUtYWxleDQyIn0sInN1YnNjcmlwdGlvbiI6W3sidHlwZSI6IiIsImlkIjoiMmRlZGU3MzItMTEyNi00MTlkLTk5M2UtNDIzYWQyYTZiZGU5In1dfQ.rzZ-HaDm1EwxbMxd8937bWzPhPSQDDSqSzaASgZZc5A5v8g6ZQHizN9CljOmoQ4lTLm7noD6rOmR07tlZdrI5w"; + //OneSignal.login(update, jwt); + refreshState(); + } + } + + @Override + public void onFailure() { + + } + }); + }); + } + private void setupAliasLayout() { setupAliasesRecyclerView(); addAliasButton.setOnClickListener(v -> dialog.createAddPairAlertDialog("Add Alias", ProfileUtil.FieldType.ALIAS, new AddPairAlertDialogCallback() { diff --git a/Examples/OneSignalDemo/app/src/main/res/layout/main_activity_layout.xml b/Examples/OneSignalDemo/app/src/main/res/layout/main_activity_layout.xml index ee4ce93cb5..f3eb64a051 100644 --- a/Examples/OneSignalDemo/app/src/main/res/layout/main_activity_layout.xml +++ b/Examples/OneSignalDemo/app/src/main/res/layout/main_activity_layout.xml @@ -152,20 +152,20 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="start|center_vertical" - android:gravity="start|center_vertical" - android:layout_marginBottom="4dp" android:layout_marginStart="12dp" android:layout_marginLeft="12dp" + android:layout_marginBottom="4dp" + android:gravity="start|center_vertical" android:text="@string/app" android:textColor="@color/colorDarkText" /> @@ -226,23 +226,23 @@ android:layout_width="match_parent" android:layout_height="56dp" android:layout_gravity="center" - android:gravity="center" android:layout_marginStart="12dp" android:layout_marginTop="4dp" android:layout_marginEnd="12dp" android:layout_marginBottom="12dp" - android:orientation="vertical" - android:background="@color/colorPrimary"> + android:background="@color/colorPrimary" + android:gravity="center" + android:orientation="vertical">