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

feat: Split app integrity logic from send transfer logic #277

Merged
merged 7 commits into from
Dec 20, 2024
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ import com.infomaniak.swisstransfer.ui.screen.newtransfer.ImportFilesViewModel.A
import com.infomaniak.swisstransfer.ui.screen.newtransfer.ImportFilesViewModel.SendActionResult
import com.infomaniak.swisstransfer.workers.UploadWorker
import dagger.hilt.android.scopes.ViewModelScoped
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
Expand All @@ -53,58 +53,98 @@ class TransferSendManager @Inject constructor(
private val _integrityCheckResult = MutableStateFlow(AppIntegrityResult.Idle)
val integrityCheckResult = _integrityCheckResult.asStateFlow()

//region App Integrity
suspend fun sendTransfer(newUploadSession: NewUploadSession) {
_integrityCheckResult.value = AppIntegrityResult.Ongoing
coroutineScope {
runCatching {
appIntegrityManager.getChallenge(
onSuccess = { requestAppIntegrityToken(appIntegrityManager, newUploadSession) },
onFailure = ::setFailedIntegrityResult,
)
}.onFailure { exception ->
SentryLog.e(TAG, "Failed to start the upload", exception)

withIntegrityToken(
onSuccess = { attestationToken -> sendTransfer(newUploadSession, attestationToken) },
onRefused = { _integrityCheckResult.value = AppIntegrityResult.Fail },
onFailure = { exception ->
if (exception !is CancellationException) {
SentryLog.e(TAG, "Integrity token received an exception", exception)
} else {
SentryLog.i(TAG, "Integrity token received an exception", exception)
}
_sendActionResult.update { SendActionResult.Failure }
}
)
}

private suspend fun sendTransfer(newUploadSession: NewUploadSession, attestationToken: String) {
_integrityCheckResult.value = AppIntegrityResult.Success
_sendActionResult.update { SendActionResult.Pending }

runCatching {
val uuid = uploadManager.createAndGetUpload(newUploadSession).uuid
uploadManager.initUploadSession(
attestationHeaderName = AppIntegrityManager.ATTESTATION_TOKEN_HEADER,
attestationToken = attestationToken,
)!! // TODO: Handle ContainerErrorsException here
uploadWorkerScheduler.scheduleWork(uuid)
_sendActionResult.update {
val totalSize = importationFilesManager.importedFiles.value.sumOf { it.fileSize }
SendActionResult.Success(totalSize)
}
}.onFailure { exception ->
SentryLog.e(TAG, "Failed to start the upload", exception)
_sendActionResult.update { SendActionResult.Failure }
}
}

private fun CoroutineScope.requestAppIntegrityToken(
appIntegrityManager: AppIntegrityManager,
newUploadSession: NewUploadSession,
//region App Integrity
private suspend inline fun withIntegrityToken(
onSuccess: (attestationToken: String) -> Unit,
onRefused: () -> Unit = {},
onFailure: (exception: Throwable) -> Unit = {},
) {
appIntegrityManager.requestClassicIntegrityVerdictToken(
onSuccess = { token ->
SentryLog.i(APP_INTEGRITY_MANAGER_TAG, "request for app integrity token successful $token")
getApiIntegrityVerdict(appIntegrityManager, token, newUploadSession)
},
onFailure = ::setFailedIntegrityResult,
)
runCatching {
var attestationToken: String? = null

coroutineScope {
appIntegrityManager.getChallenge(
onSuccess = { launch { attestationToken = requestAppIntegrityToken(appIntegrityManager) } },
onFailure = {},
)
}

attestationToken?.let(onSuccess) ?: onRefused()
}.onFailure {
onFailure.invoke(it)
}
}

private fun CoroutineScope.getApiIntegrityVerdict(
appIntegrityManager: AppIntegrityManager,
appIntegrityToken: String,
newUploadSession: NewUploadSession,
) {
launch {
appIntegrityManager.getApiIntegrityVerdict(
integrityToken = appIntegrityToken,
packageName = BuildConfig.APPLICATION_ID,
targetUrl = sharedApiUrlCreator.createUploadContainerUrl,
onSuccess = { attestationToken ->
SentryLog.i(APP_INTEGRITY_MANAGER_TAG, "Api verdict check")
Log.i(APP_INTEGRITY_MANAGER_TAG, "getApiIntegrityVerdict: $attestationToken")
_integrityCheckResult.value = AppIntegrityResult.Success
sendTransfer(attestationToken, newUploadSession)
private suspend fun requestAppIntegrityToken(appIntegrityManager: AppIntegrityManager): String? {
var attestationToken: String? = null

coroutineScope {
appIntegrityManager.requestClassicIntegrityVerdictToken(
onSuccess = { token ->
SentryLog.i(APP_INTEGRITY_MANAGER_TAG, "request for app integrity token successful")
launch { attestationToken = getApiIntegrityVerdict(appIntegrityManager, token) }
},
onFailure = ::setFailedIntegrityResult,
onFailure = {},
)
}

return attestationToken
}

private fun setFailedIntegrityResult() {
_integrityCheckResult.value = AppIntegrityResult.Fail
private suspend fun getApiIntegrityVerdict(appIntegrityManager: AppIntegrityManager, appIntegrityToken: String): String? {
var token: String? = null

appIntegrityManager.getApiIntegrityVerdict(
integrityToken = appIntegrityToken,
packageName = BuildConfig.APPLICATION_ID,
targetUrl = sharedApiUrlCreator.createUploadContainerUrl,
onSuccess = { attestationToken ->
SentryLog.i(APP_INTEGRITY_MANAGER_TAG, "Api verdict check")
Log.i(APP_INTEGRITY_MANAGER_TAG, "getApiIntegrityVerdict: $attestationToken")
token = attestationToken
},
onFailure = {},
)

return token
}

fun resetIntegrityCheckResult() {
Expand All @@ -116,27 +156,6 @@ class TransferSendManager @Inject constructor(
_sendActionResult.value = SendActionResult.NotStarted
}

private fun CoroutineScope.sendTransfer(attestationToken: String, newUploadSession: NewUploadSession) {
_sendActionResult.update { SendActionResult.Pending }
launch {
runCatching {
val uuid = uploadManager.createAndGetUpload(newUploadSession).uuid
uploadManager.initUploadSession(
attestationHeaderName = AppIntegrityManager.ATTESTATION_TOKEN_HEADER,
attestationToken = attestationToken,
)!! // TODO: Handle ContainerErrorsException here
uploadWorkerScheduler.scheduleWork(uuid)
_sendActionResult.update {
val totalSize = importationFilesManager.importedFiles.value.sumOf { it.fileSize }
SendActionResult.Success(totalSize)
}
}.onFailure { exception ->
SentryLog.e(TAG, "Failed to start the upload", exception)
_sendActionResult.update { SendActionResult.Failure }
}
}
}

companion object {
private val TAG = TransferSendManager::class.java.simpleName
}
Expand Down
Loading