fix(sync): запретил зашифрованные storage в группах и перевёл резолв storages на FindStorageUseCase

This commit is contained in:
2026-05-17 18:04:20 +03:00
parent e562e4d9e9
commit 8fd10084f7
7 changed files with 22 additions and 33 deletions

View File

@@ -91,9 +91,11 @@ class UseCasesModule {
fun provideStorageSyncEngine( fun provideStorageSyncEngine(
vaultsManager: IVaultsManager, vaultsManager: IVaultsManager,
groupStore: IStorageSyncGroupStore, groupStore: IStorageSyncGroupStore,
findStorageUseCase: FindStorageUseCase,
): IStorageSyncEngine = StorageSyncEngine( ): IStorageSyncEngine = StorageSyncEngine(
vaultsManager = vaultsManager, vaultsManager = vaultsManager,
groupStore = groupStore, groupStore = groupStore,
findStorageUseCase = findStorageUseCase,
) )
@Provides @Provides

View File

@@ -33,10 +33,7 @@ class UnlockManager(
override fun getOpenedStorageKey(uuid: UUID): EncryptKey? { override fun getOpenedStorageKey(uuid: UUID): EncryptKey? {
val opened = _openedStorages.value val opened = _openedStorages.value
val direct = opened[uuid] val direct = opened[uuid]
if (direct != null) { return direct?.getKey()
return direct.getKey()
}
return null
} }
init { init {

View File

@@ -15,7 +15,6 @@ import com.github.nullptroma.wallenc.usecases.ManageStorageSyncGroupsUseCase
import com.github.nullptroma.wallenc.usecases.RunStorageSyncUseCase import com.github.nullptroma.wallenc.usecases.RunStorageSyncUseCase
import com.github.nullptroma.wallenc.usecases.StorageSyncCompatibilityInput import com.github.nullptroma.wallenc.usecases.StorageSyncCompatibilityInput
import com.github.nullptroma.wallenc.usecases.isStorageCompatibleWithGroup import com.github.nullptroma.wallenc.usecases.isStorageCompatibleWithGroup
import com.github.nullptroma.wallenc.usecases.storageEncryptionSecret
import com.github.nullptroma.wallenc.vault.contract.DescribedVault import com.github.nullptroma.wallenc.vault.contract.DescribedVault
import com.github.nullptroma.wallenc.vault.contract.VaultDescriptor import com.github.nullptroma.wallenc.vault.contract.VaultDescriptor
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
@@ -154,15 +153,12 @@ class StorageSyncViewModel @Inject constructor(
return@withGroupMutationBusy UserNotification.TextRes(R.string.sync_storage_not_in_vaults) return@withGroupMutationBusy UserNotification.TextRes(R.string.sync_storage_not_in_vaults)
} }
val isEncrypted = storage.metaInfo.value.encInfo != null val isEncrypted = storage.metaInfo.value.encInfo != null
val secret = vaultsManager.unlockManager
.getOpenedStorageKey(storageUuid)
?.let(::storageEncryptionSecret)
val result = groupsUseCase.addStorageToGroup( val result = groupsUseCase.addStorageToGroup(
groupId = groupId, groupId = groupId,
storageUuid = storageUuid, storageUuid = storageUuid,
compatibility = StorageSyncCompatibilityInput( compatibility = StorageSyncCompatibilityInput(
isEncrypted = isEncrypted, isEncrypted = isEncrypted,
encryptionSecret = secret, encryptionSecret = null,
), ),
) )
when (result) { when (result) {
@@ -173,6 +169,9 @@ class StorageSyncViewModel @Inject constructor(
AddStorageToSyncGroupResult.AlreadyInGroup -> UserNotification.TextRes( AddStorageToSyncGroupResult.AlreadyInGroup -> UserNotification.TextRes(
R.string.sync_msg_storage_already_added, R.string.sync_msg_storage_already_added,
) )
AddStorageToSyncGroupResult.EncryptedStorageNotAllowed -> UserNotification.TextRes(
R.string.sync_msg_only_plain_storage_allowed,
)
AddStorageToSyncGroupResult.MissingEncryptionSecret -> UserNotification.TextRes( AddStorageToSyncGroupResult.MissingEncryptionSecret -> UserNotification.TextRes(
R.string.sync_msg_storage_encryption_key_required, R.string.sync_msg_storage_encryption_key_required,
) )

View File

@@ -50,6 +50,7 @@
<string name="sync_msg_storage_added">Хранилище добавлено в %1$s</string> <string name="sync_msg_storage_added">Хранилище добавлено в %1$s</string>
<string name="sync_msg_storage_removed">Хранилище убрано из %1$s</string> <string name="sync_msg_storage_removed">Хранилище убрано из %1$s</string>
<string name="sync_msg_storage_already_added">Хранилище уже добавлено в группу</string> <string name="sync_msg_storage_already_added">Хранилище уже добавлено в группу</string>
<string name="sync_msg_only_plain_storage_allowed">В группы синхронизации можно добавлять только незашифрованные хранилища</string>
<string name="sync_msg_storage_encryption_key_required">Для зашифрованного хранилища нужно знать пароль (откройте его перед добавлением)</string> <string name="sync_msg_storage_encryption_key_required">Для зашифрованного хранилища нужно знать пароль (откройте его перед добавлением)</string>
<string name="sync_msg_storage_incompatible_encryption">Хранилище не совместимо с политикой шифрования группы</string> <string name="sync_msg_storage_incompatible_encryption">Хранилище не совместимо с политикой шифрования группы</string>
<string name="sync_msg_virtual_storage_not_supported">Нельзя добавлять открытое виртуальное хранилище: синхронизация работает с исходными raw storage</string> <string name="sync_msg_virtual_storage_not_supported">Нельзя добавлять открытое виртуальное хранилище: синхронизация работает с исходными raw storage</string>

View File

@@ -14,6 +14,7 @@ sealed interface AddStorageToSyncGroupResult {
data object Added : AddStorageToSyncGroupResult data object Added : AddStorageToSyncGroupResult
data object GroupNotFound : AddStorageToSyncGroupResult data object GroupNotFound : AddStorageToSyncGroupResult
data object AlreadyInGroup : AddStorageToSyncGroupResult data object AlreadyInGroup : AddStorageToSyncGroupResult
data object EncryptedStorageNotAllowed : AddStorageToSyncGroupResult
data object IncompatibleEncryption : AddStorageToSyncGroupResult data object IncompatibleEncryption : AddStorageToSyncGroupResult
data object MissingEncryptionSecret : AddStorageToSyncGroupResult data object MissingEncryptionSecret : AddStorageToSyncGroupResult
} }
@@ -56,14 +57,12 @@ class ManageStorageSyncGroupsUseCase(
if (storageUuid in current.storageUuids) { if (storageUuid in current.storageUuids) {
return AddStorageToSyncGroupResult.AlreadyInGroup return AddStorageToSyncGroupResult.AlreadyInGroup
} }
if (compatibility.isEncrypted) {
val encryptedSecret = compatibility.encryptionSecret?.takeIf { it.isNotBlank() } return AddStorageToSyncGroupResult.EncryptedStorageNotAllowed
val effectiveEncryption = when {
!compatibility.isEncrypted -> StorageSyncGroupEncryptionKind.NONE to null
encryptedSecret == null -> return AddStorageToSyncGroupResult.MissingEncryptionSecret
else -> StorageSyncGroupEncryptionKind.PASSWORD to encryptedSecret
} }
val effectiveEncryption = StorageSyncGroupEncryptionKind.NONE to null
val (nextKind, nextSecret) = when (current.encryptionKind) { val (nextKind, nextSecret) = when (current.encryptionKind) {
StorageSyncGroupEncryptionKind.UNSET -> effectiveEncryption StorageSyncGroupEncryptionKind.UNSET -> effectiveEncryption
StorageSyncGroupEncryptionKind.NONE -> { StorageSyncGroupEncryptionKind.NONE -> {
@@ -74,14 +73,8 @@ class ManageStorageSyncGroupsUseCase(
} }
StorageSyncGroupEncryptionKind.PASSWORD -> { StorageSyncGroupEncryptionKind.PASSWORD -> {
if (
effectiveEncryption.first != StorageSyncGroupEncryptionKind.PASSWORD ||
current.encryptionSecret != effectiveEncryption.second
) {
return AddStorageToSyncGroupResult.IncompatibleEncryption return AddStorageToSyncGroupResult.IncompatibleEncryption
} }
StorageSyncGroupEncryptionKind.PASSWORD to current.encryptionSecret
}
} }
groupStore.putGroup( groupStore.putGroup(

View File

@@ -15,16 +15,13 @@ fun isStorageCompatibleWithGroup(
group: StorageSyncGroup, group: StorageSyncGroup,
resolveStorageKey: (UUID) -> EncryptKey?, resolveStorageKey: (UUID) -> EncryptKey?,
): Boolean { ): Boolean {
return when (group.encryptionKind) { // Режим упрощён: в sync-группах допускаются только незашифрованные storage.
StorageSyncGroupEncryptionKind.UNSET -> true if (storage.metaInfo.value.encInfo != null) {
StorageSyncGroupEncryptionKind.NONE -> storage.metaInfo.value.encInfo == null
StorageSyncGroupEncryptionKind.PASSWORD -> {
val groupSecret = group.encryptionSecret ?: return false
if (storage.metaInfo.value.encInfo == null) {
return false return false
} }
val currentSecret = resolveStorageKey(storage.uuid)?.let(::storageEncryptionSecret) return when (group.encryptionKind) {
currentSecret != null && currentSecret == groupSecret StorageSyncGroupEncryptionKind.UNSET -> true
} StorageSyncGroupEncryptionKind.NONE -> true
StorageSyncGroupEncryptionKind.PASSWORD -> false
} }
} }

View File

@@ -19,6 +19,7 @@ import java.util.concurrent.atomic.AtomicLong
class StorageSyncEngine( class StorageSyncEngine(
private val vaultsManager: IVaultsManager, private val vaultsManager: IVaultsManager,
private val groupStore: IStorageSyncGroupStore, private val groupStore: IStorageSyncGroupStore,
private val findStorageUseCase: FindStorageUseCase,
) : IStorageSyncEngine { ) : IStorageSyncEngine {
private val holderId: String = UUID.randomUUID().toString() private val holderId: String = UUID.randomUUID().toString()
private val groupMutexes = ConcurrentHashMap<String, Mutex>() private val groupMutexes = ConcurrentHashMap<String, Mutex>()
@@ -207,8 +208,7 @@ class StorageSyncEngine(
} }
private fun resolveStorages(uuids: Set<UUID>): List<IStorage> { private fun resolveStorages(uuids: Set<UUID>): List<IStorage> {
val byUuid = vaultsManager.allStorages.value.associateBy { it.uuid } return uuids.mapNotNull(findStorageUseCase::find)
return uuids.mapNotNull { byUuid[it] }
} }
private fun latestByPath(entries: List<StorageSyncJournalEntry>): Map<String, StorageSyncJournalEntry> { private fun latestByPath(entries: List<StorageSyncJournalEntry>): Map<String, StorageSyncJournalEntry> {