mirror of
https://github.com/ProtonDriveApps/android-drive.git
synced 2026-06-14 09:54:34 +00:00
1.3.1
This commit is contained in:
+11
-3
@@ -1,5 +1,7 @@
|
||||
default:
|
||||
image: ${CI_REGISTRY}/android/shared/docker-android:v1.1.1
|
||||
tags:
|
||||
- shared-small
|
||||
|
||||
variables:
|
||||
# Use fastzip to improve cache times
|
||||
@@ -54,7 +56,7 @@ prepare-build:
|
||||
expire_in: 1 week
|
||||
cache: []
|
||||
tags:
|
||||
- large
|
||||
- shared-large
|
||||
script:
|
||||
- export ARCHIVES_BASE_NAME=$(./gradlew -Dorg.gradle.jvmargs=-Xmx1024m -q getArchivesName | grep "\[ARCHIVES_NAME\]" | sed 's/\[ARCHIVES_NAME\]//')
|
||||
- export ARCHIVES_VERSION=$(./gradlew -Dorg.gradle.jvmargs=-Xmx1024m -q getArchivesVersion | grep "\[ARCHIVES_VERSION\]" | sed 's/\[ARCHIVES_VERSION\]//')
|
||||
@@ -65,6 +67,8 @@ prepare-build:
|
||||
|
||||
prepare-gradle-build-scan:
|
||||
stage: prepare
|
||||
tags:
|
||||
- shared-large
|
||||
script:
|
||||
- echo "BUILD_SCAN_PUBLISH=true" >> build_scan.env
|
||||
artifacts:
|
||||
@@ -77,7 +81,7 @@ prepare-gradle-build-scan:
|
||||
detekt analysis:
|
||||
stage: analyze
|
||||
tags:
|
||||
- large
|
||||
- shared-large
|
||||
script:
|
||||
- ./gradlew multiModuleDetekt --configuration-cache-problems=warn
|
||||
allow_failure: true
|
||||
@@ -179,6 +183,8 @@ dev debug unit test:
|
||||
|
||||
upload to firebase:
|
||||
stage: startReview
|
||||
tags:
|
||||
- shared-medium
|
||||
variables:
|
||||
PRODUCT_FLAVOR: "dynamic"
|
||||
script:
|
||||
@@ -202,7 +208,7 @@ upload to firebase:
|
||||
- job: "build dynamic debug"
|
||||
stage: test
|
||||
tags:
|
||||
- medium
|
||||
- shared-medium
|
||||
variables:
|
||||
RESULTS_DIR: "$FIREBASE_RESULT_ROOT/$CI_JOB_NAME"
|
||||
PRODUCT_FLAVOR: "dynamic"
|
||||
@@ -357,6 +363,8 @@ startReview:
|
||||
needs:
|
||||
- job: "prepare-build"
|
||||
- job: "build dev debug"
|
||||
tags:
|
||||
- shared-medium
|
||||
variables:
|
||||
PRODUCT_FLAVOR: "dev"
|
||||
before_script:
|
||||
|
||||
@@ -51,6 +51,7 @@ driveModule(
|
||||
implementation(project(":app-lock"))
|
||||
implementation(project(":app-ui-settings"))
|
||||
implementation(project(":drive"))
|
||||
implementation(project(":verifier"))
|
||||
|
||||
implementation(libs.androidx.activity.ktx)
|
||||
implementation(libs.androidx.compose.foundationLayout)
|
||||
|
||||
@@ -30,8 +30,6 @@ import dagger.hilt.InstallIn
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import me.proton.android.drive.BuildConfig
|
||||
import me.proton.android.drive.lock.data.usecase.BuildAppKeyImpl
|
||||
import me.proton.android.drive.lock.domain.usecase.BuildAppKey
|
||||
import me.proton.android.drive.log.DriveLogger
|
||||
import me.proton.android.drive.notification.AppNotificationBuilderProvider
|
||||
import me.proton.android.drive.notification.AppNotificationEventHandler
|
||||
|
||||
@@ -118,8 +118,8 @@ class UploadFlowTest : BaseTest() {
|
||||
}
|
||||
|
||||
@Test
|
||||
fun upload4MBFile() {
|
||||
val file = externalFilesRule.createFile("4MB.txt", 4 * 1024 * 1024)
|
||||
fun upload6MBFile() {
|
||||
val file = externalFilesRule.createFile("6MB.txt", 6 * 1024 * 1024)
|
||||
|
||||
Intents.intending(hasAction(Intent.ACTION_OPEN_DOCUMENT)).respondWithFunction {
|
||||
Instrumentation.ActivityResult(Activity.RESULT_OK, Intent().setData(Uri.fromFile(file)))
|
||||
@@ -135,7 +135,7 @@ class UploadFlowTest : BaseTest() {
|
||||
assertStageUploading()
|
||||
assertStageUploadedProgress(0)
|
||||
assertStageUploadedProgress(100)
|
||||
itemIsDisplayed("4MB.txt")
|
||||
itemIsDisplayed("6MB.txt")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ object Config {
|
||||
const val minSdk = 23
|
||||
const val targetSdk = 33
|
||||
const val testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
||||
const val versionName = "1.2.3"
|
||||
const val versionName = "1.3.1"
|
||||
const val archivesBaseName = "ProtonDrive-$versionName"
|
||||
val resourceConfigurations = listOf("en")
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ plugins {
|
||||
driveModule(
|
||||
hilt = true,
|
||||
) {
|
||||
api(project(":drive:base:domain"))
|
||||
api(libs.core.domain)
|
||||
}
|
||||
|
||||
|
||||
@@ -127,6 +127,8 @@ object Dto {
|
||||
const val URL_PASSWORD_SALT = "UrlPasswordSalt"
|
||||
const val URLS_EXPIRED = "UrlsExpired"
|
||||
const val USED_SPACE = "UsedSpace"
|
||||
const val VERIFICATION_CODE = "VerificationCode"
|
||||
const val VERIFIER = "Verifier"
|
||||
const val VOLUME = "Volume"
|
||||
const val VOLUMES = "Volumes"
|
||||
const val VOLUME_ID = "VolumeID"
|
||||
|
||||
@@ -26,6 +26,7 @@ object ProtonApiCode {
|
||||
const val ALREADY_EXISTS = 2500
|
||||
const val NOT_EXISTS = 2501
|
||||
const val INSUFFICIENT_QUOTA = 200001
|
||||
const val ENCRYPTION_VERIFICATION_FAILED = 200501
|
||||
|
||||
val Long.isSuccessful: Boolean get() = this == SUCCESS.toLong()
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ object Column {
|
||||
const val URL_PASSWORD_SALT = "url_password_salt"
|
||||
const val USED_SPACE = "used_space"
|
||||
const val USER_ID = "user_id"
|
||||
const val VERIFIER_TOKEN = "verifier_token"
|
||||
const val VOLUME_ID = "volume_id"
|
||||
const val WORK_ID = "work_id"
|
||||
const val WORKER_ID = "worker_id"
|
||||
|
||||
+4
-1
@@ -23,6 +23,7 @@ import me.proton.core.drive.base.data.api.Dto.ENC_SIGNATURE
|
||||
import me.proton.core.drive.base.data.api.Dto.HASH
|
||||
import me.proton.core.drive.base.data.api.Dto.INDEX
|
||||
import me.proton.core.drive.base.data.api.Dto.SIZE
|
||||
import me.proton.core.drive.base.data.api.Dto.VERIFIER
|
||||
|
||||
@Serializable
|
||||
data class UploadBlockDto(
|
||||
@@ -33,5 +34,7 @@ data class UploadBlockDto(
|
||||
@SerialName(ENC_SIGNATURE)
|
||||
val encSignature: String,
|
||||
@SerialName(HASH)
|
||||
val hash: String
|
||||
val hash: String,
|
||||
@SerialName(VERIFIER)
|
||||
val verifier: VerifierDto? = null,
|
||||
)
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Core.
|
||||
*
|
||||
* Proton Core is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Core is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Core. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.drive.block.data.api.entity
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import me.proton.core.drive.base.data.api.Dto.TOKEN
|
||||
|
||||
@Serializable
|
||||
data class VerifierDto(
|
||||
@SerialName(TOKEN)
|
||||
val token: String,
|
||||
)
|
||||
+2
@@ -18,6 +18,7 @@
|
||||
package me.proton.core.drive.block.data.extension
|
||||
|
||||
import me.proton.core.drive.block.data.api.entity.UploadBlockDto
|
||||
import me.proton.core.drive.block.data.api.entity.VerifierDto
|
||||
import me.proton.core.drive.linkupload.domain.entity.UploadBlock
|
||||
|
||||
fun UploadBlock.toUploadBlockDto() =
|
||||
@@ -26,4 +27,5 @@ fun UploadBlock.toUploadBlockDto() =
|
||||
size = size.value,
|
||||
hash = hashSha256,
|
||||
encSignature = encSignature,
|
||||
verifier = verifierToken?.let { token -> VerifierDto(token) },
|
||||
)
|
||||
|
||||
+14
-1
@@ -35,7 +35,7 @@ class UseSessionKey @Inject constructor(
|
||||
) {
|
||||
suspend operator fun <T> invoke(
|
||||
contentKey: ContentKey,
|
||||
checkSignature: Boolean = false,
|
||||
checkSignature: Boolean,
|
||||
coroutineContext: CoroutineContext = CryptoScope.EncryptAndDecryptWithIO.coroutineContext,
|
||||
block: suspend (SessionKey) -> T
|
||||
) =
|
||||
@@ -54,4 +54,17 @@ class UseSessionKey @Inject constructor(
|
||||
}
|
||||
block(sessionKey)
|
||||
}
|
||||
|
||||
suspend operator fun <T> invoke(
|
||||
contentKey: ContentKey,
|
||||
coroutineContext: CoroutineContext = CryptoScope.EncryptAndDecryptWithIO.coroutineContext,
|
||||
block: suspend (SessionKey) -> T
|
||||
) =
|
||||
useSessionKey(
|
||||
decryptKey = contentKey.decryptKey.keyHolder,
|
||||
encryptedKeyPacket = contentKey.encryptedKeyPacket,
|
||||
coroutineContext = coroutineContext,
|
||||
) { sessionKey ->
|
||||
block(sessionKey)
|
||||
}
|
||||
}
|
||||
|
||||
+18
@@ -30,6 +30,24 @@ class DecryptFiles @Inject constructor(
|
||||
private val useSessionKey: UseSessionKey,
|
||||
private val decryptFile: DecryptFile
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
contentKey: ContentKey,
|
||||
checkSignature: Boolean,
|
||||
input: List<File>,
|
||||
output: List<File>,
|
||||
): Result<List<DecryptedFile>> = coRunCatching {
|
||||
useSessionKey(contentKey = contentKey, checkSignature = checkSignature) { sessionKey ->
|
||||
input.mapIndexed { index, file ->
|
||||
DecryptedFile(
|
||||
file = decryptFile(sessionKey, file, output[index]).getOrThrow(),
|
||||
status = VerificationStatus.Unknown,
|
||||
filename = "",
|
||||
lastModifiedEpochSeconds = -1,
|
||||
)
|
||||
}
|
||||
}.getOrThrow()
|
||||
}
|
||||
|
||||
suspend operator fun invoke(
|
||||
contentKey: ContentKey,
|
||||
input: List<File>,
|
||||
|
||||
+2
-1
@@ -32,11 +32,12 @@ class EncryptFiles @Inject constructor(
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
contentKey: ContentKey,
|
||||
checkSignature: Boolean = false,
|
||||
input: List<File>,
|
||||
output: List<File>,
|
||||
coroutineContext: CoroutineContext = CryptoScope.EncryptAndDecryptWithIO.coroutineContext,
|
||||
): Result<List<File>> = coRunCatching {
|
||||
useSessionKey(contentKey = contentKey) { sessionKey ->
|
||||
useSessionKey(contentKey = contentKey, checkSignature = checkSignature) { sessionKey ->
|
||||
input.mapIndexed { index, file ->
|
||||
encryptFile(sessionKey, file, output[index], coroutineContext).getOrThrow()
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -192,6 +192,7 @@ import me.proton.drive.android.settings.data.db.entity.UiSettingsEntity
|
||||
AutoMigration(from = 18, to = 19),
|
||||
AutoMigration(from = 22, to = 23),
|
||||
AutoMigration(from = 23, to = 24),
|
||||
AutoMigration(from = 26, to = 27),
|
||||
],
|
||||
exportSchema = true,
|
||||
)
|
||||
@@ -250,7 +251,7 @@ abstract class DriveDatabase :
|
||||
WorkerDatabase {
|
||||
|
||||
companion object {
|
||||
const val VERSION = 26
|
||||
const val VERSION = 27
|
||||
|
||||
private val migrations = listOf(
|
||||
DriveDatabaseMigrations.MIGRATION_1_2,
|
||||
@@ -278,6 +279,7 @@ abstract class DriveDatabase :
|
||||
//AutoMigration(from = 23, to = 24)
|
||||
DriveDatabaseMigrations.MIGRATION_24_25,
|
||||
DriveDatabaseMigrations.MIGRATION_25_26,
|
||||
//AutoMigration(from = 26, to = 27)
|
||||
)
|
||||
|
||||
fun buildDatabase(context: Context): DriveDatabase =
|
||||
|
||||
@@ -53,4 +53,5 @@
|
||||
<item quantity="one">%1$d item</item>
|
||||
<item quantity="other">%1$d items</item>
|
||||
</plurals>
|
||||
<string name="files_upload_verification_failed">Upload failed: Verification of data failed</string>
|
||||
</resources>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?><!--
|
||||
~ Copyright (c) 2023 Proton AG.
|
||||
~ This file is part of Proton Core.
|
||||
~
|
||||
~ Proton Core is free software: you can redistribute it and/or modify
|
||||
~ it under the terms of the GNU General Public License as published by
|
||||
~ the Free Software Foundation, either version 3 of the License, or
|
||||
~ (at your option) any later version.
|
||||
~
|
||||
~ Proton Core is distributed in the hope that it will be useful,
|
||||
~ but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
~ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
~ GNU General Public License for more details.
|
||||
~
|
||||
~ You should have received a copy of the GNU General Public License
|
||||
~ along with Proton Core. If not, see <https://www.gnu.org/licenses/>.
|
||||
-->
|
||||
|
||||
<resources>
|
||||
<string name="verifier_initialize_failed">Verifier initialization failed</string>
|
||||
<string name="verifier_verify_blocks_failed">Verifier failed verifing blocks</string>
|
||||
</resources>
|
||||
+14
-2
@@ -54,12 +54,24 @@ class BuildContentKey @Inject constructor(
|
||||
userId: UserId,
|
||||
uploadFile: UploadFileLink,
|
||||
fileKey: Key.Node,
|
||||
): Result<ContentKey> = invoke(
|
||||
userId = userId,
|
||||
contentKeyPacket = uploadFile.contentKeyPacket,
|
||||
contentKeyPacketSignature = uploadFile.contentKeyPacketSignature,
|
||||
fileKey = fileKey,
|
||||
)
|
||||
|
||||
suspend operator fun invoke(
|
||||
userId: UserId,
|
||||
contentKeyPacket: String,
|
||||
contentKeyPacketSignature: String,
|
||||
fileKey: Key.Node,
|
||||
): Result<ContentKey> = coRunCatching {
|
||||
contentKeyFactory.createContentKey(
|
||||
decryptKey = fileKey,
|
||||
verifyKey = listOf(fileKey, getAddressKeys(userId, getSignatureAddress(userId))),
|
||||
contentKeyPacket = uploadFile.contentKeyPacket,
|
||||
contentKeyPacketSignature = uploadFile.contentKeyPacketSignature
|
||||
contentKeyPacket = contentKeyPacket,
|
||||
contentKeyPacketSignature = contentKeyPacketSignature
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
+10
@@ -56,4 +56,14 @@ abstract class UploadBlockDao : BaseDao<UploadBlockEntity>() {
|
||||
index: Long,
|
||||
token: String,
|
||||
)
|
||||
|
||||
@Query("""
|
||||
UPDATE UploadBlockEntity SET verifier_token = :verifierToken WHERE
|
||||
upload_link_id = :uploadLinkId AND `index` = :index
|
||||
""")
|
||||
abstract fun updateVerifierToken(
|
||||
uploadLinkId: Long,
|
||||
index: Long,
|
||||
verifierToken: String,
|
||||
)
|
||||
}
|
||||
|
||||
+3
@@ -29,6 +29,7 @@ import me.proton.core.drive.base.data.db.Column.SIZE
|
||||
import me.proton.core.drive.base.data.db.Column.TOKEN
|
||||
import me.proton.core.drive.base.data.db.Column.UPLOAD_LINK_ID
|
||||
import me.proton.core.drive.base.data.db.Column.URL
|
||||
import me.proton.core.drive.base.data.db.Column.VERIFIER_TOKEN
|
||||
|
||||
@Entity(
|
||||
primaryKeys = [UPLOAD_LINK_ID, INDEX],
|
||||
@@ -58,4 +59,6 @@ data class UploadBlockEntity(
|
||||
val url: String,
|
||||
@ColumnInfo(name = RAW_SIZE, defaultValue = "0")
|
||||
val rawSize: Long,
|
||||
@ColumnInfo(name = VERIFIER_TOKEN, defaultValue = "NULL")
|
||||
val verifierToken: String? = null,
|
||||
)
|
||||
|
||||
+1
@@ -30,4 +30,5 @@ fun UploadBlockEntity.toUploadBlock(uploadBlockFactory: UploadBlockFactory) =
|
||||
rawSize = rawSize.bytes,
|
||||
size = size.bytes,
|
||||
token = uploadToken,
|
||||
verifierToken = verifierToken,
|
||||
)
|
||||
|
||||
+6
-2
@@ -33,7 +33,8 @@ class UploadBlockFactoryImpl @Inject constructor() : UploadBlockFactory {
|
||||
encSignature: String,
|
||||
rawSize: Bytes,
|
||||
size: Bytes,
|
||||
token: String
|
||||
token: String,
|
||||
verifierToken: String?,
|
||||
): UploadBlock =
|
||||
UploadBlock(
|
||||
index = index,
|
||||
@@ -44,6 +45,7 @@ class UploadBlockFactoryImpl @Inject constructor() : UploadBlockFactory {
|
||||
size = size,
|
||||
token = token,
|
||||
file = block,
|
||||
verifierToken = verifierToken,
|
||||
)
|
||||
|
||||
override fun create(
|
||||
@@ -53,7 +55,8 @@ class UploadBlockFactoryImpl @Inject constructor() : UploadBlockFactory {
|
||||
encSignature: String,
|
||||
rawSize: Bytes,
|
||||
size: Bytes,
|
||||
token: String
|
||||
token: String,
|
||||
verifierToken: String?,
|
||||
): UploadBlock =
|
||||
UploadBlock(
|
||||
index = index,
|
||||
@@ -64,5 +67,6 @@ class UploadBlockFactoryImpl @Inject constructor() : UploadBlockFactory {
|
||||
size = size,
|
||||
token = token,
|
||||
file = File(requireNotNull(Uri.parse(url)?.path)),
|
||||
verifierToken = verifierToken,
|
||||
)
|
||||
}
|
||||
|
||||
+12
@@ -17,6 +17,7 @@
|
||||
*/
|
||||
package me.proton.core.drive.linkupload.data.repository
|
||||
|
||||
import android.util.Base64
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import me.proton.core.domain.entity.UserId
|
||||
@@ -227,6 +228,17 @@ class LinkUploadRepositoryImpl @Inject constructor(
|
||||
token = token,
|
||||
)
|
||||
|
||||
override suspend fun updateUploadBlockVerifierToken(
|
||||
uploadFileLinkId: Long,
|
||||
uploadBlockIndex: Long,
|
||||
verifierToken: ByteArray
|
||||
) =
|
||||
db.uploadBlockDao.updateVerifierToken(
|
||||
uploadLinkId = uploadFileLinkId,
|
||||
index = uploadBlockIndex,
|
||||
verifierToken = Base64.encodeToString(verifierToken, Base64.NO_WRAP),
|
||||
)
|
||||
|
||||
override suspend fun removeUploadBlocks(uploadFileLink: UploadFileLink) =
|
||||
db.uploadBlockDao.delete(uploadFileLink.id)
|
||||
|
||||
|
||||
+1
@@ -30,4 +30,5 @@ data class UploadBlock(
|
||||
val size: Bytes,
|
||||
val token: String,
|
||||
val file: File,
|
||||
val verifierToken: String?,
|
||||
) : Block
|
||||
|
||||
+2
@@ -30,6 +30,7 @@ interface UploadBlockFactory {
|
||||
rawSize: Bytes,
|
||||
size: Bytes,
|
||||
token: String,
|
||||
verifierToken: String?,
|
||||
): UploadBlock
|
||||
|
||||
fun create(
|
||||
@@ -40,5 +41,6 @@ interface UploadBlockFactory {
|
||||
rawSize: Bytes,
|
||||
size: Bytes,
|
||||
token: String,
|
||||
verifierToken: String?,
|
||||
): UploadBlock
|
||||
}
|
||||
|
||||
+6
@@ -110,6 +110,12 @@ interface LinkUploadRepository {
|
||||
token: String,
|
||||
)
|
||||
|
||||
suspend fun updateUploadBlockVerifierToken(
|
||||
uploadFileLinkId: Long,
|
||||
uploadBlockIndex: Long,
|
||||
verifierToken: ByteArray,
|
||||
)
|
||||
|
||||
suspend fun removeUploadBlocks(uploadFileLink: UploadFileLink)
|
||||
|
||||
suspend fun insertUploadBulk(uploadBulk: UploadBulk): UploadBulk
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Core.
|
||||
*
|
||||
* Proton Core is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Core is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Core. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.drive.linkupload.domain.usecase
|
||||
|
||||
import me.proton.core.drive.linkupload.domain.entity.UploadBlock
|
||||
import me.proton.core.drive.linkupload.domain.repository.LinkUploadRepository
|
||||
import javax.inject.Inject
|
||||
|
||||
class UpdateVerifierToken @Inject constructor(
|
||||
private val linkUploadRepository: LinkUploadRepository,
|
||||
private val getUploadBlockAfterOperation: GetUploadBlockAfterOperation,
|
||||
) {
|
||||
|
||||
suspend operator fun invoke(
|
||||
uploadFileLinkId: Long,
|
||||
index: Long,
|
||||
verifierToken: ByteArray,
|
||||
): Result<UploadBlock> = getUploadBlockAfterOperation(uploadFileLinkId, index) {
|
||||
linkUploadRepository.updateUploadBlockVerifierToken(uploadFileLinkId, index, verifierToken)
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,9 @@ driveModule(
|
||||
implementation(project(":drive:link:presentation"))
|
||||
implementation(project(":drive:notification:domain"))
|
||||
implementation(project(":drive:worker:data"))
|
||||
implementation(project(":verifier:data"))
|
||||
implementation(libs.androidx.lifecycle.livedata.ktx)
|
||||
implementation(libs.core.crypto)
|
||||
implementation(libs.core.network)
|
||||
testImplementation(libs.bundles.test.jvm)
|
||||
}
|
||||
|
||||
+11
@@ -17,6 +17,9 @@
|
||||
*/
|
||||
package me.proton.core.drive.upload.data.extension
|
||||
|
||||
import me.proton.android.drive.verifier.data.extension.log
|
||||
import me.proton.android.drive.verifier.domain.exception.VerifierException
|
||||
import me.proton.core.drive.base.presentation.extension.log
|
||||
import me.proton.core.network.domain.ApiException
|
||||
import me.proton.core.network.domain.ApiResult
|
||||
import me.proton.core.network.domain.isRetryable
|
||||
@@ -30,5 +33,13 @@ internal val Throwable.isRetryable: Boolean
|
||||
else -> this.error.isRetryable()
|
||||
}
|
||||
}
|
||||
is VerifierException -> this.cause.isRetryable
|
||||
else -> false
|
||||
}
|
||||
|
||||
internal fun Throwable.log(tag: String, message: String? = null): Throwable = this.also {
|
||||
when (this) {
|
||||
is VerifierException -> this.log(tag, message.orEmpty())
|
||||
else -> this.log(tag, message)
|
||||
}
|
||||
}
|
||||
|
||||
+11
-2
@@ -18,15 +18,24 @@
|
||||
package me.proton.core.drive.upload.data.extension
|
||||
|
||||
import android.content.Context
|
||||
import me.proton.android.drive.verifier.data.extension.log
|
||||
import me.proton.android.drive.verifier.domain.exception.VerifierException
|
||||
import me.proton.core.drive.base.presentation.extension.getDefaultMessage
|
||||
import me.proton.core.drive.base.presentation.extension.log
|
||||
import me.proton.core.drive.upload.data.exception.UploadCleanupException
|
||||
import me.proton.core.drive.i18n.R as I18N
|
||||
|
||||
internal fun UploadCleanupException.getDefaultMessage(
|
||||
context: Context,
|
||||
useExceptionMessage: Boolean,
|
||||
): String = error.getDefaultMessage(context, useExceptionMessage)
|
||||
): String = when (error) {
|
||||
is VerifierException -> context.getString(I18N.string.files_upload_verification_failed)
|
||||
else -> error.getDefaultMessage(context, useExceptionMessage)
|
||||
}
|
||||
|
||||
internal fun UploadCleanupException.log(tag: String, message: String? = null): UploadCleanupException = also {
|
||||
message?.let { error.log(tag, message) } ?: error.log(tag)
|
||||
when (error) {
|
||||
is VerifierException -> message?.let { error.log(tag, message) } ?: error.log(tag)
|
||||
else -> message?.let { error.log(tag, message) } ?: error.log(tag)
|
||||
}
|
||||
}
|
||||
|
||||
+2
-5
@@ -46,6 +46,7 @@ import me.proton.core.drive.linkupload.domain.usecase.GetUploadFileLink
|
||||
import me.proton.core.drive.linkupload.domain.usecase.UpdateToken
|
||||
import me.proton.core.drive.upload.data.extension.getSizeData
|
||||
import me.proton.core.drive.upload.data.extension.isRetryable
|
||||
import me.proton.core.drive.upload.data.extension.retryOrAbort
|
||||
import me.proton.core.drive.upload.data.extension.setSize
|
||||
import me.proton.core.drive.upload.data.worker.WorkerKeys.KEY_BLOCK_INDEX
|
||||
import me.proton.core.drive.upload.data.worker.WorkerKeys.KEY_BLOCK_TOKEN
|
||||
@@ -132,11 +133,7 @@ class BlockUploadWorker @AssistedInject constructor(
|
||||
max retries reached ${!canRetry}
|
||||
""".trimIndent(),
|
||||
)
|
||||
return@coroutineScope if (retryable && canRetry) {
|
||||
Result.retry()
|
||||
} else {
|
||||
Result.success(getSizeData(progress.value))
|
||||
}
|
||||
return@coroutineScope retryOrAbort(retryable && canRetry, error, uploadFileLink.name)
|
||||
}
|
||||
} finally {
|
||||
job.cancel()
|
||||
|
||||
+14
-2
@@ -46,13 +46,19 @@ internal sealed class FileUploadFlow {
|
||||
uploadFileLinkId = uploadFileLinkId,
|
||||
uriString = uriString,
|
||||
shouldDeleteSource = shouldDeleteSource,
|
||||
tags = uploadTags
|
||||
tags = uploadTags,
|
||||
)
|
||||
).then(
|
||||
VerifyBlocksWorker.getWorkRequest(
|
||||
userId = userId,
|
||||
uploadFileLinkId = uploadFileLinkId,
|
||||
tags = uploadTags,
|
||||
)
|
||||
).then(
|
||||
GetBlocksUploadUrlWorker.getWorkRequest(
|
||||
userId = userId,
|
||||
uploadFileLinkId = uploadFileLinkId,
|
||||
tags = uploadTags
|
||||
tags = uploadTags,
|
||||
)
|
||||
).enqueue()
|
||||
}
|
||||
@@ -73,6 +79,12 @@ internal sealed class FileUploadFlow {
|
||||
shouldDeleteSource = shouldDeleteSource,
|
||||
tags = uploadTags
|
||||
)
|
||||
).then(
|
||||
VerifyBlocksWorker.getWorkRequest(
|
||||
userId = userId,
|
||||
uploadFileLinkId = uploadFileLinkId,
|
||||
tags = uploadTags,
|
||||
)
|
||||
).then(
|
||||
GetBlocksUploadUrlWorker.getWorkRequest(
|
||||
userId = userId,
|
||||
|
||||
+10
@@ -27,6 +27,7 @@ import androidx.work.WorkerParameters
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import me.proton.android.drive.verifier.domain.usecase.CleanupVerifier
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.drive.base.data.workmanager.addTags
|
||||
import me.proton.core.drive.base.domain.entity.Percentage
|
||||
@@ -60,6 +61,7 @@ class UploadCleanupWorker @AssistedInject constructor(
|
||||
private val getBlockFolder: GetBlockFolder,
|
||||
private val removeUploadFile: RemoveUploadFile,
|
||||
private val announceEvent: AnnounceEvent,
|
||||
private val cleanupVerifier: CleanupVerifier,
|
||||
configurationProvider: ConfigurationProvider,
|
||||
canRun: CanRun,
|
||||
run: Run,
|
||||
@@ -95,6 +97,14 @@ class UploadCleanupWorker @AssistedInject constructor(
|
||||
percentage = Percentage(0)
|
||||
)
|
||||
)
|
||||
uploadFileLink.linkId?.let { linkId ->
|
||||
cleanupVerifier(
|
||||
userId = userId,
|
||||
shareId = uploadFileLink.shareId.id,
|
||||
linkId = linkId,
|
||||
revisionId = uploadFileLink.draftRevisionId,
|
||||
)
|
||||
}
|
||||
} finally {
|
||||
uploadFileLink.deleteOnServer()
|
||||
}
|
||||
|
||||
+118
@@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Core.
|
||||
*
|
||||
* Proton Core is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Core is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Core. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.drive.upload.data.worker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.BackoffPolicy
|
||||
import androidx.work.Constraints
|
||||
import androidx.work.Data
|
||||
import androidx.work.NetworkType
|
||||
import androidx.work.OneTimeWorkRequest
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.drive.base.data.workmanager.addTags
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.linkupload.domain.entity.UploadFileLink
|
||||
import me.proton.core.drive.linkupload.domain.usecase.GetUploadFileLink
|
||||
import me.proton.core.drive.upload.data.extension.isRetryable
|
||||
import me.proton.core.drive.upload.data.extension.log
|
||||
import me.proton.core.drive.upload.data.extension.logTag
|
||||
import me.proton.core.drive.upload.data.extension.retryOrAbort
|
||||
import me.proton.core.drive.upload.domain.usecase.VerifyBlocks
|
||||
import me.proton.core.drive.worker.domain.usecase.CanRun
|
||||
import me.proton.core.drive.worker.domain.usecase.Done
|
||||
import me.proton.core.drive.worker.domain.usecase.Run
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
@HiltWorker
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Suppress("LongParameterList")
|
||||
class VerifyBlocksWorker @AssistedInject constructor(
|
||||
@Assisted appContext: Context,
|
||||
@Assisted workerParams: WorkerParameters,
|
||||
workManager: WorkManager,
|
||||
broadcastMessages: BroadcastMessages,
|
||||
getUploadFileLink: GetUploadFileLink,
|
||||
private val verifyBlocks: VerifyBlocks,
|
||||
configurationProvider: ConfigurationProvider,
|
||||
canRun: CanRun,
|
||||
run: Run,
|
||||
done: Done,
|
||||
) : UploadCoroutineWorker(
|
||||
appContext = appContext,
|
||||
workerParams = workerParams,
|
||||
workManager = workManager,
|
||||
broadcastMessages = broadcastMessages,
|
||||
getUploadFileLink = getUploadFileLink,
|
||||
configurationProvider = configurationProvider,
|
||||
canRun = canRun,
|
||||
run = run,
|
||||
done = done,
|
||||
) {
|
||||
|
||||
override suspend fun doLimitedRetryUploadWork(uploadFileLink: UploadFileLink): Result {
|
||||
verifyBlocks(uploadFileLink)
|
||||
.onFailure { error ->
|
||||
val retryable = error.isRetryable
|
||||
val canRetry = canRetry()
|
||||
error.log(
|
||||
tag = uploadFileLink.logTag(),
|
||||
message = """
|
||||
Verify blocks failed with "${error.message}" retryable $retryable,
|
||||
max retries reached ${!canRetry}
|
||||
""".trimIndent()
|
||||
)
|
||||
return retryOrAbort(retryable && canRetry, error, uploadFileLink.name)
|
||||
}
|
||||
return Result.success()
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getWorkRequest(
|
||||
userId: UserId,
|
||||
uploadFileLinkId: Long,
|
||||
tags: List<String> = emptyList(),
|
||||
): OneTimeWorkRequest =
|
||||
OneTimeWorkRequest.Builder(VerifyBlocksWorker::class.java)
|
||||
.setConstraints(
|
||||
Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
)
|
||||
.setInputData(
|
||||
Data.Builder()
|
||||
.putString(WorkerKeys.KEY_USER_ID, userId.id)
|
||||
.putLong(WorkerKeys.KEY_UPLOAD_FILE_LINK_ID, uploadFileLinkId)
|
||||
.build()
|
||||
)
|
||||
.setBackoffCriteria(
|
||||
BackoffPolicy.EXPONENTIAL,
|
||||
OneTimeWorkRequest.MIN_BACKOFF_MILLIS,
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
.addTags(listOf(userId.id) + tags)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
+146
@@ -0,0 +1,146 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Core.
|
||||
*
|
||||
* Proton Core is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Core is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Core. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.drive.upload.data.worker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.work.Data
|
||||
import androidx.work.ListenableWorker
|
||||
import androidx.work.Operation
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkRequest
|
||||
import androidx.work.WorkerFactory
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.testing.TestListenableWorkerBuilder
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import me.proton.core.domain.arch.DataResult
|
||||
import me.proton.core.domain.arch.ResponseSource
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.linkupload.domain.entity.UploadFileLink
|
||||
import me.proton.core.drive.linkupload.domain.usecase.GetUploadFileLink
|
||||
import me.proton.core.drive.messagequeue.domain.entity.BroadcastMessage
|
||||
import me.proton.core.drive.upload.domain.usecase.UpdateRevision
|
||||
import me.proton.core.drive.worker.domain.usecase.CanRun
|
||||
import me.proton.core.drive.worker.domain.usecase.Done
|
||||
import me.proton.core.drive.worker.domain.usecase.Run
|
||||
import me.proton.core.network.domain.ApiException
|
||||
import me.proton.core.network.domain.ApiResult
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class UpdateRevisionWorkerTest {
|
||||
private val userId: UserId = UserId("user-id")
|
||||
private val workManager = mockk<WorkManager>()
|
||||
private val broadcastMessages = mockk<BroadcastMessages>()
|
||||
private val getUploadFileLink = mockk<GetUploadFileLink>()
|
||||
private val updateRevision = mockk<UpdateRevision>()
|
||||
private val configurationProvider = mockk<ConfigurationProvider>()
|
||||
private val canRun = mockk<CanRun>()
|
||||
private val run = mockk<Run>()
|
||||
private val done = mockk<Done>()
|
||||
private val uploadFileLink = mockk<UploadFileLink>()
|
||||
private val operation = mockk<Operation>()
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
coEvery { canRun(any(), any()) } returns Result.success(true)
|
||||
coEvery { getUploadFileLink(any() as Long) } returns DataResult.Success(ResponseSource.Local, uploadFileLink)
|
||||
coEvery { workManager.enqueue(any() as WorkRequest) } returns operation
|
||||
coEvery { configurationProvider.useExceptionMessage } returns false
|
||||
coEvery { broadcastMessages(userId, any(), any(), any()) } returns Unit
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when commit a revision receives error with proton code 200501, upload fails and message is shown`() = runTest {
|
||||
// Given
|
||||
val uploadFile = "proton_drive.pdf"
|
||||
coEvery { uploadFileLink.name } returns uploadFile
|
||||
val errorFromServer = "Upload failed: Verification of data failed"
|
||||
coEvery { updateRevision(any()) } returns Result.failure(
|
||||
ApiException(
|
||||
ApiResult.Error.Http(
|
||||
httpCode = 422,
|
||||
message = "Unprocessable Content",
|
||||
proton = ApiResult.Error.ProtonData(
|
||||
code = 200501,
|
||||
error = errorFromServer,
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
// When
|
||||
val result = updateRevisionWorker(userId).doLimitedRetryWork()
|
||||
|
||||
// Then
|
||||
assertEquals(ListenableWorker.Result.failure(), result)
|
||||
verify(exactly = 1) {
|
||||
broadcastMessages(
|
||||
userId = userId,
|
||||
message = "Uploading file $uploadFile failed with reason: $errorFromServer",
|
||||
type = BroadcastMessage.Type.ERROR,
|
||||
extra = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateRevisionWorker(
|
||||
userId: UserId,
|
||||
): UpdateRevisionWorker {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
return TestListenableWorkerBuilder<UpdateRevisionWorker>(context)
|
||||
.setWorkerFactory(
|
||||
object : WorkerFactory() {
|
||||
override fun createWorker(
|
||||
appContext: Context,
|
||||
workerClassName: String,
|
||||
workerParameters: WorkerParameters
|
||||
) = UpdateRevisionWorker(
|
||||
appContext = appContext,
|
||||
workerParams = workerParameters,
|
||||
workManager = workManager,
|
||||
broadcastMessages = broadcastMessages,
|
||||
getUploadFileLink = getUploadFileLink,
|
||||
updateRevision = updateRevision,
|
||||
configurationProvider = configurationProvider,
|
||||
canRun = canRun,
|
||||
run = run,
|
||||
done = done,
|
||||
)
|
||||
}
|
||||
)
|
||||
.setInputData(
|
||||
Data.Builder()
|
||||
.putString(WorkerKeys.KEY_USER_ID, userId.id)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
+143
@@ -0,0 +1,143 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Core.
|
||||
*
|
||||
* Proton Core is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Core is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Core. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.drive.upload.data.worker
|
||||
|
||||
import android.content.Context
|
||||
import androidx.test.core.app.ApplicationProvider
|
||||
import androidx.work.Data
|
||||
import androidx.work.ListenableWorker
|
||||
import androidx.work.Operation
|
||||
import androidx.work.WorkManager
|
||||
import androidx.work.WorkRequest
|
||||
import androidx.work.WorkerFactory
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.testing.TestListenableWorkerBuilder
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import me.proton.android.drive.verifier.domain.exception.VerifierException
|
||||
import me.proton.core.crypto.common.pgp.exception.CryptoException
|
||||
import me.proton.core.domain.arch.DataResult
|
||||
import me.proton.core.domain.arch.ResponseSource
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.drive.base.domain.provider.ConfigurationProvider
|
||||
import me.proton.core.drive.base.domain.usecase.BroadcastMessages
|
||||
import me.proton.core.drive.linkupload.domain.entity.UploadFileLink
|
||||
import me.proton.core.drive.linkupload.domain.usecase.GetUploadFileLink
|
||||
import me.proton.core.drive.upload.domain.usecase.VerifyBlocks
|
||||
import me.proton.core.drive.worker.domain.usecase.CanRun
|
||||
import me.proton.core.drive.worker.domain.usecase.Done
|
||||
import me.proton.core.drive.worker.domain.usecase.Run
|
||||
import me.proton.core.util.kotlin.CoreLogger
|
||||
import me.proton.core.util.kotlin.Logger
|
||||
import org.junit.Assert
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class VerifyBlocksWorkerTest {
|
||||
private val userId: UserId = UserId("user-id")
|
||||
private val workManager = mockk<WorkManager>()
|
||||
private val broadcastMessages = mockk<BroadcastMessages>()
|
||||
private val getUploadFileLink = mockk<GetUploadFileLink>()
|
||||
private val verifyBlocks = mockk<VerifyBlocks>()
|
||||
private val configurationProvider = mockk<ConfigurationProvider>()
|
||||
private val canRun = mockk<CanRun>()
|
||||
private val run = mockk<Run>()
|
||||
private val done = mockk<Done>()
|
||||
private val uploadFileLink = mockk<UploadFileLink>()
|
||||
private val operation = mockk<Operation>()
|
||||
private val logger = mockk<Logger>()
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
coEvery { canRun(any(), any()) } returns Result.success(true)
|
||||
coEvery { getUploadFileLink(any() as Long) } returns DataResult.Success(ResponseSource.Local, uploadFileLink)
|
||||
coEvery { uploadFileLink.id } returns 123L
|
||||
coEvery { uploadFileLink.name } returns "secret.jpg"
|
||||
coEvery { workManager.enqueue(any() as WorkRequest) } returns operation
|
||||
coEvery { configurationProvider.useExceptionMessage } returns false
|
||||
coEvery { broadcastMessages(userId, any(), any(), any()) } returns Unit
|
||||
coEvery { logger.d(any(), any()) } returns Unit
|
||||
coEvery { logger.e(any(), any(), any()) } returns Unit
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when verify blocks fails with VerifyBlock exception, upload fails and it is logged as error`() = runTest {
|
||||
// Given
|
||||
val error = VerifierException.VerifyBlock(CryptoException("Invalid key"))
|
||||
coEvery { verifyBlocks(uploadFileLink) } returns Result.failure(error)
|
||||
val uploadFileLinkId = uploadFileLink.id
|
||||
CoreLogger.set(logger)
|
||||
|
||||
// When
|
||||
val result = verifyBlocksWorker(userId).doLimitedRetryWork()
|
||||
|
||||
// Then
|
||||
Assert.assertEquals(ListenableWorker.Result.failure(), result)
|
||||
verify(exactly = 1) {
|
||||
logger.e(
|
||||
tag = "core.drive.upload.$uploadFileLinkId",
|
||||
e = error,
|
||||
message = """
|
||||
Verify blocks failed with "${error.message}" retryable false,
|
||||
max retries reached false
|
||||
""".trimIndent(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun verifyBlocksWorker(
|
||||
userId: UserId,
|
||||
): VerifyBlocksWorker {
|
||||
val context = ApplicationProvider.getApplicationContext<Context>()
|
||||
return TestListenableWorkerBuilder<VerifyBlocksWorker>(context)
|
||||
.setWorkerFactory(
|
||||
object : WorkerFactory() {
|
||||
override fun createWorker(
|
||||
appContext: Context,
|
||||
workerClassName: String,
|
||||
workerParameters: WorkerParameters
|
||||
) = VerifyBlocksWorker(
|
||||
appContext = appContext,
|
||||
workerParams = workerParameters,
|
||||
workManager = workManager,
|
||||
broadcastMessages = broadcastMessages,
|
||||
getUploadFileLink = getUploadFileLink,
|
||||
verifyBlocks = verifyBlocks,
|
||||
configurationProvider = configurationProvider,
|
||||
canRun = canRun,
|
||||
run = run,
|
||||
done = done,
|
||||
)
|
||||
}
|
||||
)
|
||||
.setInputData(
|
||||
Data.Builder()
|
||||
.putString(WorkerKeys.KEY_USER_ID, userId.id)
|
||||
.build()
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
@@ -28,6 +28,7 @@ driveModule(
|
||||
api(project(":drive:link-upload:domain"))
|
||||
api(project(":drive:notification:domain"))
|
||||
api(project(":drive:thumbnail:domain"))
|
||||
api(project(":verifier:domain"))
|
||||
|
||||
implementation(project(":drive:crypto:domain"))
|
||||
}
|
||||
|
||||
+2
@@ -197,6 +197,7 @@ class SplitFileToBlocksAndEncrypt @Inject constructor(
|
||||
rawSize = rawBlock.size,
|
||||
size = encryptedBlock.size,
|
||||
token = "",
|
||||
verifierToken = null,
|
||||
).also { rawBlock.delete() }
|
||||
}.getOrThrow()
|
||||
|
||||
@@ -231,6 +232,7 @@ class SplitFileToBlocksAndEncrypt @Inject constructor(
|
||||
rawSize = thumbnail.size.bytes,
|
||||
size = encryptedUploadThumbnail.size,
|
||||
token = "",
|
||||
verifierToken = null,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
+77
@@ -0,0 +1,77 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Core.
|
||||
*
|
||||
* Proton Core is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Core is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Core. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.core.drive.upload.domain.usecase
|
||||
|
||||
import me.proton.android.drive.verifier.domain.usecase.BuildVerifier
|
||||
import me.proton.android.drive.verifier.domain.usecase.CleanupVerifier
|
||||
import me.proton.core.drive.base.domain.usecase.GetSignatureAddress
|
||||
import me.proton.core.drive.base.domain.util.coRunCatching
|
||||
import me.proton.core.drive.key.domain.entity.Key
|
||||
import me.proton.core.drive.key.domain.usecase.BuildNodeKey
|
||||
import me.proton.core.drive.key.domain.usecase.GetNodeKey
|
||||
import me.proton.core.drive.linkupload.domain.entity.UploadFileLink
|
||||
import me.proton.core.drive.linkupload.domain.usecase.GetUploadBlocks
|
||||
import me.proton.core.drive.linkupload.domain.usecase.UpdateVerifierToken
|
||||
import javax.inject.Inject
|
||||
|
||||
class VerifyBlocks @Inject constructor(
|
||||
private val buildVerifier: BuildVerifier,
|
||||
private val cleanupVerifier: CleanupVerifier,
|
||||
private val getUploadBlocks: GetUploadBlocks,
|
||||
private val getSignatureAddress: GetSignatureAddress,
|
||||
private val getNodeKey: GetNodeKey,
|
||||
private val buildNodeKey: BuildNodeKey,
|
||||
private val updateVerifierToken: UpdateVerifierToken,
|
||||
) {
|
||||
suspend operator fun invoke(uploadFileLink: UploadFileLink): Result<Unit> = coRunCatching {
|
||||
val verifier = buildVerifier(
|
||||
userId = uploadFileLink.userId,
|
||||
shareId = uploadFileLink.shareId.id,
|
||||
linkId = requireNotNull(uploadFileLink.linkId),
|
||||
revisionId = uploadFileLink.draftRevisionId,
|
||||
fileKey = uploadFileLink.buildFileKey(),
|
||||
).getOrThrow()
|
||||
val uploadBlocks = getUploadBlocks(uploadFileLink)
|
||||
.getOrThrow()
|
||||
.associateBy { uploadBlock -> uploadBlock.file }
|
||||
verifier.verifyBlocks(uploadBlocks.keys.toList())
|
||||
.getOrThrow()
|
||||
.map { (file, verifierToken) ->
|
||||
updateVerifierToken(
|
||||
uploadFileLinkId = uploadFileLink.id,
|
||||
index = requireNotNull(uploadBlocks[file]).index,
|
||||
verifierToken = verifierToken,
|
||||
)
|
||||
}
|
||||
cleanupVerifier(
|
||||
userId = uploadFileLink.userId,
|
||||
shareId = uploadFileLink.shareId.id,
|
||||
linkId = requireNotNull(uploadFileLink.linkId),
|
||||
revisionId = uploadFileLink.draftRevisionId,
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun UploadFileLink.buildFileKey(): Key.Node =
|
||||
buildNodeKey(
|
||||
userId = userId,
|
||||
parentKey = getNodeKey(parentLinkId).getOrThrow(),
|
||||
uploadFileLink = this,
|
||||
signatureAddress = getSignatureAddress(userId),
|
||||
).getOrThrow()
|
||||
}
|
||||
@@ -231,6 +231,7 @@ androidx-test-uiautomator = { module = "androidx.test.uiautomator:uiautomator",
|
||||
androidx-test-espresso-contrib = { module = "androidx.test.espresso:espresso-contrib", version.ref = "espresso-contrib"}
|
||||
androidx-compose-ui-test = { module = "androidx.compose.ui:ui-test", version.ref = "androidx-compose" }
|
||||
androidx-compose-ui-test-junit = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "androidx-compose" }
|
||||
androidx-work-testing = { module = "androidx.work:work-testing", version.ref = "androidx-work" }
|
||||
coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" }
|
||||
junit = { module = "junit:junit", version.ref = "junit" }
|
||||
mockk-jvm = { module = "io.mockk:mockk", version.ref = "mockk" }
|
||||
@@ -243,7 +244,7 @@ accompanist = ["accompanist-insets", "accompanist-navigation-animation", "accomp
|
||||
core = ["core-account", "core-accountManager", "core-auth", "core-challenge", "core-country", "core-crypto", "core-cryptoValidator", "core-data", "core-dataRoom", "core-domain", "core-eventManager", "core-featureFlag", "core-humanVerification", "core-key", "core-keyTransparency", "core-network", "core-observability", "core-payment", "core-payment-iap", "core-plan", "core-report", "core-presentation", "core-presentation-compose", "core-user", "core-userSettings", "core-utilAndroidDagger", "core-utilKotlin"]
|
||||
core-test = ["core-auth-test", "core-humanVerification-test", "core-report-test"]
|
||||
test-android = ["junit", "mockk-android", "coroutines-test", "androidx-test-core-ktx", "androidx-test-runner", "androidx-test-rules", "androidx-compose-ui-test", "androidx-compose-ui-test-junit", "androidx-test-uiautomator", "core-test-android-instrumented"]
|
||||
test-jvm = ["junit", "mockk-jvm", "coroutines-test", "core-test-kotlin", "core-test-quark", "robolectric"]
|
||||
test-jvm = ["junit", "mockk-jvm", "coroutines-test", "androidx-test-core-ktx", "androidx-work-testing", "core-test-kotlin", "core-test-quark", "robolectric"]
|
||||
|
||||
[plugins]
|
||||
proton-detekt = { id = "me.proton.core.gradle-plugins.detekt", version.ref = "proton-detekt-plugin" }
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "me.proton.android.drive.verifier"
|
||||
}
|
||||
|
||||
driveModule(includeSubmodules = true)
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2023 Proton AG.
|
||||
* This file is part of Proton Core.
|
||||
*
|
||||
* Proton Core is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Core is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Core. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "me.proton.android.drive.verifier.data"
|
||||
}
|
||||
|
||||
driveModule(
|
||||
hilt = true,
|
||||
serialization = true,
|
||||
) {
|
||||
api(project(":verifier:domain"))
|
||||
implementation(project(":drive:base:data"))
|
||||
implementation(project(":drive:crypto:domain"))
|
||||
implementation(project(":drive:i18n"))
|
||||
implementation(libs.retrofit)
|
||||
testImplementation(project(":drive:base:data-test"))
|
||||
}
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.api
|
||||
|
||||
import me.proton.android.drive.verifier.data.api.response.GetVerificationDataResponse
|
||||
import me.proton.core.network.data.protonApi.BaseRetrofitApi
|
||||
import retrofit2.http.GET
|
||||
import retrofit2.http.Path
|
||||
|
||||
interface VerifierApi : BaseRetrofitApi {
|
||||
|
||||
@GET("drive/shares/@{enc_shareID}/links/@{enc_linkID}/revisions/@{enc_revisionID}/verification")
|
||||
suspend fun getVerificationData(
|
||||
@Path("enc_shareID") shareId: String,
|
||||
@Path("enc_linkID") linkId: String,
|
||||
@Path("enc_revisionID") revisionId: String,
|
||||
): GetVerificationDataResponse
|
||||
}
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.api
|
||||
|
||||
import me.proton.android.drive.verifier.data.api.response.GetVerificationDataResponse
|
||||
import me.proton.android.drive.verifier.domain.entity.VerificationData
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.network.data.ApiProvider
|
||||
import me.proton.core.network.domain.ApiException
|
||||
|
||||
class VerifierApiDataSource(private val apiProvider: ApiProvider) {
|
||||
|
||||
@Throws(ApiException::class)
|
||||
suspend fun getVerificationData(
|
||||
userId: UserId,
|
||||
shareId: String,
|
||||
linkId: String,
|
||||
revisionId: String,
|
||||
): GetVerificationDataResponse =
|
||||
apiProvider.get<VerifierApi>(userId).invoke { getVerificationData(shareId, linkId, revisionId) }.valueOrThrow
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.api.response
|
||||
|
||||
import kotlinx.serialization.SerialName
|
||||
import kotlinx.serialization.Serializable
|
||||
import me.proton.core.drive.base.data.api.Dto.CODE
|
||||
import me.proton.core.drive.base.data.api.Dto.CONTENT_KEY_PACKET
|
||||
import me.proton.core.drive.base.data.api.Dto.REVISION
|
||||
import me.proton.core.drive.base.data.api.Dto.SIGNATURE_ADDRESS
|
||||
import me.proton.core.drive.base.data.api.Dto.STATE
|
||||
import me.proton.core.drive.base.data.api.Dto.VERIFICATION_CODE
|
||||
|
||||
@Serializable
|
||||
data class GetVerificationDataResponse(
|
||||
@SerialName(CODE)
|
||||
val code: Long,
|
||||
@SerialName(VERIFICATION_CODE)
|
||||
val verificationCode: String,
|
||||
@SerialName(CONTENT_KEY_PACKET)
|
||||
val contentKeyPacket: String,
|
||||
)
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.di
|
||||
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import me.proton.android.drive.verifier.data.repository.VerifierRepositoryImpl
|
||||
import me.proton.android.drive.verifier.domain.repository.VerifierRepository
|
||||
import javax.inject.Singleton
|
||||
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Module
|
||||
interface VerifierBindModule {
|
||||
|
||||
@Binds
|
||||
@Singleton
|
||||
fun bindsRepositoryImpl(impl: VerifierRepositoryImpl): VerifierRepository
|
||||
}
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.di
|
||||
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
import dagger.hilt.components.SingletonComponent
|
||||
import me.proton.android.drive.verifier.data.api.VerifierApiDataSource
|
||||
import me.proton.android.drive.verifier.data.factory.VerifierFactoryImpl
|
||||
import me.proton.android.drive.verifier.domain.factory.VerifierFactory
|
||||
import me.proton.core.drive.base.domain.usecase.GetCacheTempFolder
|
||||
import me.proton.core.drive.crypto.domain.usecase.file.DecryptFiles
|
||||
import me.proton.core.network.data.ApiProvider
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
object VerifierModule {
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideVerifierApiDataSource(apiProvider: ApiProvider) =
|
||||
VerifierApiDataSource(apiProvider)
|
||||
|
||||
@Singleton
|
||||
@Provides
|
||||
fun provideVerifierFactory(
|
||||
decryptFiles: DecryptFiles,
|
||||
getCacheTempFolder: GetCacheTempFolder,
|
||||
): VerifierFactory =
|
||||
VerifierFactoryImpl(decryptFiles, getCacheTempFolder)
|
||||
}
|
||||
+79
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.entity
|
||||
|
||||
import me.proton.android.drive.verifier.domain.exception.VerifierException
|
||||
import me.proton.android.drive.verifier.data.extension.head
|
||||
import me.proton.android.drive.verifier.data.extension.xor
|
||||
import me.proton.android.drive.verifier.domain.entity.Verifier
|
||||
import me.proton.core.drive.base.domain.extension.bytes
|
||||
import me.proton.core.drive.crypto.domain.usecase.file.DecryptFiles
|
||||
import me.proton.core.drive.key.domain.entity.ContentKey
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
||||
internal class VerifierImpl constructor(
|
||||
private val decryptFiles: DecryptFiles,
|
||||
private val contentKey: ContentKey,
|
||||
private val verificationCode: ByteArray,
|
||||
private val tempFolder: File
|
||||
) : Verifier {
|
||||
|
||||
init {
|
||||
require(verificationCode.size == VERIFICATION_CODE_SIZE) {
|
||||
"Invalid verification code size"
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun verifyBlocks(blocks: List<File>): Result<Map<File, ByteArray>> =
|
||||
try {
|
||||
require(blocks.isNotEmpty()) { "Input blocks list is empty" }
|
||||
require(blocks.all { block -> block.exists() }) { "Input block does not exist" }
|
||||
verifyByDecryptingBlocks(blocks)
|
||||
Result.success(
|
||||
blocks.associateBy(
|
||||
keySelector = { file -> file }
|
||||
) { file -> verificationCode.xor(file.head(len = verificationCode.size.bytes))}
|
||||
)
|
||||
} catch (t: Throwable) {
|
||||
Result.failure(VerifierException.VerifyBlock(t))
|
||||
}
|
||||
|
||||
private suspend fun verifyByDecryptingBlocks(blocks: List<File>) {
|
||||
val output = blocks.map { block ->
|
||||
File(block.destinationFolder(), UUID.randomUUID().toString()).apply {
|
||||
parentFile?.mkdirs()
|
||||
createNewFile()
|
||||
}
|
||||
}
|
||||
val result = decryptFiles(
|
||||
contentKey = contentKey,
|
||||
input = blocks,
|
||||
output = output,
|
||||
)
|
||||
output.forEach { file -> file.delete() }
|
||||
result.getOrThrow()
|
||||
}
|
||||
|
||||
private fun File.destinationFolder() = parent ?: tempFolder.path
|
||||
|
||||
companion object {
|
||||
private const val VERIFICATION_CODE_SIZE = 32
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.extension
|
||||
|
||||
import kotlin.experimental.xor
|
||||
|
||||
internal fun ByteArray.xor(other: ByteArray): ByteArray {
|
||||
require(size == other.size) { "Arrays of same size are required" }
|
||||
val output = ByteArray(size)
|
||||
for (i in indices) {
|
||||
output[i] = this[i].xor(other[i])
|
||||
}
|
||||
return output
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.extension
|
||||
|
||||
import me.proton.core.drive.base.domain.entity.Bytes
|
||||
import me.proton.core.drive.base.domain.extension.MiB
|
||||
import java.io.File
|
||||
|
||||
internal fun File.head(len: Bytes): ByteArray = inputStream().use { fileInputStream ->
|
||||
require(len.value in LongRange(1, 1.MiB.value))
|
||||
val size = len.value.toInt()
|
||||
val bytes = ByteArray(size)
|
||||
fileInputStream.read(bytes, 0, size)
|
||||
bytes
|
||||
}
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.extension
|
||||
|
||||
import android.content.Context
|
||||
import me.proton.android.drive.verifier.domain.exception.VerifierException
|
||||
import me.proton.core.util.kotlin.CoreLogger
|
||||
import me.proton.core.drive.i18n.R as I18N
|
||||
|
||||
fun VerifierException.getDefaultMessage(context: Context): String = when (this) {
|
||||
is VerifierException.Initialize -> context.getString(I18N.string.verifier_initialize_failed)
|
||||
is VerifierException.VerifyBlock -> context.getString(I18N.string.verifier_verify_blocks_failed)
|
||||
}
|
||||
|
||||
fun VerifierException.log(tag: String, message: String = this.message.orEmpty()): VerifierException = also {
|
||||
val logToSentry = this is VerifierException.VerifyBlock
|
||||
val log: (String, Throwable, String) -> Unit = if (logToSentry) CoreLogger::e else CoreLogger::d
|
||||
log(tag, this, message)
|
||||
}
|
||||
+46
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.factory
|
||||
|
||||
import me.proton.android.drive.verifier.data.entity.VerifierImpl
|
||||
import me.proton.android.drive.verifier.domain.entity.Verifier
|
||||
import me.proton.android.drive.verifier.domain.factory.VerifierFactory
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.drive.base.domain.usecase.GetCacheTempFolder
|
||||
import me.proton.core.drive.crypto.domain.usecase.file.DecryptFiles
|
||||
import me.proton.core.drive.key.domain.entity.ContentKey
|
||||
import javax.inject.Inject
|
||||
|
||||
class VerifierFactoryImpl @Inject constructor(
|
||||
private val decryptFiles: DecryptFiles,
|
||||
private val getCacheTempFolder: GetCacheTempFolder,
|
||||
) : VerifierFactory {
|
||||
|
||||
override suspend fun create(
|
||||
userId: UserId,
|
||||
contentKey: ContentKey,
|
||||
verificationCode: ByteArray,
|
||||
): Verifier =
|
||||
VerifierImpl(
|
||||
decryptFiles = decryptFiles,
|
||||
contentKey = contentKey,
|
||||
verificationCode = verificationCode,
|
||||
tempFolder = getCacheTempFolder(userId),
|
||||
)
|
||||
}
|
||||
+84
@@ -0,0 +1,84 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.repository
|
||||
|
||||
import android.util.Base64
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import me.proton.android.drive.verifier.data.api.VerifierApiDataSource
|
||||
import me.proton.android.drive.verifier.domain.entity.VerificationData
|
||||
import me.proton.android.drive.verifier.domain.repository.VerifierRepository
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import javax.inject.Inject
|
||||
|
||||
class VerifierRepositoryImpl @Inject constructor(
|
||||
private val api: VerifierApiDataSource,
|
||||
) : VerifierRepository {
|
||||
internal val verificationDataCache: MutableMap<VerificationDataKey, VerificationData> = mutableMapOf()
|
||||
private val mutex = Mutex()
|
||||
|
||||
override suspend fun getVerificationData(
|
||||
userId: UserId,
|
||||
shareId: String,
|
||||
linkId: String,
|
||||
revisionId: String,
|
||||
): VerificationData = getOrFetch(VerificationDataKey(userId, shareId, linkId, revisionId))
|
||||
|
||||
override suspend fun removeVerificationData(
|
||||
userId: UserId,
|
||||
shareId: String,
|
||||
linkId: String,
|
||||
revisionId: String,
|
||||
) {
|
||||
remove(
|
||||
VerificationDataKey(userId, shareId, linkId, revisionId)
|
||||
)
|
||||
}
|
||||
|
||||
private suspend fun getOrFetch(key: VerificationDataKey): VerificationData = get(key) ?: fetchAndStore(key)
|
||||
|
||||
private suspend fun fetchAndStore(key: VerificationDataKey): VerificationData =
|
||||
api.getVerificationData(key.userId, key.shareId, key.linkId, key.revisionId).let { response ->
|
||||
VerificationData(
|
||||
contentKeyPacket = response.contentKeyPacket,
|
||||
verificationCode = Base64.decode(response.verificationCode, Base64.NO_WRAP),
|
||||
)
|
||||
}.also { verificationData ->
|
||||
put(key, verificationData)
|
||||
}
|
||||
|
||||
internal suspend fun get(key: VerificationDataKey): VerificationData? = mutex.withLock {
|
||||
verificationDataCache[key]
|
||||
}
|
||||
|
||||
internal suspend fun put(key: VerificationDataKey, verificationData: VerificationData) = mutex.withLock {
|
||||
verificationDataCache[key] = verificationData
|
||||
}
|
||||
|
||||
internal suspend fun remove(key: VerificationDataKey) = mutex.withLock {
|
||||
verificationDataCache.remove(key)
|
||||
}
|
||||
|
||||
data class VerificationDataKey(
|
||||
val userId: UserId,
|
||||
val shareId: String,
|
||||
val linkId: String,
|
||||
val revisionId: String,
|
||||
)
|
||||
}
|
||||
+180
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.entity
|
||||
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import me.proton.android.drive.verifier.data.extension.createFile
|
||||
import me.proton.android.drive.verifier.domain.exception.VerifierException
|
||||
import me.proton.core.crypto.common.pgp.exception.CryptoException
|
||||
import me.proton.core.drive.base.domain.extension.bytes
|
||||
import me.proton.core.drive.base.domain.extension.toHex
|
||||
import me.proton.core.drive.crypto.domain.usecase.file.DecryptFiles
|
||||
import me.proton.core.drive.key.domain.entity.ContentKey
|
||||
import me.proton.core.drive.key.domain.entity.Key
|
||||
import me.proton.core.drive.key.domain.usecase.BuildContentKey
|
||||
import me.proton.core.test.kotlin.assertEquals
|
||||
import me.proton.core.test.kotlin.assertTrue
|
||||
import org.junit.Assert.assertNotNull
|
||||
import org.junit.Before
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.File
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class VerifierTest {
|
||||
private val buildContentKey = mockk<BuildContentKey>()
|
||||
private val decryptFiles = mockk<DecryptFiles>()
|
||||
private val fileKey = mockk<Key.Node>()
|
||||
private val contentKey = mockk<ContentKey>()
|
||||
private val verificationCode = ByteArray(VERIFICATION_CODE_SIZE) { i -> i.toByte() }
|
||||
private lateinit var verifier: VerifierImpl
|
||||
private lateinit var file64B: File
|
||||
private lateinit var file17B: File
|
||||
|
||||
@get: Rule
|
||||
val temporaryFolder = TemporaryFolder()
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
coEvery {
|
||||
buildContentKey(
|
||||
userId = any(),
|
||||
contentKeyPacket = any(),
|
||||
contentKeyPacketSignature = any(),
|
||||
fileKey = fileKey,
|
||||
)
|
||||
} returns Result.success(contentKey)
|
||||
|
||||
coEvery {
|
||||
decryptFiles(
|
||||
contentKey = contentKey,
|
||||
input = any(),
|
||||
output = any(),
|
||||
)
|
||||
} returns Result.success(emptyList())
|
||||
|
||||
verifier = VerifierImpl(decryptFiles, contentKey, verificationCode, temporaryFolder.newFolder())
|
||||
file64B = temporaryFolder.createFile(64.bytes)
|
||||
file17B = temporaryFolder.createFile(17.bytes)
|
||||
}
|
||||
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun `VerifierImpl throws IllegalArgumentException if verificationCode with invalid size is given`() = runTest {
|
||||
// Given
|
||||
val verificationCode = ByteArray(31) { i -> i.toByte() }
|
||||
|
||||
// Then
|
||||
VerifierImpl(decryptFiles, contentKey, verificationCode, temporaryFolder.newFolder())
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verifyBlock when given empty block list throws VerifyBlock with cause IllegalArgumentException`() = runTest {
|
||||
// Given
|
||||
val blocks = emptyList<File>()
|
||||
|
||||
// When
|
||||
val exception = verifier.verifyBlocks(blocks).exceptionOrNull()
|
||||
|
||||
// Then
|
||||
assertNotNull(exception)
|
||||
assertTrue(exception is VerifierException.VerifyBlock) {
|
||||
"actual: $exception, expected: VerifyBlock"
|
||||
}
|
||||
assertTrue(exception?.cause is IllegalArgumentException) {
|
||||
"actual: ${exception?.cause}, expected: IllegalArgumentException"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verifyBlock when given non-existing block file(s) throws VerifyBlock with cause IllegalArgumentException`() = runTest {
|
||||
// Given
|
||||
val nonExistingFile = File("", "test.txt")
|
||||
val blocks = listOf(nonExistingFile)
|
||||
|
||||
// When
|
||||
val exception = verifier.verifyBlocks(blocks).exceptionOrNull()
|
||||
|
||||
// Then
|
||||
assertNotNull(exception)
|
||||
assertTrue(exception is VerifierException.VerifyBlock) {
|
||||
"actual: $exception, expected: VerifyBlock"
|
||||
}
|
||||
assertTrue(exception?.cause is IllegalArgumentException) {
|
||||
"actual: ${exception?.cause}, expected: IllegalArgumentException"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `verifyBlock throws VerifyBlock when decrypt blocks fails`() = runTest {
|
||||
// Given
|
||||
coEvery { decryptFiles(contentKey, any(), any()) } returns Result.failure(CryptoException())
|
||||
|
||||
// When
|
||||
val exception = verifier.verifyBlocks(listOf(file64B)).exceptionOrNull()
|
||||
|
||||
// Then
|
||||
assertNotNull(exception)
|
||||
assertTrue(exception is VerifierException.VerifyBlock) {
|
||||
"actual: $exception, expected: VerifyBlock"
|
||||
}
|
||||
assertTrue(exception?.cause is CryptoException) {
|
||||
"actual: ${exception?.cause}, expected: CryptoException"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `successful dual block verification`() = runTest {
|
||||
// Given
|
||||
val blocks = listOf(file64B, file17B)
|
||||
|
||||
// When
|
||||
val verifierTokens = verifier.verifyBlocks(blocks).getOrThrow()
|
||||
|
||||
// Then
|
||||
assertEquals(blocks.size, verifierTokens.size) { "Verify blocks result size mismatch" }
|
||||
assertTrue(verifierTokens.containsKey(file64B)) { "File mismatch" }
|
||||
assertTrue(verifierTokens.containsKey(file17B)) { "File mismatch" }
|
||||
val verificationCodeXorHeaderOfFile64B = byteArrayOf(
|
||||
0x61, 0x60, 0x63, 0x62, 0x65, 0x64, 0x67, 0x66,
|
||||
0x69, 0x68, 0x6B, 0x6A, 0x6D, 0x6C, 0x6F, 0x6E,
|
||||
0x71, 0x70, 0x73, 0x72, 0x75, 0x74, 0x77, 0x76,
|
||||
0x79, 0x78, 0x7B, 0x7A, 0x7D, 0x7C, 0x7F, 0x7E
|
||||
)
|
||||
assertTrue(verifierTokens[file64B].contentEquals(verificationCodeXorHeaderOfFile64B)) {
|
||||
"Verifier token mismatch actual: ${verifierTokens[file64B]?.toHex()}, expected: ${verificationCodeXorHeaderOfFile64B.toHex()}"
|
||||
}
|
||||
val verificationCodeXorHeaderOfFile17B = byteArrayOf(
|
||||
0x61, 0x60, 0x63, 0x62, 0x65, 0x64, 0x67, 0x66,
|
||||
0x69, 0x68, 0x6B, 0x6A, 0x6D, 0x6C, 0x6F, 0x6E,
|
||||
0x71, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17,
|
||||
0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F,
|
||||
)
|
||||
assertTrue(verifierTokens[file17B].contentEquals(verificationCodeXorHeaderOfFile17B)) {
|
||||
"Verifier token mismatch actual: ${verifierTokens[file17B]?.toHex()}, expected: ${verificationCodeXorHeaderOfFile17B.toHex()}"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val VERIFICATION_CODE_SIZE = 32 // bytes
|
||||
}
|
||||
}
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.extension
|
||||
|
||||
import me.proton.core.test.kotlin.assertTrue
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class ByteArrayParameterizedTest(
|
||||
private val first: ByteArray,
|
||||
private val second: ByteArray,
|
||||
private val firstXorSecond: ByteArray,
|
||||
) {
|
||||
|
||||
@Test
|
||||
fun `xor of two byte arrays`() {
|
||||
assertTrue(
|
||||
first.xor(second).contentEquals(firstXorSecond)
|
||||
) { "xor failed" }
|
||||
}
|
||||
|
||||
companion object {
|
||||
@get:Parameterized.Parameters(name = "{0} xor {1} equals {2}")
|
||||
@get:JvmStatic
|
||||
val data = listOf(
|
||||
// Byte array with size 1
|
||||
arrayOf(
|
||||
byteArrayOf(0x00),
|
||||
byteArrayOf(0xFF.toByte()),
|
||||
byteArrayOf(0xFF.toByte()),
|
||||
),
|
||||
arrayOf(
|
||||
byteArrayOf(0xF0.toByte()),
|
||||
byteArrayOf(0x0F.toByte()),
|
||||
byteArrayOf(0xFF.toByte()),
|
||||
),
|
||||
// Byte array with size 2
|
||||
arrayOf(
|
||||
byteArrayOf(0x00, 0xFF.toByte()),
|
||||
byteArrayOf(0xFF.toByte(), 0x00),
|
||||
byteArrayOf(0xFF.toByte(), 0xFF.toByte()),
|
||||
),
|
||||
arrayOf(
|
||||
byteArrayOf(0x00, 0x55.toByte()),
|
||||
byteArrayOf(0xAA.toByte(), 0x00),
|
||||
byteArrayOf(0xAA.toByte(), 0x55.toByte()),
|
||||
),
|
||||
// Byte array with size 4
|
||||
arrayOf(
|
||||
byteArrayOf(0x05.toByte(), 0x06.toByte(), 0x07.toByte(), 0x08.toByte()),
|
||||
byteArrayOf(0xA3.toByte(), 0xA4.toByte(), 0xA5.toByte(), 0xA6.toByte()),
|
||||
byteArrayOf(0xA6.toByte(), 0xA2.toByte(), 0xA2.toByte(), 0xAE.toByte()),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
class ByteArrayTest {
|
||||
@Test(expected = IllegalArgumentException::class)
|
||||
fun `different size byte arrays cause IllegalArgumentException`() {
|
||||
byteArrayOf(0x00).xor(byteArrayOf(0x00, 0x00))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `xor of two empty byte array result in empty byte array`() {
|
||||
assertTrue(
|
||||
ByteArray(0).xor(ByteArray(0)).contentEquals(ByteArray(0))
|
||||
) { "xor failed" }
|
||||
}
|
||||
}
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.extension
|
||||
|
||||
import me.proton.core.drive.base.domain.extension.bytes
|
||||
import me.proton.core.drive.base.domain.extension.size
|
||||
import me.proton.core.test.kotlin.assertEquals
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import org.junit.runner.RunWith
|
||||
import org.junit.runners.Parameterized
|
||||
|
||||
@RunWith(Parameterized::class)
|
||||
class FileTest(
|
||||
private val fileSize: Int,
|
||||
) {
|
||||
|
||||
@get:Rule
|
||||
val temporaryFolder = TemporaryFolder()
|
||||
|
||||
@Test
|
||||
fun `head on different file size`() {
|
||||
// Given
|
||||
val fileSizeInBytes = fileSize.bytes
|
||||
val file = temporaryFolder.createFile(fileSizeInBytes)
|
||||
check(file.exists()) { "File does not exist" }
|
||||
check(file.size == fileSizeInBytes) {
|
||||
"File size mismatch expected: $fileSizeInBytes, actual: ${file.size}"
|
||||
}
|
||||
|
||||
// When
|
||||
val head = file.head(TARGET_HEAD_SIZE.bytes)
|
||||
|
||||
// Then
|
||||
assertEquals(TARGET_HEAD_SIZE, head.size) {
|
||||
"Head size mismatch"
|
||||
}
|
||||
for (i in 0 until minOf(fileSize, TARGET_HEAD_SIZE)) {
|
||||
assertEquals('a'.code, head[i].toInt()) { "Byte mismatch" }
|
||||
}
|
||||
for (i in fileSize until TARGET_HEAD_SIZE) {
|
||||
assertEquals(0, head[i].toInt()) { "Byte mismatch" }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TARGET_HEAD_SIZE = 32
|
||||
|
||||
@get:Parameterized.Parameters
|
||||
@get:JvmStatic
|
||||
val data = listOf(
|
||||
arrayOf(0),
|
||||
arrayOf(1),
|
||||
arrayOf(17),
|
||||
arrayOf(33),
|
||||
)
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.extension
|
||||
|
||||
import me.proton.core.drive.base.domain.entity.Bytes
|
||||
import org.junit.rules.TemporaryFolder
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
||||
fun TemporaryFolder.createFile(size: Bytes): File =
|
||||
File(root, "${size.value}_byte(s)_${UUID.randomUUID()}.txt").apply {
|
||||
if (exists()) { delete() }
|
||||
createNewFile()
|
||||
appendText(
|
||||
(0 until size.value.toInt()).joinToString(separator = "") { "a" }
|
||||
)
|
||||
}
|
||||
+166
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.data.repository
|
||||
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.mockk
|
||||
import junit.framework.TestCase.assertNull
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import me.proton.android.drive.verifier.data.api.VerifierApiDataSource
|
||||
import me.proton.android.drive.verifier.data.api.response.GetVerificationDataResponse
|
||||
import me.proton.android.drive.verifier.domain.entity.VerificationData
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.network.domain.ApiException
|
||||
import me.proton.core.network.domain.ApiResult
|
||||
import me.proton.core.test.kotlin.assertEquals
|
||||
import org.junit.Before
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import java.util.Base64
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class VerifierRepositoryTest {
|
||||
private val apiDataSource = mockk<VerifierApiDataSource>()
|
||||
private val userId = UserId("user-id")
|
||||
private val shareId = "share-id"
|
||||
private val linkId = "link-id"
|
||||
private val revisionId = "revision-id"
|
||||
private val contentKeyPacket = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
|
||||
private val verificationCode = Base64.getEncoder().encodeToString(ByteArray(32) { i -> i.toByte() })
|
||||
private lateinit var repository: VerifierRepositoryImpl
|
||||
|
||||
@Before
|
||||
fun before() {
|
||||
coEvery {
|
||||
apiDataSource.getVerificationData(
|
||||
userId = any(),
|
||||
shareId = any(),
|
||||
linkId = any(),
|
||||
revisionId = any(),
|
||||
)
|
||||
} returns GetVerificationDataResponse(
|
||||
code = 1000,
|
||||
verificationCode = verificationCode,
|
||||
contentKeyPacket = contentKeyPacket,
|
||||
)
|
||||
repository = VerifierRepositoryImpl(apiDataSource)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `successful remove of verification data removes it from repository cache`() = runTest {
|
||||
// When
|
||||
repository.getVerificationData(userId, shareId, linkId, revisionId)
|
||||
repository.removeVerificationData(userId, shareId, linkId, revisionId)
|
||||
|
||||
// Then
|
||||
val key = VerifierRepositoryImpl.VerificationDataKey(userId, shareId, linkId, revisionId)
|
||||
assertNull(repository.verificationDataCache[key])
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `successful verification data from verifier api data source and repository cache`() = runTest {
|
||||
// When
|
||||
val verificationData = repository.getVerificationData(userId, shareId, linkId, revisionId)
|
||||
|
||||
// Then
|
||||
val expectedVerificationData = VerificationData(
|
||||
contentKeyPacket = contentKeyPacket,
|
||||
verificationCode = Base64.getDecoder().decode(verificationCode),
|
||||
)
|
||||
assertEquals(expectedVerificationData, verificationData) { "Verification data mismatch from data source" }
|
||||
val key = VerifierRepositoryImpl.VerificationDataKey(userId, shareId, linkId, revisionId)
|
||||
assertEquals(expectedVerificationData, repository.verificationDataCache[key]) {
|
||||
"Verification data mismatch from cache"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cached verification data is provided when available`() = runTest {
|
||||
// Given
|
||||
repository.getVerificationData(userId, shareId, linkId, revisionId)
|
||||
coEvery {
|
||||
apiDataSource.getVerificationData(
|
||||
userId = any(),
|
||||
shareId = any(),
|
||||
linkId = any(),
|
||||
revisionId = any(),
|
||||
)
|
||||
} throws ApiException(ApiResult.Error.Http(httpCode = 500, message = "Internal server error"))
|
||||
|
||||
// When
|
||||
val verificationData = repository.getVerificationData(userId, shareId, linkId, revisionId)
|
||||
|
||||
// Then
|
||||
val expectedVerificationData = VerificationData(contentKeyPacket, Base64.getDecoder().decode(verificationCode))
|
||||
assertEquals(expectedVerificationData, verificationData) { "Verification data mismatch from data source" }
|
||||
}
|
||||
|
||||
@Test(expected = ApiException::class)
|
||||
fun `when network error occurs it is propagated`() = runTest {
|
||||
// Given
|
||||
coEvery {
|
||||
apiDataSource.getVerificationData(
|
||||
userId = any(),
|
||||
shareId = any(),
|
||||
linkId = any(),
|
||||
revisionId = any(),
|
||||
)
|
||||
} throws ApiException(ApiResult.Error.Http(httpCode = 500, message = "Internal server error"))
|
||||
|
||||
// Then
|
||||
repository.getVerificationData(userId, shareId, linkId, revisionId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `many concurrent calls to get same verification data leaves repository cache in consistent state`() = runTest {
|
||||
// When
|
||||
(0..99).map {
|
||||
async {
|
||||
repository.getVerificationData(userId, shareId, linkId, revisionId)
|
||||
}
|
||||
}.awaitAll()
|
||||
|
||||
// Then
|
||||
val key = VerifierRepositoryImpl.VerificationDataKey(userId, shareId, linkId, revisionId)
|
||||
assertEquals(1, repository.verificationDataCache.keys.size) { "Invalid cache size" }
|
||||
val expectedVerificationData = VerificationData(contentKeyPacket, Base64.getDecoder().decode(verificationCode))
|
||||
assertEquals(expectedVerificationData, repository.verificationDataCache[key]) {
|
||||
"Verification data mismatch from cache"
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `many concurrent calls to get different verification data and then to remove it leaves repository cache in consistent state`() = runTest {
|
||||
// When
|
||||
(0..99).map { i ->
|
||||
async {
|
||||
repository.getVerificationData(userId, shareId, linkId, "${revisionId}_$i")
|
||||
repository.removeVerificationData(userId, shareId, linkId, "${revisionId}_$i")
|
||||
}
|
||||
}.awaitAll()
|
||||
|
||||
// Then
|
||||
assertEquals(0, repository.verificationDataCache.keys.size) { "Invalid cache size" }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2021-2023 Proton AG.
|
||||
* This file is part of Proton Core.
|
||||
*
|
||||
* Proton Core is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Core is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Core. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
plugins {
|
||||
id("com.android.library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "me.proton.android.drive.verifier.domain"
|
||||
}
|
||||
|
||||
driveModule(
|
||||
hilt = true,
|
||||
) {
|
||||
api(project(":drive:key:domain"))
|
||||
api(project(":drive:link-upload:domain"))
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.domain.entity
|
||||
|
||||
data class VerificationData(
|
||||
val contentKeyPacket: String,
|
||||
val verificationCode: ByteArray,
|
||||
) {
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as VerificationData
|
||||
|
||||
if (contentKeyPacket != other.contentKeyPacket) return false
|
||||
if (!verificationCode.contentEquals(other.verificationCode)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = contentKeyPacket.hashCode()
|
||||
result = 31 * result + verificationCode.contentHashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.domain.entity
|
||||
|
||||
import java.io.File
|
||||
|
||||
interface Verifier {
|
||||
|
||||
suspend fun verifyBlocks(blocks: List<File>): Result<Map<File, ByteArray>>
|
||||
}
|
||||
+26
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.domain.exception
|
||||
|
||||
sealed class VerifierException(override val cause: Throwable) : Throwable() {
|
||||
|
||||
data class VerifyBlock(override val cause: Throwable) : VerifierException(cause)
|
||||
|
||||
data class Initialize(override val cause: Throwable) : VerifierException(cause)
|
||||
}
|
||||
+31
@@ -0,0 +1,31 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.domain.factory
|
||||
|
||||
import me.proton.android.drive.verifier.domain.entity.Verifier
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.drive.key.domain.entity.ContentKey
|
||||
|
||||
interface VerifierFactory {
|
||||
suspend fun create(
|
||||
userId: UserId,
|
||||
contentKey: ContentKey,
|
||||
verificationCode: ByteArray,
|
||||
): Verifier
|
||||
}
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.domain.repository
|
||||
|
||||
import me.proton.android.drive.verifier.domain.entity.VerificationData
|
||||
import me.proton.core.domain.entity.UserId
|
||||
|
||||
interface VerifierRepository {
|
||||
suspend fun getVerificationData(
|
||||
userId: UserId,
|
||||
shareId: String,
|
||||
linkId: String,
|
||||
revisionId: String,
|
||||
): VerificationData
|
||||
|
||||
suspend fun removeVerificationData(
|
||||
userId: UserId,
|
||||
shareId: String,
|
||||
linkId: String,
|
||||
revisionId: String,
|
||||
)
|
||||
}
|
||||
+57
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.domain.usecase
|
||||
|
||||
import me.proton.android.drive.verifier.domain.entity.Verifier
|
||||
import me.proton.android.drive.verifier.domain.exception.VerifierException
|
||||
import me.proton.android.drive.verifier.domain.factory.VerifierFactory
|
||||
import me.proton.android.drive.verifier.domain.repository.VerifierRepository
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import me.proton.core.drive.key.domain.entity.Key
|
||||
import me.proton.core.drive.key.domain.usecase.BuildContentKey
|
||||
import javax.inject.Inject
|
||||
|
||||
class BuildVerifier @Inject constructor(
|
||||
private val repository: VerifierRepository,
|
||||
private val buildContentKey: BuildContentKey,
|
||||
private val factory: VerifierFactory,
|
||||
) {
|
||||
suspend operator fun invoke(
|
||||
userId: UserId,
|
||||
shareId: String,
|
||||
linkId: String,
|
||||
revisionId: String,
|
||||
fileKey: Key.Node,
|
||||
): Result<Verifier> = try {
|
||||
val verificationData = repository.getVerificationData(userId, shareId, linkId, revisionId)
|
||||
val contentKey = buildContentKey(
|
||||
userId = userId,
|
||||
contentKeyPacket = verificationData.contentKeyPacket,
|
||||
contentKeyPacketSignature = "",
|
||||
fileKey = fileKey
|
||||
).getOrThrow()
|
||||
Result.success(factory.create(
|
||||
userId = userId,
|
||||
contentKey = contentKey,
|
||||
verificationCode = verificationData.verificationCode,
|
||||
))
|
||||
} catch (t: Throwable) {
|
||||
Result.failure(VerifierException.Initialize(t))
|
||||
}
|
||||
}
|
||||
+30
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2023 Proton AG.
|
||||
* This file is part of Proton Drive.
|
||||
*
|
||||
* Proton Drive is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* Proton Drive is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with Proton Drive. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package me.proton.android.drive.verifier.domain.usecase
|
||||
|
||||
import me.proton.android.drive.verifier.domain.repository.VerifierRepository
|
||||
import me.proton.core.domain.entity.UserId
|
||||
import javax.inject.Inject
|
||||
|
||||
class CleanupVerifier @Inject constructor(
|
||||
private val repository: VerifierRepository,
|
||||
) {
|
||||
suspend operator fun invoke(userId: UserId, shareId: String, linkId: String, revisionId: String) =
|
||||
repository.removeVerificationData(userId, shareId, linkId, revisionId)
|
||||
}
|
||||
Reference in New Issue
Block a user