feat(user-recovery): Added User Recovery modules.

Added GetRecoveryFile/GetRecoveryPrivateKeys.
Added GetRecoveryInactivePrivateKeys.
Added DeviceRecoveryHandler/DeviceRecoveryHandlerInitializer.
This commit is contained in:
Neil Marietta 2024-03-26 15:29:36 +01:00
parent f765277259
commit db6de90587
51 changed files with 1554 additions and 130 deletions

View File

@ -232,6 +232,13 @@ Core libraries coordinates can be found under [coordinates section](#coordinates
| me.proton.core:user-dagger |
| me.proton.core:user-data |
| me.proton.core:user-domain |
| me.proton.core:user-recovery |
| me.proton.core:user-recovery-dagger |
| me.proton.core:user-recovery-data |
| me.proton.core:user-recovery-domain |
| me.proton.core:user-recovery-presentation |
| me.proton.core:user-recovery-presentation-compose |
| me.proton.core:user-recovery-test |
| me.proton.core:user-settings |
| me.proton.core:user-settings-dagger |
| me.proton.core:user-settings-data |

View File

@ -233,6 +233,7 @@ dependencies {
project(Module.report),
project(Module.telemetry),
project(Module.user),
project(Module.userRecovery),
project(Module.userSettings),
project(Module.strictModeUtil),
project(Module.keyTransparency),

View File

@ -99,6 +99,10 @@
android:name="me.proton.core.paymentiap.presentation.GooglePurchaseHandlerInitializer"
android:value="androidx.startup"
tools:node="remove" />
<meta-data
android:name="me.proton.core.userrecovery.presentation.DeviceRecoveryHandlerInitializer"
android:value="androidx.startup"
tools:node="remove" />
</provider>
<activity

View File

@ -35,6 +35,7 @@ import me.proton.core.network.presentation.init.UnAuthSessionFetcherInitializer
import me.proton.core.paymentiap.presentation.GooglePurchaseHandlerInitializer
import me.proton.core.plan.presentation.PurchaseHandlerInitializer
import me.proton.core.plan.presentation.UnredeemedPurchaseInitializer
import me.proton.core.userrecovery.presentation.DeviceRecoveryHandlerInitializer
@HiltAndroidApp
class CoreExampleApp : Application() {
@ -44,6 +45,7 @@ class CoreExampleApp : Application() {
initializeComponent(WorkManagerInitializer::class.java)
initializeComponent(EventManagerInitializer::class.java)
initializeComponent(AccountStateHandlerInitializer::class.java)
initializeComponent(DeviceRecoveryHandlerInitializer::class.java)
initializeComponent(CryptoValidatorInitializer::class.java)
initializeComponent(PurchaseHandlerInitializer::class.java)
initializeComponent(GooglePurchaseHandlerInitializer::class.java)

View File

@ -176,6 +176,14 @@ public object Module {
public const val countryDomain: String = "$country:country-domain"
public const val countryPresentation: String = "$country:country-presentation"
// User Recovery
public const val userRecovery: String = ":user-recovery"
public const val userRecoveryDagger: String = "$userRecovery:user-recovery-dagger"
public const val userRecoveryData: String = "$userRecovery:user-recovery-data"
public const val userRecoveryDomain: String = "$userRecovery:user-recovery-domain"
public const val userRecoveryPresentation: String = "$userRecovery:user-recovery-presentation"
public const val userRecoveryPresentationCompose: String = "$userRecovery:user-recovery-presentation-compose"
// Settings
public const val userSettings: String = ":user-settings"
public const val userSettingsDagger: String = "$userSettings:user-settings-dagger"

View File

@ -0,0 +1,42 @@
/*
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
import studio.forface.easygradle.dsl.*
plugins {
protonAndroidLibrary
}
protonCoverage.disabled.set(true)
publishOption.shouldBePublishedAsLib = true
android {
namespace = "me.proton.core.userrecovery"
}
dependencies {
api(
project(Module.userRecoveryDagger),
project(Module.userRecoveryData),
project(Module.userRecoveryDomain),
project(Module.userRecoveryPresentation),
project(Module.userRecoveryPresentationCompose)
)
}
dependencyAnalysis.issues { onAny { severity("ignore") } }

View File

@ -0,0 +1,15 @@
public class hilt_aggregated_deps/_me_proton_core_userrecovery_dagger_CoreDeviceRecoveryModule {
public fun <init> ()V
}
public final class me/proton/core/userrecovery/dagger/BuildConfig {
public static final field BUILD_TYPE Ljava/lang/String;
public static final field DEBUG Z
public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
public fun <init> ()V
}
public final class me/proton/core/userrecovery/dagger/CoreDeviceRecoveryModule {
public static final field INSTANCE Lme/proton/core/userrecovery/dagger/CoreDeviceRecoveryModule;
}

View File

@ -0,0 +1,37 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
import studio.forface.easygradle.dsl.*
plugins {
protonAndroidLibrary
protonDagger
}
publishOption.shouldBePublishedAsLib = true
android {
namespace = "me.proton.core.userrecovery.dagger"
}
dependencies {
api(
project(Module.userRecoveryData),
project(Module.userRecoveryDomain),
)
}

View File

@ -0,0 +1,29 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.userrecovery.dagger
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
@Module
@InstallIn(SingletonComponent::class)
public object CoreDeviceRecoveryModule {
}

View File

@ -0,0 +1,88 @@
public class hilt_aggregated_deps/_me_proton_core_userrecovery_data_worker_SetRecoverySecretWorker_HiltModule {
public fun <init> ()V
}
public final class me/proton/core/userrecovery/data/BuildConfig {
public static final field BUILD_TYPE Ljava/lang/String;
public static final field DEBUG Z
public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
public fun <init> ()V
}
public final class me/proton/core/userrecovery/data/DeviceRecoveryHandler {
public fun <init> (Lme/proton/core/util/kotlin/CoroutineScopeProvider;Lme/proton/core/crypto/common/context/CryptoContext;Lme/proton/core/accountmanager/domain/AccountManager;Lme/proton/core/user/domain/UserManager;Lme/proton/core/userrecovery/domain/usecase/GetRecoveryFile;Lme/proton/core/userrecovery/domain/usecase/GetRecoveryPrivateKeys;Lme/proton/core/userrecovery/domain/usecase/GetRecoveryInactivePrivateKeys;)V
public final fun start ()V
}
public final class me/proton/core/userrecovery/data/DeviceRecoveryHandler_Factory : dagger/internal/Factory {
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/userrecovery/data/DeviceRecoveryHandler_Factory;
public synthetic fun get ()Ljava/lang/Object;
public fun get ()Lme/proton/core/userrecovery/data/DeviceRecoveryHandler;
public static fun newInstance (Lme/proton/core/util/kotlin/CoroutineScopeProvider;Lme/proton/core/crypto/common/context/CryptoContext;Lme/proton/core/accountmanager/domain/AccountManager;Lme/proton/core/user/domain/UserManager;Lme/proton/core/userrecovery/domain/usecase/GetRecoveryFile;Lme/proton/core/userrecovery/domain/usecase/GetRecoveryPrivateKeys;Lme/proton/core/userrecovery/domain/usecase/GetRecoveryInactivePrivateKeys;)Lme/proton/core/userrecovery/data/DeviceRecoveryHandler;
}
public final class me/proton/core/userrecovery/data/IsDeviceRecoveryEnabledImpl : me/proton/core/userrecovery/domain/IsDeviceRecoveryEnabled {
public static final field Companion Lme/proton/core/userrecovery/data/IsDeviceRecoveryEnabledImpl$Companion;
public fun <init> (Landroid/content/Context;Lme/proton/core/featureflag/domain/FeatureFlagManager;)V
public fun invoke (Lme/proton/core/domain/entity/UserId;)Z
public fun isLocalEnabled ()Z
public fun isRemoteEnabled (Lme/proton/core/domain/entity/UserId;)Z
}
public final class me/proton/core/userrecovery/data/IsDeviceRecoveryEnabledImpl$Companion {
public final fun getFeatureId ()Lme/proton/core/featureflag/domain/entity/FeatureId;
}
public final class me/proton/core/userrecovery/data/IsDeviceRecoveryEnabledImpl_Factory : dagger/internal/Factory {
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/userrecovery/data/IsDeviceRecoveryEnabledImpl_Factory;
public synthetic fun get ()Ljava/lang/Object;
public fun get ()Lme/proton/core/userrecovery/data/IsDeviceRecoveryEnabledImpl;
public static fun newInstance (Landroid/content/Context;Lme/proton/core/featureflag/domain/FeatureFlagManager;)Lme/proton/core/userrecovery/data/IsDeviceRecoveryEnabledImpl;
}
public final class me/proton/core/userrecovery/data/worker/SetRecoverySecretWorker : androidx/work/CoroutineWorker {
public static final field Companion Lme/proton/core/userrecovery/data/worker/SetRecoverySecretWorker$Companion;
public fun <init> (Landroid/content/Context;Landroidx/work/WorkerParameters;Lme/proton/core/userrecovery/domain/usecase/SetRecoverySecretRemote;)V
public fun doWork (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun getContext ()Landroid/content/Context;
}
public final class me/proton/core/userrecovery/data/worker/SetRecoverySecretWorker$Companion {
public final fun getRequest (Lme/proton/core/domain/entity/UserId;)Landroidx/work/OneTimeWorkRequest;
}
public abstract interface class me/proton/core/userrecovery/data/worker/SetRecoverySecretWorker_AssistedFactory : androidx/hilt/work/WorkerAssistedFactory {
}
public final class me/proton/core/userrecovery/data/worker/SetRecoverySecretWorker_AssistedFactory_Impl : me/proton/core/userrecovery/data/worker/SetRecoverySecretWorker_AssistedFactory {
public synthetic fun create (Landroid/content/Context;Landroidx/work/WorkerParameters;)Landroidx/work/ListenableWorker;
public fun create (Landroid/content/Context;Landroidx/work/WorkerParameters;)Lme/proton/core/userrecovery/data/worker/SetRecoverySecretWorker;
public static fun create (Lme/proton/core/userrecovery/data/worker/SetRecoverySecretWorker_Factory;)Ljavax/inject/Provider;
}
public final class me/proton/core/userrecovery/data/worker/SetRecoverySecretWorker_Factory {
public fun <init> (Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;)Lme/proton/core/userrecovery/data/worker/SetRecoverySecretWorker_Factory;
public fun get (Landroid/content/Context;Landroidx/work/WorkerParameters;)Lme/proton/core/userrecovery/data/worker/SetRecoverySecretWorker;
public static fun newInstance (Landroid/content/Context;Landroidx/work/WorkerParameters;Lme/proton/core/userrecovery/domain/usecase/SetRecoverySecretRemote;)Lme/proton/core/userrecovery/data/worker/SetRecoverySecretWorker;
}
public abstract interface class me/proton/core/userrecovery/data/worker/SetRecoverySecretWorker_HiltModule {
public abstract fun bind (Lme/proton/core/userrecovery/data/worker/SetRecoverySecretWorker_AssistedFactory;)Landroidx/hilt/work/WorkerAssistedFactory;
}
public final class me/proton/core/userrecovery/data/worker/UserRecoveryWorkerManagerImpl : me/proton/core/userrecovery/domain/worker/UserRecoveryWorkerManager {
public fun <init> (Landroidx/work/WorkManager;)V
public fun enqueueSetRecoverySecret (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class me/proton/core/userrecovery/data/worker/UserRecoveryWorkerManagerImpl_Factory : dagger/internal/Factory {
public fun <init> (Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;)Lme/proton/core/userrecovery/data/worker/UserRecoveryWorkerManagerImpl_Factory;
public synthetic fun get ()Ljava/lang/Object;
public fun get ()Lme/proton/core/userrecovery/data/worker/UserRecoveryWorkerManagerImpl;
public static fun newInstance (Landroidx/work/WorkManager;)Lme/proton/core/userrecovery/data/worker/UserRecoveryWorkerManagerImpl;
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
import studio.forface.easygradle.dsl.*
import studio.forface.easygradle.dsl.android.*
plugins {
protonAndroidLibrary
protonDagger
kotlin("plugin.serialization")
}
protonBuild {
apiModeDisabled()
}
protonCoverage {
branchCoveragePercentage.set(71)
lineCoveragePercentage.set(68)
}
publishOption.shouldBePublishedAsLib = true
protonDagger {
workManagerHiltIntegration = true
}
android {
namespace = "me.proton.core.userrecovery.data"
}
dependencies {
api(
project(Module.domain),
project(Module.userRecoveryDomain),
project(Module.eventManagerDomain),
`android-work-runtime`,
`coroutines-core`,
`javax-inject`,
retrofit,
`serialization-core`
)
implementation(
project(Module.data),
project(Module.kotlinUtil),
project(Module.userData)
)
testImplementation(
project(Module.androidTest),
project(Module.kotlinTest),
`android-work-testing`,
`coroutines-test`,
`hilt-android-testing`,
junit,
`kotlin-test`,
mockk,
robolectric
)
kaptTest(`hilt-android-compiler`)
}

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) 2024 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.userrecovery.data
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.launch
import me.proton.core.accountmanager.domain.AccountManager
import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.user.domain.UserManager
import me.proton.core.userrecovery.domain.LogTag
import me.proton.core.userrecovery.domain.usecase.GetRecoveryFile
import me.proton.core.userrecovery.domain.usecase.GetRecoveryInactivePrivateKeys
import me.proton.core.userrecovery.domain.usecase.GetRecoveryPrivateKeys
import me.proton.core.util.kotlin.CoreLogger
import me.proton.core.util.kotlin.CoroutineScopeProvider
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class DeviceRecoveryHandler @Inject constructor(
internal val scopeProvider: CoroutineScopeProvider,
internal val cryptoContext: CryptoContext,
internal val accountManager: AccountManager,
internal val userManager: UserManager,
internal val getRecoveryFile: GetRecoveryFile,
internal val getRecoveryPrivateKeys: GetRecoveryPrivateKeys,
internal val getRecoveryInactivePrivateKeys: GetRecoveryInactivePrivateKeys,
) {
fun start() {
scopeProvider.GlobalDefaultSupervisedScope.launch {
val userId = accountManager.getPrimaryUserId().firstOrNull() ?: return@launch
val message = getRecoveryFile(userId)
CoreLogger.d(LogTag.DEFAULT, "Recovery file: $message")
val keys = getRecoveryPrivateKeys(userId, message)
CoreLogger.d(LogTag.DEFAULT, "Recovery Private Keys: $keys")
val recoverable = getRecoveryInactivePrivateKeys(userId, keys)
CoreLogger.d(LogTag.DEFAULT, "Recovery inactive keys: $recoverable")
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* Copyright (c) 2024 Proton Technologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
@ -16,7 +16,7 @@
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.usersettings.data
package me.proton.core.userrecovery.data
import android.content.Context
import dagger.hilt.android.qualifiers.ApplicationContext
@ -24,7 +24,7 @@ import me.proton.core.domain.entity.UserId
import me.proton.core.featureflag.domain.ExperimentalProtonFeatureFlag
import me.proton.core.featureflag.domain.FeatureFlagManager
import me.proton.core.featureflag.domain.entity.FeatureId
import me.proton.core.usersettings.domain.IsDeviceRecoveryEnabled
import me.proton.core.userrecovery.domain.IsDeviceRecoveryEnabled
import javax.inject.Inject
class IsDeviceRecoveryEnabledImpl @Inject constructor(

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
@ -16,7 +16,7 @@
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.usersettings.data.worker
package me.proton.core.userrecovery.data.worker
import android.content.Context
import androidx.hilt.work.HiltWorker
@ -32,8 +32,8 @@ import dagger.assisted.AssistedInject
import me.proton.core.domain.entity.UserId
import me.proton.core.network.domain.ApiException
import me.proton.core.network.domain.isRetryable
import me.proton.core.usersettings.domain.LogTag
import me.proton.core.usersettings.domain.usecase.SetRecoverySecretRemote
import me.proton.core.userrecovery.domain.usecase.SetRecoverySecretRemote
import me.proton.core.userrecovery.domain.LogTag
import me.proton.core.util.kotlin.CoreLogger
@HiltWorker

View File

@ -0,0 +1,38 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.userrecovery.data.worker
import androidx.work.ExistingWorkPolicy
import androidx.work.WorkManager
import me.proton.core.domain.entity.UserId
import me.proton.core.userrecovery.domain.worker.UserRecoveryWorkerManager
import javax.inject.Inject
class UserRecoveryWorkerManagerImpl @Inject constructor(
private val workManager: WorkManager
) : UserRecoveryWorkerManager {
override suspend fun enqueueSetRecoverySecret(userId: UserId) {
workManager.enqueueUniqueWork(
"setRecoverySecretWork-${userId.id}",
ExistingWorkPolicy.KEEP,
SetRecoverySecretWorker.getRequest(userId)
)
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
@ -16,7 +16,7 @@
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.usersettings.data
package me.proton.core.userrecovery.data
import android.content.Context
import android.content.res.Resources

View File

@ -16,7 +16,7 @@
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.usersettings.data.worker
package me.proton.core.userrecovery.data.worker
import android.content.Context
import androidx.test.core.app.ApplicationProvider
@ -30,7 +30,7 @@ import kotlinx.coroutines.test.runTest
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.usersettings.domain.usecase.SetRecoverySecretRemote
import me.proton.core.userrecovery.domain.usecase.SetRecoverySecretRemote
import org.junit.Before
import org.junit.Test
import org.junit.runner.RunWith

View File

@ -0,0 +1,57 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.userrecovery.data.worker
import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.WorkManager
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.test.runTest
import me.proton.core.domain.entity.UserId
import org.junit.Before
import org.junit.Test
class UserRecoveryWorkerManagerImplTest {
private val testUserId = "test-user-id"
private val workManager = mockk<WorkManager>(relaxed = true)
private lateinit var tested: UserRecoveryWorkerManagerImpl
@Before
fun beforeEveryTest() {
tested = UserRecoveryWorkerManagerImpl(workManager)
}
@Test
fun setRecoverySecret() = runTest {
// WHEN
tested.enqueueSetRecoverySecret(UserId(testUserId))
// THEN
verify {
workManager.enqueueUniqueWork(
"setRecoverySecretWork-test-user-id",
ExistingWorkPolicy.KEEP,
any<OneTimeWorkRequest>()
)
}
}
}

View File

@ -0,0 +1,67 @@
public abstract interface class me/proton/core/userrecovery/domain/IsDeviceRecoveryEnabled {
public abstract fun invoke (Lme/proton/core/domain/entity/UserId;)Z
public abstract fun isLocalEnabled ()Z
public abstract fun isRemoteEnabled (Lme/proton/core/domain/entity/UserId;)Z
}
public final class me/proton/core/userrecovery/domain/LogTag {
public static final field DEFAULT Ljava/lang/String;
public static final field INSTANCE Lme/proton/core/userrecovery/domain/LogTag;
}
public final class me/proton/core/userrecovery/domain/usecase/ByteArrayList {
public static final field Companion Lme/proton/core/userrecovery/domain/usecase/ByteArrayList$Companion;
public fun <init> (Ljava/util/List;)V
public final fun component1 ()Ljava/util/List;
public final fun copy (Ljava/util/List;)Lme/proton/core/userrecovery/domain/usecase/ByteArrayList;
public static synthetic fun copy$default (Lme/proton/core/userrecovery/domain/usecase/ByteArrayList;Ljava/util/List;ILjava/lang/Object;)Lme/proton/core/userrecovery/domain/usecase/ByteArrayList;
public fun equals (Ljava/lang/Object;)Z
public final fun getKeys ()Ljava/util/List;
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}
public final class me/proton/core/userrecovery/domain/usecase/ByteArrayList$$serializer : kotlinx/serialization/internal/GeneratedSerializer {
public static final field INSTANCE Lme/proton/core/userrecovery/domain/usecase/ByteArrayList$$serializer;
public fun childSerializers ()[Lkotlinx/serialization/KSerializer;
public synthetic fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Ljava/lang/Object;
public fun deserialize (Lkotlinx/serialization/encoding/Decoder;)Lme/proton/core/userrecovery/domain/usecase/ByteArrayList;
public fun getDescriptor ()Lkotlinx/serialization/descriptors/SerialDescriptor;
public synthetic fun serialize (Lkotlinx/serialization/encoding/Encoder;Ljava/lang/Object;)V
public fun serialize (Lkotlinx/serialization/encoding/Encoder;Lme/proton/core/userrecovery/domain/usecase/ByteArrayList;)V
public fun typeParametersSerializers ()[Lkotlinx/serialization/KSerializer;
}
public final class me/proton/core/userrecovery/domain/usecase/ByteArrayList$Companion {
public final fun serializer ()Lkotlinx/serialization/KSerializer;
}
public final class me/proton/core/userrecovery/domain/usecase/GetRecoveryFile {
public fun <init> (Lme/proton/core/user/domain/UserManager;Lme/proton/core/crypto/common/context/CryptoContext;)V
public final fun invoke (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class me/proton/core/userrecovery/domain/usecase/GetRecoveryInactivePrivateKeys {
public fun <init> (Lme/proton/core/user/domain/UserManager;Lme/proton/core/crypto/common/context/CryptoContext;)V
public final fun invoke (Lme/proton/core/domain/entity/UserId;Ljava/util/List;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class me/proton/core/userrecovery/domain/usecase/GetRecoveryPrivateKeys {
public fun <init> (Lme/proton/core/user/domain/UserManager;Lme/proton/core/user/domain/repository/PassphraseRepository;Lme/proton/core/crypto/common/context/CryptoContext;)V
public final fun invoke (Lme/proton/core/domain/entity/UserId;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class me/proton/core/userrecovery/domain/usecase/GetRecoverySecret {
public fun <init> (Lme/proton/core/user/domain/UserManager;Lme/proton/core/crypto/common/context/CryptoContext;)V
public final fun invoke (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class me/proton/core/userrecovery/domain/usecase/SetRecoverySecretRemote {
public fun <init> (Lme/proton/core/eventmanager/domain/EventManagerProvider;Lme/proton/core/userrecovery/domain/usecase/GetRecoverySecret;Lme/proton/core/usersettings/domain/repository/UserSettingsRemoteDataSource;)V
public final fun invoke (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public abstract interface class me/proton/core/userrecovery/domain/worker/UserRecoveryWorkerManager {
public abstract fun enqueueSetRecoverySecret (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}

View File

@ -0,0 +1,62 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
import studio.forface.easygradle.dsl.*
plugins {
protonKotlinLibrary
kotlin("plugin.serialization")
}
protonBuild {
apiModeDisabled()
}
protonCoverage {
disabled.set(true)
//branchCoveragePercentage.set(38)
//lineCoveragePercentage.set(71)
}
publishOption.shouldBePublishedAsLib = true
dependencies {
api(
project(Module.domain),
project(Module.keyDomain),
project(Module.userDomain),
project(Module.userSettingsDomain),
project(Module.cryptoCommon),
project(Module.eventManagerDomain),
`coroutines-core`,
`javax-inject`,
)
implementation(
project(Module.kotlinUtil),
`serialization-json`
)
testImplementation(
project(Module.kotlinTest),
`coroutines-test`,
junit,
`kotlin-test`,
mockk
)
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
@ -16,7 +16,7 @@
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.usersettings.domain
package me.proton.core.userrecovery.domain
import me.proton.core.domain.entity.UserId

View File

@ -0,0 +1,24 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.userrecovery.domain
object LogTag {
/** Default tag for any other issue we need to log */
const val DEFAULT = "core.userrecovery.default"
}

View File

@ -0,0 +1,60 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.userrecovery.domain.usecase
import kotlinx.serialization.Serializable
import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.crypto.common.pgp.EncryptedMessage
import me.proton.core.domain.entity.UserId
import me.proton.core.key.domain.unlockOrNull
import me.proton.core.user.domain.UserManager
import me.proton.core.util.kotlin.serialize
import javax.inject.Inject
/**
* Generate a recovery file encrypted with primary recovery secret.
*/
class GetRecoveryFile @Inject constructor(
private val userManager: UserManager,
private val cryptoContext: CryptoContext
) {
private val pgpCrypto = cryptoContext.pgpCrypto
suspend operator fun invoke(
userId: UserId
): EncryptedMessage {
val user = userManager.getUser(userId)
val primaryKey = user.keys.firstOrNull { it.privateKey.isPrimary }
val primaryKeyRecoverySecret = requireNotNull(primaryKey?.recoverySecret)
val activeKeys = user.keys.filter { it.active ?: false }
val privateKeys = activeKeys.map { it.privateKey }
val unlockedKeys = privateKeys.mapNotNull { it.unlockOrNull(cryptoContext)?.unlockedKey }
check(unlockedKeys.isNotEmpty())
val byteArrayList = ByteArrayList(unlockedKeys.map { it.value })
val fileByteArray = byteArrayList.serialize().encodeToByteArray()
val secret = pgpCrypto.getBase64Decoded(primaryKeyRecoverySecret)
return pgpCrypto.encryptDataWithPassword(fileByteArray, secret)
}
}
@Deprecated("Replace with proper binary serialization (pgp).")
@Serializable
data class ByteArrayList(
val keys: List<ByteArray>
)

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.userrecovery.domain.usecase
import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.domain.entity.UserId
import me.proton.core.key.domain.canUnlock
import me.proton.core.key.domain.entity.key.PrivateKey
import me.proton.core.key.domain.fingerprint
import me.proton.core.user.domain.UserManager
import javax.inject.Inject
class GetRecoveryInactivePrivateKeys @Inject constructor(
private val userManager: UserManager,
private val cryptoContext: CryptoContext
) {
suspend operator fun invoke(
userId: UserId,
keys: List<PrivateKey>,
): List<PrivateKey> {
val user = userManager.getUser(userId)
val inactive = user.keys.filter { it.active?.not() ?: false }
val fingerprint = inactive.associateBy { it.privateKey.fingerprint(cryptoContext) }
val recoverable = keys.filter { it.fingerprint(cryptoContext) in fingerprint }
return recoverable.filter { it.canUnlock(cryptoContext) }
}
}

View File

@ -0,0 +1,69 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.userrecovery.domain.usecase
import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.crypto.common.pgp.EncryptedMessage
import me.proton.core.crypto.common.pgp.Unarmored
import me.proton.core.crypto.common.pgp.UnlockedKey
import me.proton.core.crypto.common.pgp.decryptDataWithPasswordOrNull
import me.proton.core.domain.entity.UserId
import me.proton.core.key.domain.entity.key.PrivateKey
import me.proton.core.key.domain.entity.key.UnlockedPrivateKey
import me.proton.core.key.domain.lock
import me.proton.core.user.domain.UserManager
import me.proton.core.user.domain.repository.PassphraseRepository
import me.proton.core.util.kotlin.deserialize
import javax.inject.Inject
class GetRecoveryPrivateKeys @Inject constructor(
private val userManager: UserManager,
private val passphraseRepository: PassphraseRepository,
private val cryptoContext: CryptoContext
) {
private val pgpCrypto = cryptoContext.pgpCrypto
suspend operator fun invoke(
userId: UserId,
message: EncryptedMessage,
): List<PrivateKey> {
val user = userManager.getUser(userId, refresh = true)
val secrets = user.keys.mapNotNull { key -> key.recoverySecret }
secrets.forEach { secret ->
val decodedSecret = pgpCrypto.getBase64Decoded(secret)
pgpCrypto.decryptDataWithPasswordOrNull(message, decodedSecret)?.let {
val byteArrayList = it.decodeToString().deserialize<ByteArrayList>()
val passphrase = checkNotNull(passphraseRepository.getPassphrase(userId))
return byteArrayList.keys.map { key ->
UnlockedPrivateKey(TempUnlockedKey(key), isPrimary = false).use { unlocked ->
unlocked.lock(cryptoContext, passphrase = passphrase, isPrimary = false)
}
}
}
}
return emptyList()
}
// Only used to create PrivateKey from UnlockedPrivateKey.
private class TempUnlockedKey(override val value: Unarmored) : UnlockedKey {
override fun close() {
value.fill(0)
}
}
}

View File

@ -16,7 +16,7 @@
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.usersettings.domain.usecase
package me.proton.core.userrecovery.domain.usecase
import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.crypto.common.pgp.Based64Encoded
@ -24,7 +24,7 @@ import me.proton.core.crypto.common.pgp.EncryptedSignature
import me.proton.core.domain.entity.UserId
import me.proton.core.key.domain.generateNewToken
import me.proton.core.key.domain.getBase64Encoded
import me.proton.core.key.domain.signData
import me.proton.core.key.domain.signText
import me.proton.core.key.domain.useKeys
import me.proton.core.user.domain.UserManager
import javax.inject.Inject
@ -32,7 +32,7 @@ import javax.inject.Inject
/**
* Generate and sign a new user primary recovery secret.
*/
class GenerateRecoverySecret @Inject constructor(
class GetRecoverySecret @Inject constructor(
private val userManager: UserManager,
private val cryptoContext: CryptoContext
) {
@ -42,7 +42,7 @@ class GenerateRecoverySecret @Inject constructor(
return userManager.getUser(userId).useKeys(cryptoContext) {
val token = generateNewToken(32)
val secret = getBase64Encoded(token)
val signature = signData(token)
val signature = signText(secret)
secret to signature
}
}

View File

@ -16,7 +16,7 @@
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.usersettings.domain.usecase
package me.proton.core.userrecovery.domain.usecase
import me.proton.core.domain.entity.UserId
import me.proton.core.eventmanager.domain.EventManagerConfig
@ -30,14 +30,14 @@ import javax.inject.Inject
*/
class SetRecoverySecretRemote @Inject constructor(
private val eventManagerProvider: EventManagerProvider,
private val generateRecoverySecret: GenerateRecoverySecret,
private val getRecoverySecret: GetRecoverySecret,
private val userSettingsRemoteDataSource: UserSettingsRemoteDataSource
) {
suspend operator fun invoke(
userId: UserId
) {
eventManagerProvider.suspend(EventManagerConfig.Core(userId)) {
val (secret, signature) = generateRecoverySecret(userId)
val (secret, signature) = getRecoverySecret(userId)
userSettingsRemoteDataSource.setRecoverySecret(userId, secret, signature)
}
}

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.userrecovery.domain.worker
import me.proton.core.domain.entity.UserId
interface UserRecoveryWorkerManager {
/**
* Set the user primary key recovery secret, remotely, in background.
*
* Note: Once remotely set, local will be updated asap.
*/
suspend fun enqueueSetRecoverySecret(userId: UserId)
}

View File

@ -0,0 +1,118 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.userrecovery.domain.usecase
import io.mockk.coEvery
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkConstructor
import io.mockk.mockkStatic
import me.proton.core.crypto.common.context.CryptoContext
import me.proton.core.crypto.common.keystore.EncryptedByteArray
import me.proton.core.crypto.common.pgp.PGPCrypto
import me.proton.core.crypto.common.pgp.exception.CryptoException
import me.proton.core.domain.entity.UserId
import me.proton.core.key.domain.canUnlock
import me.proton.core.key.domain.entity.key.PrivateKey
import me.proton.core.key.domain.entity.key.UnlockedPrivateKey
import me.proton.core.key.domain.fingerprint
import me.proton.core.key.domain.lock
import me.proton.core.key.domain.unlockOrNull
import me.proton.core.user.domain.UserManager
import me.proton.core.user.domain.entity.User
import me.proton.core.user.domain.entity.UserKey
import me.proton.core.user.domain.repository.PassphraseRepository
import org.junit.Before
/**
* Test data: 1 User -> 2 UserKey -> 1 primary/active and 1 inactive.
*/
abstract class BaseUserKeysTest {
internal val testSecretValid = "valid"
internal val testSecretInvalid = "invalid"
internal val unlockedKey = mockk<UnlockedPrivateKey> {
every { this@mockk.unlockedKey } returns mockk { every { value } returns "unlocked".toByteArray() }
}
internal val testPrivateKeyInactive = mockk<PrivateKey> {
every { this@mockk.isActive } returns false
every { this@mockk.isPrimary } returns false
every { this@mockk.key } returns "inactive.key"
}
internal val testPrivateKeyPrimary = mockk<PrivateKey> {
every { this@mockk.isActive } returns true
every { this@mockk.isPrimary } returns true
every { this@mockk.key } returns "active.key"
}
internal val testKey1 = mockk<UserKey> {
every { this@mockk.privateKey } returns testPrivateKeyPrimary
every { this@mockk.recoverySecret } returns testSecretValid
every { this@mockk.active } returns true
}
internal val testKey2 = mockk<UserKey> {
every { this@mockk.privateKey } returns testPrivateKeyInactive
every { this@mockk.recoverySecret } returns testSecretInvalid
every { this@mockk.active } returns false
}
internal val testUser = mockk<User> {
every { this@mockk.userId } returns UserId("userId")
every { this@mockk.keys } returns listOf(testKey1, testKey2)
}
internal val testUserManager = mockk<UserManager> {
coEvery { this@mockk.getUser(any(), any()) } returns testUser
}
internal val testDecodedSecret1 = "decodedSecret1".toByteArray()
internal val testDecodedSecret2 = "decodedSecret2".toByteArray()
internal val testFingerprint1 = "fingerprint1"
internal val testFingerprint2 = "fingerprint2"
internal val testPgpCrypto = mockk<PGPCrypto>(relaxed = true) {
every { this@mockk.getBase64Decoded(testSecretValid) } returns testDecodedSecret1
every { this@mockk.getBase64Decoded(testSecretInvalid) } returns testDecodedSecret2
every { this@mockk.decryptDataWithPassword(any(), testDecodedSecret1) } returns "{\"keys\":[[0]]}".toByteArray()
every { this@mockk.decryptDataWithPassword(any(), testDecodedSecret2) } throws CryptoException()
}
internal val testCryptoContext = mockk<CryptoContext>(relaxed = true) {
every { this@mockk.pgpCrypto } returns testPgpCrypto
}
internal val encryptedPassphrase = EncryptedByteArray("passphrase".toByteArray())
internal val testPassphraseRepository = mockk<PassphraseRepository> {
coEvery { this@mockk.getPassphrase(any()) } returns encryptedPassphrase
}
@Before
open fun before() {
mockkStatic(PrivateKey::unlockOrNull)
every { testPrivateKeyPrimary.unlockOrNull(any()) } returns unlockedKey
every { testPrivateKeyInactive.unlockOrNull(any()) } returns unlockedKey
every { testPrivateKeyPrimary.fingerprint(any()) } returns testFingerprint1
every { testPrivateKeyInactive.fingerprint(any()) } returns testFingerprint2
every { testPrivateKeyPrimary.canUnlock(any()) } returns true
every { testPrivateKeyInactive.canUnlock(any()) } returns true
mockkStatic(UnlockedPrivateKey::lock)
mockkConstructor(UnlockedPrivateKey::class)
every { anyConstructed<UnlockedPrivateKey>().lock(any(), any()) } returns testPrivateKeyInactive
}
}

View File

@ -0,0 +1,90 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.userrecovery.domain.usecase
import io.mockk.every
import io.mockk.verify
import kotlinx.coroutines.test.runTest
import me.proton.core.key.domain.unlockOrNull
import org.junit.Before
import org.junit.Test
import kotlin.test.assertFailsWith
class GetRecoveryFileTest : BaseUserKeysTest() {
private lateinit var tested: GetRecoveryFile
@Before
override fun before() {
super.before()
tested = GetRecoveryFile(
userManager = testUserManager,
cryptoContext = testCryptoContext
)
}
@Test
fun getRecoverFileHappyPath() = runTest {
// WHEN
tested.invoke(testUser.userId)
// THEN
verify(exactly = 1) { testPrivateKeyPrimary.unlockOrNull(any()) }
verify(exactly = 0) { testPrivateKeyInactive.unlockOrNull(any()) }
verify(exactly = 1) { testPgpCrypto.getBase64Decoded(testSecretValid) }
verify(exactly = 0) { testPgpCrypto.getBase64Decoded(testSecretInvalid) }
verify(exactly = 1) { testPgpCrypto.encryptDataWithPassword(any(), testDecodedSecret1) }
verify(exactly = 0) { testPgpCrypto.encryptDataWithPassword(any(), testDecodedSecret2) }
}
@Test
fun getRecoverFileThrowIllegalArgumentWhenNoPrimary() = runTest {
// GIVEN
every { testPrivateKeyPrimary.isPrimary } returns false
// WHEN
assertFailsWith<IllegalArgumentException> {
tested.invoke(testUser.userId)
}
}
@Test
fun getRecoverFileThrowIllegalArgumentWhenNoSecret() = runTest {
// GIVEN
every { testKey1.recoverySecret } returns null
every { testKey2.recoverySecret } returns null
// WHEN
assertFailsWith<IllegalArgumentException> {
tested.invoke(testUser.userId)
}
}
@Test
fun getRecoverFileThrowIllegalStateWhenNoActive() = runTest {
// GIVEN
every { testKey1.active } returns false
every { testKey2.active } returns false
// WHEN
assertFailsWith<IllegalStateException> {
tested.invoke(testUser.userId)
}
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.userrecovery.domain.usecase
import io.mockk.every
import kotlinx.coroutines.test.runTest
import me.proton.core.key.domain.canUnlock
import org.junit.Before
import org.junit.Test
import kotlin.test.assertContains
import kotlin.test.assertTrue
class GetRecoveryInactivePrivateKeysTest : BaseUserKeysTest() {
private lateinit var tested: GetRecoveryInactivePrivateKeys
@Before
override fun before() {
super.before()
tested = GetRecoveryInactivePrivateKeys(
userManager = testUserManager,
cryptoContext = testCryptoContext
)
}
@Test
fun getRecoveryInactivePrivateKeysHappyPath() = runTest {
// GIVEN
val recoverable = listOf(testPrivateKeyPrimary, testPrivateKeyInactive)
// WHEN
val result = tested.invoke(testUser.userId, recoverable)
// THEN
assertTrue(result.size == 1)
assertContains(result, testPrivateKeyInactive)
}
@Test
fun getRecoveryInactivePrivateKeysCannotUnlock() = runTest {
// GIVEN
every { testPrivateKeyPrimary.canUnlock(any()) } returns false
every { testPrivateKeyInactive.canUnlock(any()) } returns false
val recoverable = listOf(testPrivateKeyPrimary, testPrivateKeyInactive)
// WHEN
val result = tested.invoke(testUser.userId, recoverable)
// THEN
assertTrue(result.isEmpty())
}
}

View File

@ -0,0 +1,94 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.userrecovery.domain.usecase
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.test.runTest
import me.proton.core.crypto.common.keystore.EncryptedByteArray
import me.proton.core.crypto.common.pgp.exception.CryptoException
import me.proton.core.user.domain.repository.PassphraseRepository
import org.junit.Before
import org.junit.Test
import kotlin.test.assertFailsWith
import kotlin.test.assertTrue
class GetRecoveryPrivateKeysTest : BaseUserKeysTest() {
private lateinit var tested: GetRecoveryPrivateKeys
@Before
override fun before() {
super.before()
tested = GetRecoveryPrivateKeys(
userManager = testUserManager,
passphraseRepository = testPassphraseRepository,
cryptoContext = testCryptoContext
)
}
@Test
fun getRecoveryPrivateKeysHappyPath() = runTest {
// WHEN
val result = tested.invoke(testUser.userId, "encryptedMessage")
// THEN
assertTrue(result.size == 1)
verify(exactly = 1) { testPgpCrypto.getBase64Decoded(testSecretValid) }
verify(exactly = 0) { testPgpCrypto.getBase64Decoded(testSecretInvalid) }
verify(exactly = 1) { testPgpCrypto.decryptDataWithPassword(any(), testDecodedSecret1) }
verify(exactly = 0) { testPgpCrypto.decryptDataWithPassword(any(), testDecodedSecret2) }
}
@Test
fun getRecoveryPrivateKeysRefreshUser() = runTest {
// WHEN
tested.invoke(testUser.userId, "encryptedMessage")
// THEN
coVerify { testUserManager.getUser(any(), refresh = true) }
}
@Test
fun getRecoveryPrivateKeysReturnsEmptyListWhenDecryptFail() = runTest {
// GIVEN
every { testPgpCrypto.decryptDataWithPassword(any(), any()) } throws CryptoException()
// WHEN
val result = tested.invoke(testUser.userId, "encryptedMessage")
// THEN
assertTrue(result.isEmpty())
}
@Test
fun getRecoveryPrivateKeysThrowIllegalStateWhenNoPassphrase() = runTest {
// GIVEN
coEvery { testPassphraseRepository.getPassphrase(any()) } returns null
// WHEN
assertFailsWith<IllegalStateException> {
tested.invoke(testUser.userId, "encryptedMessage")
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (c) 2024 Proton Technologies AG
* Copyright (c) 2024 ProtonTechnologies AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
@ -16,7 +16,7 @@
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.usersettings.domain.usecase
package me.proton.core.userrecovery.domain.usecase
import io.mockk.coEvery
import io.mockk.coVerify
@ -44,7 +44,7 @@ class SetRecoverySecretRemoteTest {
lambda<(suspend () -> Unit)>().captured()
}
}
private val generateRecoverySecret: GenerateRecoverySecret = mockk(relaxed = true) {
private val getRecoverySecret: GetRecoverySecret = mockk(relaxed = true) {
coEvery { this@mockk.invoke(any()) } returns Pair(secret, signature)
}
private val userSettingsRemoteDataSource: UserSettingsRemoteDataSource = mockk(relaxed = true)
@ -54,7 +54,7 @@ class SetRecoverySecretRemoteTest {
@Before
fun setup() {
mockkStatic("me.proton.core.eventmanager.domain.extension.EventManagerKt")
tested = SetRecoverySecretRemote(eventManagerProvider, generateRecoverySecret, userSettingsRemoteDataSource)
tested = SetRecoverySecretRemote(eventManagerProvider, getRecoverySecret, userSettingsRemoteDataSource)
}
@After

View File

@ -0,0 +1,7 @@
public final class me/proton/core/userrecovery/presentation/compose/BuildConfig {
public static final field BUILD_TYPE Ljava/lang/String;
public static final field DEBUG Z
public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
public fun <init> ()V
}

View File

@ -0,0 +1,83 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
import studio.forface.easygradle.dsl.*
import studio.forface.easygradle.dsl.android.*
plugins {
protonComposeUiLibrary
protonDagger
}
protonBuild {
apiModeDisabled()
}
publishOption.shouldBePublishedAsLib = true
android {
namespace = "me.proton.core.userrecovery.presentation.compose"
}
dependencies {
api(
project(Module.presentationCompose),
project(Module.userRecoveryDomain),
project(Module.userRecoveryPresentation),
`compose-foundation`,
`compose-foundation-layout`,
`compose-material`,
`compose-runtime`,
`compose-ui`,
`compose-ui-graphics`,
`compose-ui-text`,
`coroutines-core`,
`hilt-android`,
`hilt-navigation-compose`,
`lifecycle-common`,
`lifecycle-viewModel`,
)
implementation(
project(Module.presentation),
`android-ktx`,
`appcompat`,
`compose-animation-core`,
`compose-material-icons-core`,
`compose-material3`,
`compose-ui-tooling-preview`,
`compose-ui-unit`,
`lifecycle-runtime`,
)
debugImplementation(
`compose-ui-tooling`,
)
androidTestImplementation(
`android-test-runner`,
`compose-ui-test`,
`compose-ui-test-junit`,
`compose-ui-test-manifest`,
`junit`,
`junit-ktx`,
`kotlin-test`
)
}

View File

@ -0,0 +1,23 @@
public class hilt_aggregated_deps/_me_proton_core_userrecovery_presentation_DeviceRecoveryHandlerInitializer_DeviceRecoveryHandlerInitializerEntryPoint {
public fun <init> ()V
}
public final class me/proton/core/userrecovery/presentation/BuildConfig {
public static final field BUILD_TYPE Ljava/lang/String;
public static final field DEBUG Z
public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
public fun <init> ()V
}
public final class me/proton/core/userrecovery/presentation/DeviceRecoveryHandlerInitializer : androidx/startup/Initializer {
public static final field $stable I
public fun <init> ()V
public synthetic fun create (Landroid/content/Context;)Ljava/lang/Object;
public fun create (Landroid/content/Context;)V
public fun dependencies ()Ljava/util/List;
}
public abstract interface class me/proton/core/userrecovery/presentation/DeviceRecoveryHandlerInitializer$DeviceRecoveryHandlerInitializerEntryPoint {
public abstract fun deviceRecoveryHandler ()Lme/proton/core/userrecovery/data/DeviceRecoveryHandler;
}

View File

@ -0,0 +1,84 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
import studio.forface.easygradle.dsl.*
import studio.forface.easygradle.dsl.android.*
plugins {
protonComposeUiLibrary
protonDagger
id("kotlin-parcelize")
}
protonBuild {
apiModeDisabled()
}
protonCoverage {
//branchCoveragePercentage.set(26)
//lineCoveragePercentage.set(55)
}
publishOption.shouldBePublishedAsLib = true
android {
namespace = "me.proton.core.userrecovery.presentation"
buildFeatures {
viewBinding = true
}
}
dependencies {
api(
project(Module.domain),
project(Module.presentation),
project(Module.userRecoveryData),
project(Module.userRecoveryDomain),
activity,
`compose-ui`,
`constraint-layout`,
`coroutines-core`,
`hilt-android`,
material
)
implementation(
// Core
project(Module.kotlinUtil),
// Android
`android-ktx`,
appcompat,
fragment,
`lifecycle-common`,
`lifecycle-runtime`,
`lifecycle-viewModel`,
)
testImplementation(
project(Module.androidTest),
project(Module.kotlinTest),
`android-arch-testing`,
`coroutines-test`,
junit,
`kotlin-test`,
mockk,
turbine
)
}

View File

@ -0,0 +1,35 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2024 Proton AG
~ This file is part of Proton AG and ProtonCore.
~
~ ProtonCore 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.
~
~ ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<application>
<provider
android:name="androidx.startup.InitializationProvider"
android:authorities="${applicationId}.androidx-startup"
android:exported="false"
tools:node="merge">
<meta-data
android:name="me.proton.core.userrecovery.presentation.DeviceRecoveryHandlerInitializer"
android:value="androidx.startup" />
</provider>
</application>
</manifest>

View File

@ -0,0 +1,43 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.userrecovery.presentation
import android.content.Context
import androidx.startup.Initializer
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import me.proton.core.userrecovery.data.DeviceRecoveryHandler
class DeviceRecoveryHandlerInitializer : Initializer<Unit> {
override fun create(context: Context) = EntryPointAccessors.fromApplication(
context.applicationContext,
DeviceRecoveryHandlerInitializerEntryPoint::class.java
).deviceRecoveryHandler().start()
override fun dependencies(): List<Class<out Initializer<*>?>> = emptyList()
@EntryPoint
@InstallIn(SingletonComponent::class)
interface DeviceRecoveryHandlerInitializerEntryPoint {
fun deviceRecoveryHandler(): DeviceRecoveryHandler
}
}

View File

@ -0,0 +1,7 @@
public final class me/proton/core/userrecovery/test/BuildConfig {
public static final field BUILD_TYPE Ljava/lang/String;
public static final field DEBUG Z
public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String;
public fun <init> ()V
}

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2024 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore 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.
*
* ProtonCore 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 ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
import studio.forface.easygradle.dsl.android.retrofit
import studio.forface.easygradle.dsl.api
import studio.forface.easygradle.dsl.`coroutines-core`
import studio.forface.easygradle.dsl.implementation
import studio.forface.easygradle.dsl.`kotlin-test`
import studio.forface.easygradle.dsl.`kotlin-test-junit`
plugins {
protonAndroidLibrary
}
protonCoverage.disabled.set(true)
publishOption.shouldBePublishedAsLib = true
android {
namespace = "me.proton.core.userrecovery.test"
}
dependencies {
api(
project(Module.quark),
junit
)
implementation(
project(Module.androidInstrumentedTest),
`androidx-test-monitor`,
`compose-ui-test-junit`,
`coroutines-core`,
fusion,
`kotlin-test`,
`kotlin-test-junit`,
retrofit,
uiautomator
)
}

View File

@ -2,10 +2,6 @@ public class hilt_aggregated_deps/_me_proton_core_usersettings_data_worker_Fetch
public fun <init> ()V
}
public class hilt_aggregated_deps/_me_proton_core_usersettings_data_worker_SetRecoverySecretWorker_HiltModule {
public fun <init> ()V
}
public class hilt_aggregated_deps/_me_proton_core_usersettings_data_worker_UpdateUserSettingsWorker_HiltModule {
public fun <init> ()V
}
@ -17,26 +13,6 @@ public final class me/proton/core/usersettings/data/BuildConfig {
public fun <init> ()V
}
public final class me/proton/core/usersettings/data/IsDeviceRecoveryEnabledImpl : me/proton/core/usersettings/domain/IsDeviceRecoveryEnabled {
public static final field Companion Lme/proton/core/usersettings/data/IsDeviceRecoveryEnabledImpl$Companion;
public fun <init> (Landroid/content/Context;Lme/proton/core/featureflag/domain/FeatureFlagManager;)V
public fun invoke (Lme/proton/core/domain/entity/UserId;)Z
public fun isLocalEnabled ()Z
public fun isRemoteEnabled (Lme/proton/core/domain/entity/UserId;)Z
}
public final class me/proton/core/usersettings/data/IsDeviceRecoveryEnabledImpl$Companion {
public final fun getFeatureId ()Lme/proton/core/featureflag/domain/entity/FeatureId;
}
public final class me/proton/core/usersettings/data/IsDeviceRecoveryEnabledImpl_Factory : dagger/internal/Factory {
public fun <init> (Ljavax/inject/Provider;Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;Ljavax/inject/Provider;)Lme/proton/core/usersettings/data/IsDeviceRecoveryEnabledImpl_Factory;
public synthetic fun get ()Ljava/lang/Object;
public fun get ()Lme/proton/core/usersettings/data/IsDeviceRecoveryEnabledImpl;
public static fun newInstance (Landroid/content/Context;Lme/proton/core/featureflag/domain/FeatureFlagManager;)Lme/proton/core/usersettings/data/IsDeviceRecoveryEnabledImpl;
}
public class me/proton/core/usersettings/data/UserSettingsEventListener : me/proton/core/eventmanager/domain/EventListener {
public fun <init> (Lme/proton/core/usersettings/data/db/UserSettingsDatabase;Lme/proton/core/usersettings/domain/repository/UserSettingsRepository;)V
public fun deserializeEvents (Lme/proton/core/eventmanager/domain/EventManagerConfig;Lme/proton/core/eventmanager/domain/entity/EventsResponse;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@ -973,7 +949,6 @@ public final class me/proton/core/usersettings/data/repository/UserSettingsRepos
public fun getUserSettings (Lme/proton/core/domain/entity/UserId;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun getUserSettingsFlow (Lme/proton/core/domain/entity/UserId;Z)Lkotlinx/coroutines/flow/Flow;
public fun markAsStale (Lme/proton/core/domain/entity/UserId;)V
public fun setRecoverySecret (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun setUsername (Lme/proton/core/domain/entity/UserId;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun updateCrashReports (Lme/proton/core/domain/entity/UserId;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public fun updateLoginPassword (Lme/proton/core/domain/entity/UserId;Lme/proton/core/crypto/common/srp/SrpProofs;Ljava/lang/String;Ljava/lang/String;Lme/proton/core/crypto/common/srp/Auth;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@ -1021,37 +996,6 @@ public abstract interface class me/proton/core/usersettings/data/worker/FetchUse
public abstract fun bind (Lme/proton/core/usersettings/data/worker/FetchUserSettingsWorker_AssistedFactory;)Landroidx/hilt/work/WorkerAssistedFactory;
}
public final class me/proton/core/usersettings/data/worker/SetRecoverySecretWorker : androidx/work/CoroutineWorker {
public static final field Companion Lme/proton/core/usersettings/data/worker/SetRecoverySecretWorker$Companion;
public fun <init> (Landroid/content/Context;Landroidx/work/WorkerParameters;Lme/proton/core/usersettings/domain/usecase/SetRecoverySecretRemote;)V
public fun doWork (Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public final fun getContext ()Landroid/content/Context;
}
public final class me/proton/core/usersettings/data/worker/SetRecoverySecretWorker$Companion {
public final fun getRequest (Lme/proton/core/domain/entity/UserId;)Landroidx/work/OneTimeWorkRequest;
}
public abstract interface class me/proton/core/usersettings/data/worker/SetRecoverySecretWorker_AssistedFactory : androidx/hilt/work/WorkerAssistedFactory {
}
public final class me/proton/core/usersettings/data/worker/SetRecoverySecretWorker_AssistedFactory_Impl : me/proton/core/usersettings/data/worker/SetRecoverySecretWorker_AssistedFactory {
public synthetic fun create (Landroid/content/Context;Landroidx/work/WorkerParameters;)Landroidx/work/ListenableWorker;
public fun create (Landroid/content/Context;Landroidx/work/WorkerParameters;)Lme/proton/core/usersettings/data/worker/SetRecoverySecretWorker;
public static fun create (Lme/proton/core/usersettings/data/worker/SetRecoverySecretWorker_Factory;)Ljavax/inject/Provider;
}
public final class me/proton/core/usersettings/data/worker/SetRecoverySecretWorker_Factory {
public fun <init> (Ljavax/inject/Provider;)V
public static fun create (Ljavax/inject/Provider;)Lme/proton/core/usersettings/data/worker/SetRecoverySecretWorker_Factory;
public fun get (Landroid/content/Context;Landroidx/work/WorkerParameters;)Lme/proton/core/usersettings/data/worker/SetRecoverySecretWorker;
public static fun newInstance (Landroid/content/Context;Landroidx/work/WorkerParameters;Lme/proton/core/usersettings/domain/usecase/SetRecoverySecretRemote;)Lme/proton/core/usersettings/data/worker/SetRecoverySecretWorker;
}
public abstract interface class me/proton/core/usersettings/data/worker/SetRecoverySecretWorker_HiltModule {
public abstract fun bind (Lme/proton/core/usersettings/data/worker/SetRecoverySecretWorker_AssistedFactory;)Landroidx/hilt/work/WorkerAssistedFactory;
}
public final class me/proton/core/usersettings/data/worker/UpdateUserSettingsWorker : androidx/work/CoroutineWorker {
public static final field Companion Lme/proton/core/usersettings/data/worker/UpdateUserSettingsWorker$Companion;
public fun <init> (Landroid/content/Context;Landroidx/work/WorkerParameters;Lme/proton/core/usersettings/domain/usecase/UpdateUserSettingsRemote;Lme/proton/core/usersettings/domain/repository/UserSettingsRepository;)V

View File

@ -29,7 +29,7 @@ protonBuild {
}
protonCoverage {
branchCoveragePercentage.set(62)
branchCoveragePercentage.set(59)
lineCoveragePercentage.set(81)
}

View File

@ -36,7 +36,6 @@ import me.proton.core.domain.entity.SessionUserId
import me.proton.core.domain.entity.UserId
import me.proton.core.usersettings.data.extension.toUserSettingsPropertySerializable
import me.proton.core.usersettings.data.worker.FetchUserSettingsWorker
import me.proton.core.usersettings.data.worker.SetRecoverySecretWorker
import me.proton.core.usersettings.data.worker.UpdateUserSettingsWorker
import me.proton.core.usersettings.domain.entity.UserSettings
import me.proton.core.usersettings.domain.entity.UserSettingsProperty
@ -71,14 +70,6 @@ class UserSettingsRepositoryImpl @Inject constructor(
override suspend fun setUsername(sessionUserId: SessionUserId, username: String): Boolean =
remoteDataSource.setUsername(sessionUserId, username)
override suspend fun setRecoverySecret(userId: UserId) {
workManager.enqueueUniqueWork(
"setRecoverySecretWork-${userId.id}",
ExistingWorkPolicy.KEEP,
SetRecoverySecretWorker.getRequest(userId)
)
}
override suspend fun updateUserSettings(userSettings: UserSettings) {
localDataSource.insertOrUpdate(userSettings)
}

View File

@ -399,20 +399,6 @@ class UserSettingsRepositoryImplTest {
}
}
@Test
fun setRecoverySecret() = runTest(dispatcherProvider.Main) {
// WHEN
repository.setRecoverySecret(UserId(testUserId))
// THEN
verify {
workManager.enqueueUniqueWork(
"setRecoverySecretWork-test-user-id",
ExistingWorkPolicy.KEEP,
any<OneTimeWorkRequest>()
)
}
}
@Test
fun localObjectIsReturnedForCredentialLess() = runTest(dispatcherProvider.Main) {
// GIVEN

View File

@ -6,12 +6,6 @@ public final class me/proton/core/usersettings/domain/DeviceSettingsHandlerKt {
public static final fun onDeviceSettingsChanged (Lme/proton/core/usersettings/domain/DeviceSettingsHandler;Lkotlin/jvm/functions/Function2;)Lkotlinx/coroutines/Job;
}
public abstract interface class me/proton/core/usersettings/domain/IsDeviceRecoveryEnabled {
public abstract fun invoke (Lme/proton/core/domain/entity/UserId;)Z
public abstract fun isLocalEnabled ()Z
public abstract fun isRemoteEnabled (Lme/proton/core/domain/entity/UserId;)Z
}
public final class me/proton/core/usersettings/domain/LogTag {
public static final field DEFAULT Ljava/lang/String;
public static final field INSTANCE Lme/proton/core/usersettings/domain/LogTag;
@ -348,7 +342,6 @@ public abstract interface class me/proton/core/usersettings/domain/repository/Us
public abstract fun getUserSettings (Lme/proton/core/domain/entity/UserId;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun getUserSettingsFlow (Lme/proton/core/domain/entity/UserId;Z)Lkotlinx/coroutines/flow/Flow;
public abstract fun markAsStale (Lme/proton/core/domain/entity/UserId;)V
public abstract fun setRecoverySecret (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun setUsername (Lme/proton/core/domain/entity/UserId;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun updateCrashReports (Lme/proton/core/domain/entity/UserId;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
public abstract fun updateLoginPassword (Lme/proton/core/domain/entity/UserId;Lme/proton/core/crypto/common/srp/SrpProofs;Ljava/lang/String;Ljava/lang/String;Lme/proton/core/crypto/common/srp/Auth;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
@ -362,11 +355,6 @@ public final class me/proton/core/usersettings/domain/repository/UserSettingsRep
public static synthetic fun getUserSettingsFlow$default (Lme/proton/core/usersettings/domain/repository/UserSettingsRepository;Lme/proton/core/domain/entity/UserId;ZILjava/lang/Object;)Lkotlinx/coroutines/flow/Flow;
}
public final class me/proton/core/usersettings/domain/usecase/GenerateRecoverySecret {
public fun <init> (Lme/proton/core/user/domain/UserManager;Lme/proton/core/crypto/common/context/CryptoContext;)V
public final fun invoke (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class me/proton/core/usersettings/domain/usecase/GetOrganization {
public fun <init> (Lme/proton/core/usersettings/domain/repository/OrganizationRepository;)V
public final fun invoke (Lme/proton/core/domain/entity/UserId;ZLkotlin/coroutines/Continuation;)Ljava/lang/Object;
@ -421,11 +409,6 @@ public final class me/proton/core/usersettings/domain/usecase/PerformUpdateUserP
public static synthetic fun invoke$default (Lme/proton/core/usersettings/domain/usecase/PerformUpdateUserPassword;ZLme/proton/core/domain/entity/UserId;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Lkotlin/coroutines/Continuation;ILjava/lang/Object;)Ljava/lang/Object;
}
public final class me/proton/core/usersettings/domain/usecase/SetRecoverySecretRemote {
public fun <init> (Lme/proton/core/eventmanager/domain/EventManagerProvider;Lme/proton/core/usersettings/domain/usecase/GenerateRecoverySecret;Lme/proton/core/usersettings/domain/repository/UserSettingsRemoteDataSource;)V
public final fun invoke (Lme/proton/core/domain/entity/UserId;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;
}
public final class me/proton/core/usersettings/domain/usecase/SetupUsername {
public fun <init> (Lme/proton/core/user/domain/repository/UserRepository;Lme/proton/core/usersettings/domain/repository/UserSettingsRepository;)V
public final fun invoke (Lme/proton/core/domain/entity/UserId;Ljava/lang/String;Lkotlin/coroutines/Continuation;)Ljava/lang/Object;

View File

@ -20,6 +20,7 @@ import studio.forface.easygradle.dsl.*
plugins {
protonKotlinLibrary
kotlin("plugin.serialization")
}
protonBuild {
@ -27,8 +28,8 @@ protonBuild {
}
protonCoverage {
branchCoveragePercentage.set(38)
lineCoveragePercentage.set(71)
branchCoveragePercentage.set(39)
lineCoveragePercentage.set(72)
}
publishOption.shouldBePublishedAsLib = true
@ -47,6 +48,7 @@ dependencies {
implementation(
project(Module.kotlinUtil),
`serialization-json`
)
testImplementation(

View File

@ -30,13 +30,6 @@ interface UserSettingsRepository {
suspend fun setUsername(sessionUserId: SessionUserId, username: String): Boolean
/**
* Set the user primary key recovery secret, remotely, in background.
*
* Note: Once remotely set, local will be updated asap.
*/
suspend fun setRecoverySecret(userId: UserId)
/**
* Update [UserSettings], locally.
*

View File

@ -51,6 +51,7 @@ dependencies {
project(Module.domain),
project(Module.presentation),
project(Module.presentationCompose),
project(Module.userSettingsData),
project(Module.userSettingsDomain),
project(Module.accountManagerPresentation),
project(Module.accountRecoveryDomain),