Skip to content

Commit

Permalink
Abstract Link Attestation Check
Browse files Browse the repository at this point in the history
  • Loading branch information
toluo-stripe committed Feb 7, 2025
1 parent 027b9c8 commit 2696bd5
Show file tree
Hide file tree
Showing 8 changed files with 256 additions and 25 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.stripe.android.link.attestation

import com.stripe.android.link.LinkConfiguration
import com.stripe.android.link.account.LinkAccountManager
import com.stripe.android.link.account.LinkAuth
import com.stripe.android.link.account.LinkAuthResult
import com.stripe.android.link.gate.LinkGate
import com.stripe.android.model.EmailSource
import com.stripe.attestation.IntegrityRequestManager
import javax.inject.Inject

internal class DefaultLinkAttestationCheck @Inject constructor(
private val linkGate: LinkGate,
private val linkAuth: LinkAuth,
private val integrityRequestManager: IntegrityRequestManager,
private val linkAccountManager: LinkAccountManager,
private val linkConfiguration: LinkConfiguration
) : LinkAttestationCheck {
override suspend fun invoke(): LinkAttestationCheck.Result {
if (linkGate.useAttestationEndpoints.not()) return LinkAttestationCheck.Result.Successful
val result = integrityRequestManager.prepare()

return result.fold(
onSuccess = {
val email = linkAccountManager.linkAccount.value?.email
?: linkConfiguration.customerInfo.email
if (email == null) return@fold LinkAttestationCheck.Result.Successful
val lookupResult = linkAuth.lookUp(
email = email,
emailSource = EmailSource.CUSTOMER_OBJECT,
startSession = false
)
when (lookupResult) {
is LinkAuthResult.AttestationFailed -> {
LinkAttestationCheck.Result.AttestationFailed(lookupResult.throwable)
}
is LinkAuthResult.Error -> {
LinkAttestationCheck.Result.Error(lookupResult.throwable)
}
else -> LinkAttestationCheck.Result.Successful
}
},
onFailure = { error ->
LinkAttestationCheck.Result.AttestationFailed(error)
}
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.stripe.android.link.attestation

interface LinkAttestationCheck {
suspend fun invoke(): Result

sealed interface Result {
data object Successful : Result
data class AttestationFailed(val error: Throwable) : Result
data class Error(val error: Throwable) : Result
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.stripe.android.link.injection

import android.app.Application
import dagger.Module
import dagger.Provides
import javax.inject.Named

@Module
object ApplicationIdModule {
@Provides
@Named(APPLICATION_ID)
fun provideApplicationId(
application: Application
): String {
return application.packageName
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.stripe.android.link.injection

import android.app.Application
import com.stripe.android.BuildConfig
import com.stripe.android.core.Logger
import com.stripe.attestation.IntegrityRequestManager
import com.stripe.attestation.IntegrityStandardRequestManager
import com.stripe.attestation.RealStandardIntegrityManagerFactory
import dagger.Module
import dagger.Provides

@Module
object IntegrityRequestManagerModule {
@Provides
fun providesIntegrityStandardRequestManager(
context: Application
): IntegrityRequestManager = IntegrityStandardRequestManager(
cloudProjectNumber = 577365562050, // stripe-payments-sdk-prod
logError = { message, error ->
Logger.getInstance(BuildConfig.DEBUG).error(message, error)
},
factory = RealStandardIntegrityManagerFactory(context)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ internal annotation class NativeLinkScope
modules = [
NativeLinkModule::class,
LinkViewModelModule::class,
IntegrityRequestManagerModule::class,
ApplicationIdModule::class,
DefaultConfirmationModule::class,
]
)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package com.stripe.android.link.injection

import android.app.Application
import android.content.Context
import androidx.core.os.LocaleListCompat
import androidx.lifecycle.SavedStateHandle
Expand Down Expand Up @@ -46,9 +45,6 @@ import com.stripe.android.paymentsheet.analytics.DefaultEventReporter
import com.stripe.android.paymentsheet.analytics.EventReporter
import com.stripe.android.repository.ConsumersApiService
import com.stripe.android.repository.ConsumersApiServiceImpl
import com.stripe.attestation.IntegrityRequestManager
import com.stripe.attestation.IntegrityStandardRequestManager
import com.stripe.attestation.RealStandardIntegrityManagerFactory
import dagger.Binds
import dagger.Module
import dagger.Provides
Expand Down Expand Up @@ -191,29 +187,8 @@ internal interface NativeLinkModule {
factory: DefaultLinkConfirmationHandler.Factory
): LinkConfirmationHandler.Factory = factory

@Provides
@NativeLinkScope
fun providesIntegrityStandardRequestManager(
context: Application
): IntegrityRequestManager = IntegrityStandardRequestManager(
cloudProjectNumber = 577365562050, // stripe-payments-sdk-prod
logError = { message, error ->
Logger.getInstance(BuildConfig.DEBUG).error(message, error)
},
factory = RealStandardIntegrityManagerFactory(context)
)

@Provides
@NativeLinkScope
fun provideEventReporterMode(): EventReporter.Mode = EventReporter.Mode.Custom

@Provides
@NativeLinkScope
@Named(APPLICATION_ID)
fun provideApplicationId(
application: Application
): String {
return application.packageName
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package com.stripe.android.link.attestation

import com.google.common.truth.Truth.assertThat
import com.stripe.android.link.FakeIntegrityRequestManager
import com.stripe.android.link.LinkConfiguration
import com.stripe.android.link.TestFactory
import com.stripe.android.link.account.FakeLinkAccountManager
import com.stripe.android.link.account.FakeLinkAuth
import com.stripe.android.link.account.LinkAccountManager
import com.stripe.android.link.account.LinkAuth
import com.stripe.android.link.account.LinkAuthResult
import com.stripe.android.link.gate.FakeLinkGate
import com.stripe.android.link.gate.LinkGate
import com.stripe.android.testing.CoroutineTestRule
import com.stripe.attestation.IntegrityRequestManager
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test

internal class DefaultLinkAttestationCheckTest {
@get:Rule
val testRule = CoroutineTestRule()

@Test
fun `attestation check should be successful when useAttestationEndpoints is false`() = runTest {
val linkGate = FakeLinkGate()
linkGate.setUseAttestationEndpoints(false)

val attestationCheck = attestationCheck(linkGate = linkGate)

assertThat(attestationCheck.invoke())
.isEqualTo(LinkAttestationCheck.Result.Successful)
}

@Test
fun `attestation check should be successful when there is no email for lookup`() = runTest {
val linkGate = FakeLinkGate()
val linkAccountManager = FakeLinkAccountManager()
linkGate.setUseAttestationEndpoints(true)
linkAccountManager.setLinkAccount(null)

val attestationCheck = attestationCheck(
linkGate = linkGate,
linkAccountManager = linkAccountManager,
linkConfiguration = TestFactory.LINK_CONFIGURATION.copy(
customerInfo = TestFactory.LINK_CUSTOMER_INFO.copy(
email = null
)
)
)

assertThat(attestationCheck.invoke())
.isEqualTo(LinkAttestationCheck.Result.Successful)
}

@Test
fun `attestation check should return AttestationFailed when integrity preparation fails`() = runTest {
val error = Throwable("oops")
val integrityRequestManager = FakeIntegrityRequestManager()
integrityRequestManager.prepareResult = Result.failure(error)

val attestationCheck = attestationCheck(integrityRequestManager = integrityRequestManager)

assertThat(attestationCheck.invoke())
.isEqualTo(LinkAttestationCheck.Result.AttestationFailed(error))
}

@Test
fun `attestation check should return AttestationFailed when lookup returns AttestationFailed`() = runTest {
val error = Throwable("oops")
val linkGate = FakeLinkGate()
val linkAuth = FakeLinkAuth()
linkAuth.lookupResult = LinkAuthResult.AttestationFailed(error)

val attestationCheck = attestationCheck(linkGate = linkGate, linkAuth = linkAuth)

assertThat(attestationCheck.invoke())
.isEqualTo(LinkAttestationCheck.Result.AttestationFailed(error))
}

@Test
fun `attestation check should return Successful when lookup returns NoLinkAccountFound`() = runTest {
val linkGate = FakeLinkGate()
val linkAuth = FakeLinkAuth()
linkAuth.lookupResult = LinkAuthResult.NoLinkAccountFound

val attestationCheck = attestationCheck(linkGate = linkGate, linkAuth = linkAuth)

assertThat(attestationCheck.invoke())
.isEqualTo(LinkAttestationCheck.Result.Successful)
}

@Test
fun `attestation check should return Successful when lookup returns success`() = runTest {
val linkGate = FakeLinkGate()
val linkAuth = FakeLinkAuth()
linkAuth.lookupResult = LinkAuthResult.Success(TestFactory.LINK_ACCOUNT)

val attestationCheck = attestationCheck(linkGate = linkGate, linkAuth = linkAuth)

assertThat(attestationCheck.invoke())
.isEqualTo(LinkAttestationCheck.Result.Successful)
}

@Test
fun `attestation check should return Error when lookup returns error`() = runTest {
val error = Throwable("oops")
val linkGate = FakeLinkGate()
val linkAuth = FakeLinkAuth()
linkAuth.lookupResult = LinkAuthResult.Error(error)

val attestationCheck = attestationCheck(linkGate = linkGate, linkAuth = linkAuth)

assertThat(attestationCheck.invoke())
.isEqualTo(LinkAttestationCheck.Result.Error(error))
}

private fun attestationCheck(
linkGate: LinkGate = FakeLinkGate(),
linkAuth: LinkAuth = FakeLinkAuth(),
integrityRequestManager: IntegrityRequestManager = FakeIntegrityRequestManager(),
linkAccountManager: LinkAccountManager = FakeLinkAccountManager(),
linkConfiguration: LinkConfiguration = TestFactory.LINK_CONFIGURATION
): DefaultLinkAttestationCheck {
return DefaultLinkAttestationCheck(
linkGate = linkGate,
linkAuth = linkAuth,
integrityRequestManager = integrityRequestManager,
linkAccountManager = linkAccountManager,
linkConfiguration = linkConfiguration
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.stripe.android.link.attestation

import app.cash.turbine.Turbine

internal class FakeLinkAttestationCheck : LinkAttestationCheck {
var result: LinkAttestationCheck.Result = LinkAttestationCheck.Result.Successful
private val calls = Turbine<Unit>()

override suspend fun invoke(): LinkAttestationCheck.Result {
calls.add(Unit)
return result
}

suspend fun awaitInvokeCall() {
calls.awaitItem()
}

fun ensureAllEventsConsumed() {
calls.ensureAllEventsConsumed()
}
}

0 comments on commit 2696bd5

Please sign in to comment.