Skip to content
Open
9 changes: 8 additions & 1 deletion OneSignalSDK/detekt/detekt-baseline-core.xml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,8 @@
<ID>ConstructorParameterNaming:NewRecordsState.kt$NewRecordsState$private val _time: ITime</ID>
<ID>ConstructorParameterNaming:OSDatabase.kt$OSDatabase$private val _outcomeTableProvider: OutcomeTableProvider</ID>
<ID>ConstructorParameterNaming:OperationRepo.kt$OperationRepo$private val _configModelStore: ConfigModelStore</ID>
<ID>ConstructorParameterNaming:OperationRepo.kt$OperationRepo$private val _identityVerificationService: IdentityVerificationService</ID>
Comment thread
claude[bot] marked this conversation as resolved.
<ID>ConstructorParameterNaming:OperationRepo.kt$OperationRepo$private val _jwtTokenStore: JwtTokenStore</ID>
Comment thread
claude[bot] marked this conversation as resolved.
<ID>ConstructorParameterNaming:OperationRepo.kt$OperationRepo$private val _newRecordState: NewRecordsState</ID>
<ID>ConstructorParameterNaming:OperationRepo.kt$OperationRepo$private val _operationModelStore: OperationModelStore</ID>
<ID>ConstructorParameterNaming:OperationRepo.kt$OperationRepo$private val _time: ITime</ID>
Expand All @@ -82,6 +84,7 @@
<ID>ConstructorParameterNaming:PreferencesService.kt$PreferencesService$private val _applicationService: IApplicationService</ID>
<ID>ConstructorParameterNaming:PreferencesService.kt$PreferencesService$private val _time: ITime</ID>
<ID>ConstructorParameterNaming:PropertiesModelStoreListener.kt$PropertiesModelStoreListener$private val _configModelStore: ConfigModelStore</ID>
<ID>ConstructorParameterNaming:PropertiesModelStoreListener.kt$PropertiesModelStoreListener$private val _identityModelStore: IdentityModelStore</ID>
<ID>ConstructorParameterNaming:RebuildUserService.kt$RebuildUserService$private val _configModelStore: ConfigModelStore</ID>
<ID>ConstructorParameterNaming:RebuildUserService.kt$RebuildUserService$private val _identityModelStore: IdentityModelStore</ID>
<ID>ConstructorParameterNaming:RebuildUserService.kt$RebuildUserService$private val _propertiesModelStore: PropertiesModelStore</ID>
Expand Down Expand Up @@ -161,7 +164,6 @@
<ID>ForbiddenComment:IUserBackendService.kt$IUserBackendService$// TODO: Change to send only the push subscription, optimally</ID>
<ID>ForbiddenComment:LoginHelper.kt$LoginHelper$// TODO: Set JWT Token for all future requests.</ID>
<ID>ForbiddenComment:LogoutHelper.kt$LogoutHelper$// TODO: remove JWT Token for all future requests.</ID>
<ID>ForbiddenComment:OperationRepo.kt$OperationRepo$// TODO: Need to provide callback for app to reset JWT. For now, fail with no retry.</ID>
<ID>ForbiddenComment:ParamsBackendService.kt$ParamsBackendService$// TODO: New</ID>
<ID>ForbiddenComment:PermissionsActivity.kt$PermissionsActivity$// TODO after we remove IAM from being an activity window we may be able to remove this handler</ID>
<ID>ForbiddenComment:PermissionsActivity.kt$PermissionsActivity$// TODO improve this method</ID>
Expand Down Expand Up @@ -194,6 +196,7 @@
<ID>LongMethod:TrackGooglePurchase.kt$TrackGooglePurchase$private fun queryBoughtItems()</ID>
<ID>LongMethod:TrackGooglePurchase.kt$TrackGooglePurchase$private fun sendPurchases( skusToAdd: ArrayList&lt;String>, newPurchaseTokens: ArrayList&lt;String>, )</ID>
<ID>LongMethod:UpdateUserOperationExecutor.kt$UpdateUserOperationExecutor$override suspend fun execute(operations: List&lt;Operation>): ExecutionResponse</ID>
<ID>LongParameterList:CreateSubscriptionOperation.kt$CreateSubscriptionOperation$(appId: String, onesignalId: String, externalId: String?, subscriptionId: String, type: SubscriptionType, enabled: Boolean, address: String, status: SubscriptionStatus)</ID>
<ID>LongParameterList:ICustomEventBackendService.kt$ICustomEventBackendService$( appId: String, onesignalId: String, externalId: String?, timestamp: Long, eventName: String, eventProperties: String?, metadata: CustomEventMetadata, )</ID>
<ID>LongParameterList:IDatabase.kt$IDatabase$( table: String, columns: Array&lt;String>? = null, whereClause: String? = null, whereArgs: Array&lt;String>? = null, groupBy: String? = null, having: String? = null, orderBy: String? = null, limit: String? = null, action: (ICursor) -> Unit, )</ID>
<ID>LongParameterList:IOutcomeEventsBackendService.kt$IOutcomeEventsBackendService$( appId: String, userId: String, subscriptionId: String, deviceType: String, direct: Boolean?, event: OutcomeEvent, )</ID>
Expand All @@ -203,6 +206,7 @@
<ID>LongParameterList:OutcomeEventsController.kt$OutcomeEventsController$( private val _session: ISessionService, private val _influenceManager: IInfluenceManager, private val _outcomeEventsCache: IOutcomeEventsRepository, private val _outcomeEventsPreferences: IOutcomeEventsPreferences, private val _outcomeEventsBackend: IOutcomeEventsBackendService, private val _configModelStore: ConfigModelStore, private val _identityModelStore: IdentityModelStore, private val _subscriptionManager: ISubscriptionManager, private val _deviceService: IDeviceService, private val _time: ITime, )</ID>
<ID>LongParameterList:SubscriptionObject.kt$SubscriptionObject$( val id: String? = null, val type: SubscriptionObjectType? = null, val token: String? = null, val enabled: Boolean? = null, val notificationTypes: Int? = null, val sdk: String? = null, val deviceModel: String? = null, val deviceOS: String? = null, val rooted: Boolean? = null, val netType: Int? = null, val carrier: String? = null, val appVersion: String? = null, )</ID>
<ID>LongParameterList:SubscriptionOperationExecutor.kt$SubscriptionOperationExecutor$( private val _subscriptionBackend: ISubscriptionBackendService, private val _deviceService: IDeviceService, private val _applicationService: IApplicationService, private val _subscriptionModelStore: SubscriptionModelStore, private val _configModelStore: ConfigModelStore, private val _buildUserService: IRebuildUserService, private val _newRecordState: NewRecordsState, private val _consistencyManager: IConsistencyManager, )</ID>
<ID>LongParameterList:UpdateSubscriptionOperation.kt$UpdateSubscriptionOperation$(appId: String, onesignalId: String, externalId: String?, subscriptionId: String, type: SubscriptionType, enabled: Boolean, address: String, status: SubscriptionStatus)</ID>
<ID>LongParameterList:UserSwitcher.kt$UserSwitcher$( private val preferencesService: IPreferencesService, private val operationRepo: IOperationRepo, private val services: ServiceProvider, private val idManager: IDManager = IDManager, private val identityModelStore: IdentityModelStore, private val propertiesModelStore: PropertiesModelStore, private val subscriptionModelStore: SubscriptionModelStore, private val configModel: ConfigModel, private val oneSignalUtils: OneSignalUtils = OneSignalUtils, private val carrierName: String? = null, private val deviceOS: String? = null, private val androidUtils: AndroidUtils = AndroidUtils, private val appContextProvider: () -> Context, )</ID>
<ID>LoopWithTooManyJumpStatements:ModelStore.kt$ModelStore$for (index in jsonArray.length() - 1 downTo 0) { val newModel = create(jsonArray.getJSONObject(index)) ?: continue /* * NOTE: Migration fix for bug introduced in 5.1.12 * The following check is intended for the operation model store. * When the call to this method moved out of the operation model store's initializer, * duplicate operations could be cached. * See https://github.com/OneSignal/OneSignal-Android-SDK/pull/2099 */ val hasExisting = models.any { it.id == newModel.id } if (hasExisting) { Logging.debug("ModelStore&lt;$name>: load - operation.id: ${newModel.id} already exists in the store.") continue } models.add(0, newModel) // listen for changes to this model newModel.subscribe(this) }</ID>
<ID>MagicNumber:ApplicationService.kt$ApplicationService$50</ID>
Expand Down Expand Up @@ -285,6 +289,7 @@
<ID>ReturnCount:BackgroundManager.kt$BackgroundManager$override fun cancelRunBackgroundServices(): Boolean</ID>
<ID>ReturnCount:OneSignalImp.kt$OneSignalImp$private fun internalInit( context: Context, appId: String?, ): Boolean</ID>
<ID>ReturnCount:ConfigModel.kt$ConfigModel$override fun createModelForProperty( property: String, jsonObject: JSONObject, ): Model?</ID>
<ID>ReturnCount:FeatureFlagsBackendService.kt$FeatureFlagsBackendService$override suspend fun fetchRemoteFeatureFlags(appId: String): RemoteFeatureFlagsFetchOutcome</ID>
<ID>ReturnCount:HttpClient.kt$HttpClient$private suspend fun makeRequest( url: String, method: String?, jsonBody: JSONObject?, timeout: Int, headers: OptionalHeaders?, ): HttpResponse</ID>
<ID>ReturnCount:IdentityOperationExecutor.kt$IdentityOperationExecutor$override suspend fun execute(operations: List&lt;Operation>): ExecutionResponse</ID>
<ID>ReturnCount:JSONUtils.kt$JSONUtils$fun compareJSONArrays( jsonArray1: JSONArray?, jsonArray2: JSONArray?, ): Boolean</ID>
Expand All @@ -297,6 +302,8 @@
<ID>ReturnCount:Model.kt$Model$protected fun getOptLongProperty( name: String, create: (() -> Long?)? = null, ): Long?</ID>
<ID>ReturnCount:Model.kt$Model$protected inline fun &lt;reified T : Enum&lt;T>> getOptEnumProperty(name: String): T?</ID>
<ID>ReturnCount:OperationModelStore.kt$OperationModelStore$override fun create(jsonObject: JSONObject?): Operation?</ID>
<ID>ReturnCount:OperationRepoIvExtensions.kt$internal fun OperationRepo.handleFailUnauthorized( startingOp: OperationRepo.OperationQueueItem, ops: List&lt;OperationRepo.OperationQueueItem>, jwtTokenStore: JwtTokenStore, ivBehaviorActive: Boolean, ): Boolean</ID>
<ID>ReturnCount:OperationRepoIvExtensions.kt$internal fun OperationRepo.hasValidJwtIfRequired( jwtTokenStore: JwtTokenStore, op: com.onesignal.core.internal.operations.Operation, ivBehaviorActive: Boolean, ): Boolean</ID>
<ID>ReturnCount:OperationModelStore.kt$OperationModelStore$private fun isValidOperation(jsonObject: JSONObject): Boolean</ID>
<ID>ReturnCount:OutcomeEventsController.kt$OutcomeEventsController$private suspend fun sendAndCreateOutcomeEvent( name: String, weight: Float, // Note: this is optional sessionTime: Long, influences: List&lt;Influence>, ): OutcomeEvent?</ID>
<ID>ReturnCount:OutcomeEventsController.kt$OutcomeEventsController$private suspend fun sendUniqueOutcomeEvent( name: String, sessionInfluences: List&lt;Influence>, ): OutcomeEvent?</ID>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import com.onesignal.core.internal.background.impl.BackgroundManager
import com.onesignal.core.internal.config.ConfigModelStore
import com.onesignal.core.internal.config.impl.ConfigModelStoreListener
import com.onesignal.core.internal.config.impl.FeatureFlagsRefreshService
import com.onesignal.core.internal.config.impl.IdentityVerificationService
import com.onesignal.core.internal.database.IDatabaseProvider
import com.onesignal.core.internal.database.impl.DatabaseProvider
import com.onesignal.core.internal.device.IDeviceService
Expand Down Expand Up @@ -70,6 +71,9 @@ internal class CoreModule : IModule {
builder.register<FeatureFlagsRefreshService>().provides<IStartableService>()

builder.register<JwtTokenStore>().provides<JwtTokenStore>()
builder.register<IdentityVerificationService>()
.provides<IdentityVerificationService>()
.provides<IStartableService>()

// Operations
builder.register<OperationModelStore>().provides<OperationModelStore>()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,7 @@ internal class ParamsBackendService(
return ParamsObject(
googleProjectNumber = responseJson.safeString("android_sender_id"),
enterprise = responseJson.safeBool("enterp"),
// TODO: New
useIdentityVerification = responseJson.safeBool("require_ident_auth"),
useIdentityVerification = responseJson.safeBool("jwt_required"),
notificationChannels = responseJson.optJSONArray("chnl_lst"),
firebaseAnalytics = responseJson.safeBool("fba"),
restoreTTLFilter = responseJson.safeBool("restore_ttl_filter"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -83,12 +83,10 @@ internal class ConfigModelStoreListener(
config.fcmParams.projectId = params.fcmParams.projectId
config.fcmParams.appId = params.fcmParams.appId
config.fcmParams.apiKey = params.fcmParams.apiKey
config.useIdentityVerification = JwtRequirement.fromBoolean(params.useIdentityVerification ?: false)

// these are only copied from the backend params when the backend has set them.
params.enterprise?.let { config.enterprise = it }
params.useIdentityVerification?.let {
config.useIdentityVerification = JwtRequirement.fromBoolean(it)
}
params.firebaseAnalytics?.let { config.firebaseAnalytics = it }
params.restoreTTLFilter?.let { config.restoreTTLFilter = it }
Comment thread
claude[bot] marked this conversation as resolved.
params.clearGroupOnSummaryClick?.let { config.clearGroupOnSummaryClick = it }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.onesignal.core.internal.config.impl

import com.onesignal.common.modeling.ISingletonModelStoreChangeHandler
import com.onesignal.common.modeling.ModelChangeTags
import com.onesignal.common.modeling.ModelChangedArgs
import com.onesignal.core.internal.config.ConfigModel
import com.onesignal.core.internal.config.ConfigModelStore
import com.onesignal.core.internal.features.FeatureFlag
import com.onesignal.core.internal.features.IFeatureManager
import com.onesignal.core.internal.startup.IStartableService
import com.onesignal.user.internal.jwt.JwtRequirement

/**
* Single source of truth for Identity Verification gating, and for forwarding HYDRATE events to
* the [com.onesignal.core.internal.operations.IOperationRepo] post-HYDRATE choreography.
*
* Gate state is derived on read from the injected [IFeatureManager] (rollout flag) and
* [ConfigModelStore] (customer `jwt_required`); nothing is duplicated here. UNKNOWN
* (pre-HYDRATE) reads as `false` for both gates, which is the safe default.
*
* Invariant `ivBehaviorActive == true ⇒ newCodePathsRun == true` holds because both are derived
* from the same `useIdentityVerification` field.
*
* Consumers (e.g. OperationRepo) wire post-HYDRATE behavior via [setOnJwtConfigHydratedHandler];
* the handler fires once per HYDRATE with `ivRequired = useIdentityVerification == REQUIRED`.
*/
internal class IdentityVerificationService(
private val featureManager: IFeatureManager,
private val configModelStore: ConfigModelStore,
) : IStartableService, ISingletonModelStoreChangeHandler<ConfigModel> {
/** Whether IV-specific behavior (JWT attachment, auth error handling) applies. UNKNOWN reads as `false`. */
val ivBehaviorActive: Boolean
get() = configModelStore.model.useIdentityVerification == JwtRequirement.REQUIRED

/** Whether new IV-related code paths should run. `featureFlag_IV_ON || jwt_required == REQUIRED`. */
val newCodePathsRun: Boolean
get() = featureManager.isEnabled(FeatureFlag.SDK_IDENTITY_VERIFICATION) || ivBehaviorActive

private val handlerLock = Any()
private var onJwtConfigHydrated: ((ivRequired: Boolean) -> Unit)? = null

/**
* Register a handler invoked once per HYDRATE of the config model. Used by OperationRepo to
* release pre-HYDRATE deferral and (when IV is required) purge anonymous queued ops.
* Pass `null` to clear.
*/
fun setOnJwtConfigHydratedHandler(handler: ((ivRequired: Boolean) -> Unit)?) {
synchronized(handlerLock) {
onJwtConfigHydrated = handler
}
}

override fun start() {
configModelStore.subscribe(this)
}

override fun onModelReplaced(
model: ConfigModel,
tag: String,
) {
if (tag != ModelChangeTags.HYDRATE) return
// Snapshot the handler under the lock, then invoke outside — never hold the lock
// across user-supplied code.
val handler = synchronized(handlerLock) { onJwtConfigHydrated }
handler?.invoke(model.useIdentityVerification == JwtRequirement.REQUIRED)
}

override fun onModelUpdated(
args: ModelChangedArgs,
tag: String,
) {
// Remote params arrive as full-model replacements (HYDRATE); individual property
// updates are not expected for useIdentityVerification.
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ import com.onesignal.core.internal.backend.impl.FeatureFlagsJsonParser
import com.onesignal.core.internal.config.ConfigModel
import com.onesignal.core.internal.config.ConfigModelStore
import com.onesignal.debug.internal.logging.Logging
import com.onesignal.user.internal.jwt.IdentityVerificationGates
import kotlinx.serialization.json.JsonObject

internal interface IFeatureManager {
Expand Down Expand Up @@ -165,12 +164,9 @@ internal class FeatureManager(
source = "FeatureManager:${feature.activationMode}"
)

FeatureFlag.SDK_IDENTITY_VERIFICATION ->
IdentityVerificationGates.update(
featureFlagOn = enabled,
jwtRequirement = configModelStore.model.useIdentityVerification,
source = "FeatureManager:${feature.activationMode}"
)
// SDK_IDENTITY_VERIFICATION has no side effect: IdentityVerificationService
// reads featureStates directly via isEnabled() at gate-check time.
FeatureFlag.SDK_IDENTITY_VERIFICATION -> {}
}
}

Expand Down
Loading
Loading