Skip to content

Commit

Permalink
Update fingerprinting logic (stripe#2584)
Browse files Browse the repository at this point in the history
Summary
Use server-defined values instead of generating on the client

Motivation
ANDROID-550

Testing
Updated tests
  • Loading branch information
mshafrir-stripe authored Jun 12, 2020
1 parent 2860008 commit 4c61be2
Show file tree
Hide file tree
Showing 26 changed files with 228 additions and 335 deletions.

This file was deleted.

This file was deleted.

32 changes: 19 additions & 13 deletions stripe/src/main/java/com/stripe/android/FingerprintData.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,29 @@
package com.stripe.android

import com.stripe.android.model.StripeModel
import java.util.concurrent.TimeUnit
import kotlinx.android.parcel.Parcelize
import org.json.JSONObject

@Parcelize
internal data class FingerprintData(
internal val guid: String? = null,
internal val guid: String,
internal val muid: String,
internal val sid: String,
internal val timestamp: Long = 0L
) {
) : StripeModel {
val params: Map<String, String>
get() = mapOf(
KEY_GUID to guid,
KEY_MUID to muid,
KEY_SID to sid
)

fun toJson(): JSONObject {
return JSONObject()
.put(KEY_GUID, guid)
.put(KEY_MUID, muid)
.put(KEY_SID, sid)
.put(KEY_TIMESTAMP, timestamp)
}

Expand All @@ -18,18 +32,10 @@ internal data class FingerprintData(
}

internal companion object {
fun fromJson(json: JSONObject): FingerprintData {
return FingerprintData(
guid = json.optString(KEY_GUID),
timestamp = json.optLong(
KEY_TIMESTAMP,
-1
)
)
}

private const val KEY_GUID = "guid"
private const val KEY_TIMESTAMP = "timestamp"
private const val KEY_MUID = "muid"
private const val KEY_SID = "sid"
internal const val KEY_TIMESTAMP = "timestamp"

private val TTL = TimeUnit.MINUTES.toMillis(30L)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ internal interface FingerprintDataRepository {
if (Stripe.advancedFraudSignalsEnabled) {
coroutineScope.launch {
Transformations.switchMap(store.get()) { localFingerprintData ->
if (localFingerprintData.isExpired(timestampSupplier())) {
if (localFingerprintData == null ||
localFingerprintData.isExpired(timestampSupplier())) {
fingerprintRequestExecutor.execute(
request = fingerprintRequestFactory.create(
localFingerprintData.guid
localFingerprintData
)
)
} else {
Expand Down
15 changes: 9 additions & 6 deletions stripe/src/main/java/com/stripe/android/FingerprintDataStore.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,13 @@ package com.stripe.android
import android.content.Context
import androidx.lifecycle.LiveData
import androidx.lifecycle.liveData
import com.stripe.android.model.parsers.FingerprintDataJsonParser
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
import org.json.JSONObject

internal interface FingerprintDataStore {
fun get(): LiveData<FingerprintData>
fun get(): LiveData<FingerprintData?>
fun save(fingerprintData: FingerprintData)

class Default(
Expand All @@ -21,13 +22,15 @@ internal interface FingerprintDataStore {
)
}

override fun get() = liveData<FingerprintData>(coroutineDispatcher) {
override fun get() = liveData<FingerprintData?>(coroutineDispatcher) {
emit(
runCatching {
FingerprintData.fromJson(
JSONObject(prefs.getString(KEY_DATA, null).orEmpty())
)
}.getOrDefault(FingerprintData())
val json = JSONObject(prefs.getString(KEY_DATA, null).orEmpty())
val timestampSupplier = {
json.optLong(FingerprintData.KEY_TIMESTAMP, -1)
}
FingerprintDataJsonParser(timestampSupplier).parse(json)
}.getOrNull()
)
}

Expand Down
20 changes: 6 additions & 14 deletions stripe/src/main/java/com/stripe/android/FingerprintParamsUtils.kt
Original file line number Diff line number Diff line change
@@ -1,23 +1,15 @@
package com.stripe.android

import android.content.Context
import androidx.annotation.VisibleForTesting
import com.stripe.android.model.ConfirmPaymentIntentParams
import com.stripe.android.model.ConfirmStripeIntentParams.Companion.PARAM_PAYMENT_METHOD_DATA

/**
* Utility class for adding fingerprint data to API params
*/
internal class FingerprintParamsUtils @VisibleForTesting constructor(
private val apiFingerprintParamsFactory: ApiFingerprintParamsFactory
) {
constructor(context: Context) : this(
ApiFingerprintParamsFactory(context)
)

internal class FingerprintParamsUtils {
internal fun addFingerprintData(
params: Map<String, *>,
fingerprintGuid: String?
fingerprintData: FingerprintData?
): Map<String, *> {
return setOf(ConfirmPaymentIntentParams.PARAM_SOURCE_DATA, PARAM_PAYMENT_METHOD_DATA)
.firstOrNull { key ->
Expand All @@ -26,21 +18,21 @@ internal class FingerprintParamsUtils @VisibleForTesting constructor(
addFingerprintData(
params,
key,
fingerprintGuid
fingerprintData
)
} ?: params
}

private fun addFingerprintData(
stripeIntentParams: Map<String, *>,
key: String,
fingerprintGuid: String?
fingerprintData: FingerprintData?
): Map<String, *> {
return (stripeIntentParams[key] as? Map<*, *>)?.let {
stripeIntentParams.plus(
mapOf(key to it.plus(
apiFingerprintParamsFactory.createParams(fingerprintGuid)
))
fingerprintData?.params.orEmpty())
)
)
} ?: stripeIntentParams
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,6 @@ internal class FingerprintRequest(
}

private companion object {
private const val URL = "https://m.stripe.com/4"
private const val URL = "https://m.stripe.com/6"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.stripe.android

import androidx.lifecycle.LiveData
import androidx.lifecycle.liveData
import com.stripe.android.model.parsers.FingerprintDataJsonParser
import java.util.Calendar
import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers
Expand Down Expand Up @@ -34,10 +35,8 @@ internal interface FingerprintRequestExecutor {
connectionFactory.create(request).use { conn ->
return runCatching {
conn.response.takeIf { it.isOk }?.let {
FingerprintData(
guid = it.body,
timestamp = timestampSupplier()
)
FingerprintDataJsonParser(timestampSupplier)
.parse(it.responseJson)
}
}.getOrNull()
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,16 +5,16 @@ import androidx.annotation.VisibleForTesting

internal class FingerprintRequestFactory @VisibleForTesting internal constructor(
private val fingerprintRequestParamsFactory: FingerprintRequestParamsFactory
) : Factory1<String?, FingerprintRequest> {
) : Factory1<FingerprintData?, FingerprintRequest> {

internal constructor(context: Context) : this(
fingerprintRequestParamsFactory = FingerprintRequestParamsFactory(context)
)

override fun create(arg: String?): FingerprintRequest {
override fun create(arg: FingerprintData?): FingerprintRequest {
return FingerprintRequest(
params = fingerprintRequestParamsFactory.createParams(),
guid = arg.orEmpty()
params = fingerprintRequestParamsFactory.createParams(arg),
guid = arg?.guid.orEmpty()
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,8 @@ internal class FingerprintRequestParamsFactory @VisibleForTesting internal const
private val displayMetrics: DisplayMetrics,
private val packageName: String,
private val versionName: String?,
private val timeZone: String,
private val clientFingerprintDataStore: ClientFingerprintDataStore
private val timeZone: String
) {

private val screen: String =
"${displayMetrics.widthPixels}w_${displayMetrics.heightPixels}h_${displayMetrics.densityDpi}dpi"

Expand All @@ -29,18 +27,17 @@ internal class FingerprintRequestParamsFactory @VisibleForTesting internal const
displayMetrics = context.resources.displayMetrics,
packageName = context.packageName.orEmpty(),
versionName = context.packageInfo?.versionName,
timeZone = createTimezone(),
clientFingerprintDataStore = ClientFingerprintDataStore.Default(context)
timeZone = createTimezone()
)

@JvmSynthetic
internal fun createParams(): Map<String, Any> {
internal fun createParams(fingerprintData: FingerprintData?): Map<String, Any> {
return mapOf(
"v2" to 1,
"tag" to BuildConfig.VERSION_NAME,
"src" to "android-sdk",
"a" to createFirstMap(),
"b" to createSecondMap()
"b" to createSecondMap(fingerprintData)
)
}

Expand All @@ -53,10 +50,10 @@ internal class FingerprintRequestParamsFactory @VisibleForTesting internal const
)
}

private fun createSecondMap(): Map<String, Any> {
private fun createSecondMap(fingerprintData: FingerprintData?): Map<String, Any> {
return mapOf(
"d" to clientFingerprintDataStore.getMuid(),
"e" to clientFingerprintDataStore.getSid(),
"d" to fingerprintData?.muid.orEmpty(),
"e" to fingerprintData?.sid.orEmpty(),
"k" to packageName,
"o" to Build.VERSION.RELEASE,
"p" to Build.VERSION.SDK_INT,
Expand Down
Loading

0 comments on commit 4c61be2

Please sign in to comment.