Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

3DS2 2.2 Device Data Requirements #9298

Merged
merged 8 commits into from
Oct 1, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.stripe.android.stripe3ds2.init

import com.stripe.android.stripe3ds2.transaction.SdkTransactionId

fun interface DeviceDataFactory {
fun create(): Map<String, Any?>
suspend fun create(sdkReferenceNumber: String, sdkTransactionId: SdkTransactionId): Map<String, Any?>
}
Original file line number Diff line number Diff line change
@@ -1,46 +1,132 @@
package com.stripe.android.stripe3ds2.init

import android.content.Context
import android.content.pm.PackageManager.FEATURE_TELEPHONY_IMS
import android.media.AudioManager
import android.os.Build
import android.provider.Settings
import android.telephony.TelephonyManager
import android.util.DisplayMetrics
import android.util.Log
import android.webkit.WebSettings
import androidx.core.content.PackageManagerCompat.LOG_TAG
import androidx.core.os.LocaleListCompat
import com.stripe.android.stripe3ds2.utils.Supplier
import com.stripe.android.stripe3ds2.transaction.MessageVersionRegistry
import com.stripe.android.stripe3ds2.transaction.SdkTransactionId
import java.lang.reflect.Field
import java.text.SimpleDateFormat
import java.util.Calendar
import java.util.Locale
import java.util.TimeZone


/**
* Creates a Map populated with device identification data as defined in
* "EMV® 3-D Secure SDK—Device Information".
*/
internal class DeviceDataFactoryImpl internal constructor(
context: Context,
private val hardwareIdSupplier: Supplier<HardwareId>
private val appInfoRepository: AppInfoRepository,
private val messageVersionRegistry: MessageVersionRegistry,
) : DeviceDataFactory {
private val displayMetrics: DisplayMetrics = context.resources.displayMetrics
private val defaultUserAgent = WebSettings.getDefaultUserAgent(context)
private val telephonyManager = (context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager)
private val secureFRPMode = Settings.Secure.getInt(context.contentResolver, Settings.Global.SECURE_FRP_MODE, 0)
private val audioManager = (context.getSystemService(Context.AUDIO_SERVICE) as AudioManager)
private val packageManager = context.packageManager

override suspend fun create(sdkReferenceNumber: String, sdkTransactionId: SdkTransactionId): Map<String, Any?> {
val apiVersion = Build.VERSION.SDK_INT
val dateFormat = SimpleDateFormat("yyyyMMddHHmmss", Locale.getDefault())
val dateTime = dateFormat.format(Calendar.getInstance().time)

val codeName = buildCodeName() ?: "UNKNOWN"
val osName = "Android " + codeName + " " + Build.VERSION.RELEASE + " API " + apiVersion

override fun create(): Map<String, Any?> {
val hardwareId = hardwareIdSupplier.get().value
return mapOf(
val map = hashMapOf(
DeviceParam.PARAM_PLATFORM.toString() to "Android",
DeviceParam.PARAM_DEVICE_MODEL.toString() to Build.MODEL,
DeviceParam.PARAM_OS_NAME.toString() to Build.VERSION.CODENAME,
DeviceParam.PARAM_DEVICE_MODEL.toString() to Build.MANUFACTURER + "||" + Build.MODEL,
DeviceParam.PARAM_OS_NAME.toString() to osName,
DeviceParam.PARAM_OS_VERSION.toString() to Build.VERSION.RELEASE,
DeviceParam.PARAM_LOCALE.toString() to
LocaleListCompat.create(Locale.getDefault()).toLanguageTags(),
DeviceParam.PARAM_TIME_ZONE.toString() to TimeZone.getDefault().displayName,
LocaleListCompat.create(Locale.getDefault()).toLanguageTags(),
DeviceParam.PARAM_TIME_ZONE.toString() to (TimeZone.getDefault().rawOffset / 1000 / 60).toString(),
DeviceParam.PARAM_SCREEN_RESOLUTION.toString() to
String.format(
Locale.ROOT,
"%sx%s",
displayMetrics.heightPixels,
displayMetrics.widthPixels
)
).plus(
if (hardwareId.isNotEmpty()) {
mapOf(DeviceParam.PARAM_HARDWARE_ID.toString() to hardwareId)
} else {
emptyMap()
}
String.format(
Locale.ROOT,
"%sx%s",
displayMetrics.heightPixels,
displayMetrics.widthPixels
),
DeviceParam.PARAM_SDK_APP_ID.toString() to appInfoRepository.get().sdkAppId,
DeviceParam.PARAM_SDK_VERSION.toString() to messageVersionRegistry.current,
DeviceParam.PARAM_SDK_REF_NUMBER.toString() to sdkReferenceNumber,
DeviceParam.PARAM_DATE_TIME.toString() to dateTime,
DeviceParam.PARAM_SDK_TRANS_ID.toString() to sdkTransactionId.toString(),
DeviceParam.PARAM_WEB_VIEW_USER_AGENT.toString() to defaultUserAgent,
)

if (apiVersion >= Build.VERSION_CODES.P) {
map[DeviceParam.PARAM_SIM_CARRIER_ID.toString()] =
telephonyManager.simCarrierId.toString()
map[DeviceParam.PARAM_SIM_CARRIER_ID_NAME.toString()] =
telephonyManager.simCarrierIdName.toString()

if (packageManager.hasSystemFeature(FEATURE_TELEPHONY_IMS)) {
map[DeviceParam.PARAM_RTT_CALLING_MODE.toString()] =
telephonyManager.isRttSupported.toString()
}
}

if (apiVersion >= Build.VERSION_CODES.Q) {
map[DeviceParam.PARAM_SIM_SPECIFIC_CARRIER_ID.toString()] =
telephonyManager.simSpecificCarrierId.toString()
map[DeviceParam.PARAM_SIM_SPECIFIC_CARRIER_ID_NAME.toString()] =
telephonyManager.simSpecificCarrierIdName.toString()
map[DeviceParam.PARAM_MULTI_SIM_SUPPORTED.toString()] =
telephonyManager.isMultiSimSupported.toString()
map[DeviceParam.PARAM_APPLY_RAMPING_RINGER.toString()] =
audioManager.isRampingRingerEnabled.toString()
}

if (apiVersion >= Build.VERSION_CODES.R) {
map[DeviceParam.PARAM_SUBSCRIPTION_ID.toString()] =
telephonyManager.subscriptionId.toString()
map[DeviceParam.PARAM_SECURE_FRP_MODE.toString()] =
if (secureFRPMode == 1) "true" else "false"
}

if (apiVersion >= Build.VERSION_CODES.S) {
map[DeviceParam.PARAM_HARDWARE_SKU.toString()] =
Build.SKU
map[DeviceParam.PARAM_SOC_MANUFACTURER.toString()] =
Build.SOC_MANUFACTURER
map[DeviceParam.PARAM_SOC_MODEL.toString()] =
Build.SOC_MODEL
}

return map
}

private fun buildCodeName(): String? {
val fields: Array<Field> = Build.VERSION_CODES::class.java.fields
for (field in fields) {
val fieldName: String = field.name
var fieldValue = -1

try {
fieldValue = field.getInt(Any())
} catch (_: IllegalArgumentException) {
} catch (_: IllegalAccessException) {
} catch (_: NullPointerException) {
}

if (fieldValue == Build.VERSION.SDK_INT) {
return fieldName
}
}

return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,17 @@ internal enum class DeviceParam(private val code: String) {
PARAM_OS_VERSION("C004"),
PARAM_LOCALE("C005"),
PARAM_TIME_ZONE("C006"),
PARAM_HARDWARE_ID("C007"),
PARAM_SCREEN_RESOLUTION("C008"),
PARAM_DEVICE_NAME("C009"),
PARAM_IP_ADDRESS("C010"),
PARAM_LATITUDE("C011"),
PARAM_LONGITUDE("C012"),
PARAM_APP_PACKAGE_NAME("C013"),
PARAM_SDK_APP_ID("C014"),
PARAM_SDK_VERSION("C015"),
PARAM_SDK_REF_NUMBER("C016"),
PARAM_DATE_TIME("C017"),
PARAM_SDK_TRANS_ID("C018"),

// Android-Specific parameters

Expand Down Expand Up @@ -178,7 +183,30 @@ internal enum class DeviceParam(private val code: String) {
PARAM_DISPLAY_YDPI("A135"),

// StatFs parameters
PARAM_STAT_FS_TOTAL_BYTES("A136");
PARAM_STAT_FS_TOTAL_BYTES("A136"),

// Web View parameters
PARAM_WEB_VIEW_USER_AGENT("A137"),

// SIM parameters
PARAM_SIM_CARRIER_ID("A138"),
PARAM_SIM_CARRIER_ID_NAME("A139"),
PARAM_MANUFACTURER_CODE("A140"),
PARAM_SIM_SPECIFIC_CARRIER_ID("A141"),
PARAM_SIM_SPECIFIC_CARRIER_ID_NAME("A142"),
PARAM_MULTI_SIM_SUPPORTED("A143"),
PARAM_SUBSCRIPTION_ID("A145"),

PARAM_6GHZ_BAND_SUPPORTED("A146"),
PARAM_PASSPOINT_FQDN("A147"),
PARAM_PASSPOINT_PROVIDER_FRIENDLY_NAME("A148"),
PARAM_BONDED_DEVICES_ALIAS("A149"),
PARAM_RTT_CALLING_MODE("A150"),
PARAM_SECURE_FRP_MODE("A151"),
PARAM_APPLY_RAMPING_RINGER("A152"),
PARAM_HARDWARE_SKU("A153"),
PARAM_SOC_MANUFACTURER("A154"),
PARAM_SOC_MODEL("A155");

override fun toString(): String {
return code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,17 @@ import java.util.HashMap
*/
internal class DeviceParamNotAvailableFactoryImpl internal constructor(
private val apiVersion: Int,
private val hardwareIdSupplier: Supplier<HardwareId>
) : DeviceParamNotAvailableFactory {

internal constructor(
hardwareIdSupplier: Supplier<HardwareId>
) : this(
internal constructor() : this(
Build.VERSION.SDK_INT,
hardwareIdSupplier
)

override fun create(): Map<String, String> {
return marketOrRegionRestrictionParams
.plus(platformVersionParams)
.plus(permissionParams)
.plus(unavailableParams)
}

@VisibleForTesting
Expand All @@ -39,8 +36,26 @@ internal class DeviceParamNotAvailableFactoryImpl internal constructor(
DeviceParam.PARAM_OS_VERSION,
DeviceParam.PARAM_LOCALE,
DeviceParam.PARAM_TIME_ZONE,
DeviceParam.PARAM_HARDWARE_ID,
DeviceParam.PARAM_SCREEN_RESOLUTION
DeviceParam.PARAM_SCREEN_RESOLUTION,
DeviceParam.PARAM_SDK_APP_ID,
DeviceParam.PARAM_SDK_VERSION,
DeviceParam.PARAM_SDK_REF_NUMBER,
DeviceParam.PARAM_DATE_TIME,
DeviceParam.PARAM_SDK_TRANS_ID,
DeviceParam.PARAM_WEB_VIEW_USER_AGENT,
DeviceParam.PARAM_SIM_CARRIER_ID,
DeviceParam.PARAM_SECURE_FRP_MODE,
DeviceParam.PARAM_APPLY_RAMPING_RINGER,
DeviceParam.PARAM_HARDWARE_SKU,
DeviceParam.PARAM_SOC_MANUFACTURER,
DeviceParam.PARAM_SOC_MODEL,
DeviceParam.PARAM_SIM_CARRIER_ID_NAME,
DeviceParam.PARAM_MANUFACTURER_CODE,
DeviceParam.PARAM_SIM_SPECIFIC_CARRIER_ID,
DeviceParam.PARAM_SIM_SPECIFIC_CARRIER_ID_NAME,
DeviceParam.PARAM_MULTI_SIM_SUPPORTED,
DeviceParam.PARAM_SUBSCRIPTION_ID,
DeviceParam.PARAM_RTT_CALLING_MODE
)

DeviceParam.entries.forEach {
Expand Down Expand Up @@ -97,6 +112,44 @@ internal class DeviceParamNotAvailableFactoryImpl internal constructor(
Reason.PLATFORM_VERSION.toString()
}

if (apiVersion < Build.VERSION_CODES.P) {
params[DeviceParam.PARAM_SIM_CARRIER_ID.toString()] =
Reason.PLATFORM_VERSION.toString()
params[DeviceParam.PARAM_SIM_CARRIER_ID_NAME.toString()] =
Reason.PLATFORM_VERSION.toString()
params[DeviceParam.PARAM_RTT_CALLING_MODE.toString()] =
Reason.PLATFORM_VERSION.toString()
}

if (apiVersion < Build.VERSION_CODES.Q) {
params[DeviceParam.PARAM_SIM_SPECIFIC_CARRIER_ID.toString()] =
Reason.PLATFORM_VERSION.toString()
params[DeviceParam.PARAM_SIM_SPECIFIC_CARRIER_ID_NAME.toString()] =
Reason.PLATFORM_VERSION.toString()
params[DeviceParam.PARAM_MULTI_SIM_SUPPORTED.toString()] =
Reason.PLATFORM_VERSION.toString()
params[DeviceParam.PARAM_APPLY_RAMPING_RINGER.toString()] =
Reason.PLATFORM_VERSION.toString()
}

if (apiVersion < Build.VERSION_CODES.R) {
params[DeviceParam.PARAM_SUBSCRIPTION_ID.toString()] =
Reason.PLATFORM_VERSION.toString()
params[DeviceParam.PARAM_BONDED_DEVICES_ALIAS.toString()] =
Reason.PLATFORM_VERSION.toString()
params[DeviceParam.PARAM_SECURE_FRP_MODE.toString()] =
Reason.PLATFORM_VERSION.toString()
}

if (apiVersion < Build.VERSION_CODES.S) {
params[DeviceParam.PARAM_HARDWARE_SKU.toString()] =
Reason.PLATFORM_VERSION.toString()
params[DeviceParam.PARAM_SOC_MANUFACTURER.toString()] =
Reason.PLATFORM_VERSION.toString()
params[DeviceParam.PARAM_SOC_MODEL.toString()] =
Reason.PLATFORM_VERSION.toString()
}

return params
}

Expand Down Expand Up @@ -131,10 +184,6 @@ internal class DeviceParamNotAvailableFactoryImpl internal constructor(
params[DeviceParam.PARAM_LATITUDE.toString()] = Reason.PERMISSION.toString()
params[DeviceParam.PARAM_LONGITUDE.toString()] = Reason.PERMISSION.toString()

if (!hardwareIdSupplier.get().isPresent) {
params[DeviceParam.PARAM_HARDWARE_ID.toString()] = Reason.PLATFORM_VERSION.toString()
}

params[DeviceParam.PARAM_DEVICE_NAME.toString()] =
Reason.PERMISSION.toString()
params[DeviceParam.PARAM_BLUETOOTH_ADDRESS.toString()] =
Expand Down Expand Up @@ -168,13 +217,33 @@ internal class DeviceParamNotAvailableFactoryImpl internal constructor(
params[DeviceParam.PARAM_SECURE_INSTALL_NON_MARKET_APPS.toString()] =
Reason.PERMISSION.toString()

params[DeviceParam.PARAM_6GHZ_BAND_SUPPORTED.toString()] =
Reason.PERMISSION.toString()
params[DeviceParam.PARAM_PASSPOINT_FQDN.toString()] =
Reason.PERMISSION.toString()
params[DeviceParam.PARAM_PASSPOINT_PROVIDER_FRIENDLY_NAME.toString()] =
Reason.PERMISSION.toString()
params[DeviceParam.PARAM_BONDED_DEVICES_ALIAS.toString()] =
Reason.PERMISSION.toString()

return params
}

private val unavailableParams: Map<String, String>
get() {
val params = HashMap<String, String>()

params[DeviceParam.PARAM_MANUFACTURER_CODE.toString()] =
Reason.UNAVAILABLE.toString()

return params
}

internal enum class Reason(private val code: String) {
MARKET_OR_REGION_RESTRICTION("RE01"),
PLATFORM_VERSION("RE02"),
PERMISSION("RE03");
PERMISSION("RE03"),
UNAVAILABLE("RE04");

override fun toString(): String {
return code
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Context
import androidx.annotation.VisibleForTesting
import com.stripe.android.stripe3ds2.exceptions.InvalidInputException
import com.stripe.android.stripe3ds2.exceptions.SDKRuntimeException
import com.stripe.android.stripe3ds2.init.AppInfoRepository
import com.stripe.android.stripe3ds2.init.DefaultAppInfoRepository
import com.stripe.android.stripe3ds2.init.DefaultSecurityChecker
import com.stripe.android.stripe3ds2.init.DeviceDataFactoryImpl
Expand Down Expand Up @@ -96,6 +97,7 @@ class StripeThreeDs2ServiceImpl @VisibleForTesting internal constructor(
HardwareIdSupplier(context),
DefaultSecurityChecker(),
MessageVersionRegistry(),
DefaultAppInfoRepository(context, workContext),
workContext
)

Expand All @@ -108,6 +110,7 @@ class StripeThreeDs2ServiceImpl @VisibleForTesting internal constructor(
hardwareIdSupplier: HardwareIdSupplier,
securityChecker: SecurityChecker,
messageVersionRegistry: MessageVersionRegistry,
appInfoRepository: AppInfoRepository,
workContext: CoroutineContext
) : this(
messageVersionRegistry = messageVersionRegistry,
Expand All @@ -117,14 +120,13 @@ class StripeThreeDs2ServiceImpl @VisibleForTesting internal constructor(
DefaultAuthenticationRequestParametersFactory(
DeviceDataFactoryImpl(
context = context.applicationContext,
hardwareIdSupplier = hardwareIdSupplier
),
DeviceParamNotAvailableFactoryImpl(
hardwareIdSupplier
appInfoRepository = appInfoRepository,
messageVersionRegistry = messageVersionRegistry
),
DeviceParamNotAvailableFactoryImpl(),
securityChecker,
ephemeralKeyPairGenerator,
DefaultAppInfoRepository(context, workContext),
appInfoRepository,
messageVersionRegistry,
sdkReferenceNumber,
errorReporter,
Expand Down
Loading
Loading