Черновик ПЗ
This commit is contained in:
BIN
Report/Другие отчёты Wallenc/puml/Domain_Диаграмма_классов.png
Normal file
BIN
Report/Другие отчёты Wallenc/puml/Domain_Диаграмма_классов.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 199 KiB |
BIN
Report/Другие отчёты Wallenc/puml/domain-classdiagram.png
Normal file
BIN
Report/Другие отчёты Wallenc/puml/domain-classdiagram.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
387
Report/Другие отчёты Wallenc/puml/domain-classdiagram.puml
Normal file
387
Report/Другие отчёты Wallenc/puml/domain-classdiagram.puml
Normal file
@@ -0,0 +1,387 @@
|
||||
@startuml
|
||||
scale 2
|
||||
skinparam shadowing false
|
||||
skinparam classFontSize 11
|
||||
|
||||
' PNG (лимит растра по умолчанию 4096): PLANTUML_LIMIT_SIZE=8192 java -Xmx2g -jar plantuml.jar -charset UTF-8 -tpng …
|
||||
|
||||
package usecases {
|
||||
class ManageStoragesEncryptionUseCase {
|
||||
+ enableEncryption(IStorageInfo, EncryptKey, boolean): Unit
|
||||
+ openStorage(IStorageInfo, EncryptKey, boolean): Unit
|
||||
+ clearAndDisableEncryption(IStorageInfo): Unit
|
||||
+ closeStorage(IStorageInfo): Unit
|
||||
+ canEncrypt(IStorageInfo): Unit
|
||||
+ changePassword(IStorageInfo, EncryptKey, boolean): Unit
|
||||
}
|
||||
class RemoveStorageUseCase {
|
||||
+ remove(IStorageInfo): Unit
|
||||
}
|
||||
class GetOpenedStoragesUseCase {
|
||||
+ getOpenedStorages(): StateFlow<Map<UUID, IStorageInfo>>
|
||||
}
|
||||
class ManageLocalVaultUseCase {
|
||||
+ getLocalStorages(): StateFlow<List<IStorageInfo>>
|
||||
+ createStorage(): Unit
|
||||
}
|
||||
class StorageFileManagementUseCase {
|
||||
+ getAllDirs(): Unit
|
||||
+ setStorage(IStorageInfo): void
|
||||
+ getAllFiles(): Unit
|
||||
}
|
||||
class RenameStorageUseCase {
|
||||
+ rename(IStorageInfo, String): Unit
|
||||
}
|
||||
}
|
||||
|
||||
package tasks {
|
||||
class TaskLogLine {
|
||||
+ getTimestampMs(): long
|
||||
+ getLevel(): TaskLogLevel
|
||||
+ getMessage(): String
|
||||
}
|
||||
class PipelineWork {
|
||||
+ run(TaskContext): Unit
|
||||
}
|
||||
class TaskContext {
|
||||
+ reportProgress(Float, String): Unit
|
||||
+ reportProgress(TaskProgress): Unit
|
||||
+ log(TaskLogLevel, String): void
|
||||
+ getTaskId(): TaskId
|
||||
}
|
||||
class PipelineState {
|
||||
+ getTasks(): List
|
||||
+ getRunningTaskIds(): Set
|
||||
}
|
||||
class ITaskOrchestrator {
|
||||
+ getLogLines(): StateFlow<List<TaskLogLine>>
|
||||
+ enqueue(String, PipelineWork): TaskId
|
||||
+ getPipelineState(): StateFlow<PipelineState>
|
||||
+ getForegroundUi(): StateFlow<TaskForegroundUiState>
|
||||
+ cancel(TaskId): boolean
|
||||
+ cancelAll(): void
|
||||
}
|
||||
class TaskProgress {
|
||||
+ getLabel(): String
|
||||
+ getFraction(): Float
|
||||
}
|
||||
class TaskForegroundUiState {
|
||||
}
|
||||
class TaskForegroundItem {
|
||||
+ getProgress(): TaskProgress
|
||||
+ getTitle(): String
|
||||
+ getTaskId(): TaskId
|
||||
}
|
||||
class TaskLogLevel {
|
||||
+ Debug
|
||||
+ Warn
|
||||
+ Error
|
||||
+ Info
|
||||
+ values(): TaskLogLevel[]
|
||||
+ valueOf(String): TaskLogLevel
|
||||
+ getEntries(): EnumEntries<TaskLogLevel>
|
||||
}
|
||||
class TaskRunState {
|
||||
}
|
||||
class TaskId {
|
||||
+ getUuid(): UUID
|
||||
}
|
||||
class PipelineTask {
|
||||
+ getId(): TaskId
|
||||
+ getDispatcher(): CoroutineDispatcher
|
||||
+ getState(): TaskRunState
|
||||
+ getTitle(): String
|
||||
}
|
||||
}
|
||||
|
||||
package interfaces {
|
||||
class ILogger {
|
||||
+ debug(String, String): void
|
||||
}
|
||||
class IYandexVault {
|
||||
+ getAccountEmail(): String
|
||||
}
|
||||
class IMetaInfo {
|
||||
+ getSize(): long
|
||||
+ isDeleted(): boolean
|
||||
+ isHidden(): boolean
|
||||
+ getLastModified(): Instant
|
||||
+ getPath(): String
|
||||
}
|
||||
class IStorage {
|
||||
+ rename(String): Unit
|
||||
+ setEncInfo(StorageEncryptionInfo): Unit
|
||||
+ isEmpty(): Flow<Boolean>
|
||||
+ getUuid(): UUID
|
||||
+ getAccessor(): IStorageAccessor
|
||||
+ isAvailable(): StateFlow<Boolean>
|
||||
+ getSize(): StateFlow<Long>
|
||||
+ getNumberOfFiles(): StateFlow<Integer>
|
||||
+ clearAllContent(): Unit
|
||||
+ getMetaInfo(): StateFlow<IStorageMetaInfo>
|
||||
+ isVirtualStorage(): boolean
|
||||
}
|
||||
class IVaultsManager {
|
||||
+ getLocalVault(): IVault
|
||||
+ removeRemoteVault(UUID): Unit
|
||||
+ addYandexVault(String): Unit
|
||||
+ getRemoteVaults(): StateFlow<List<IVault>>
|
||||
+ getAllStorages(): StateFlow<List<IStorage>>
|
||||
+ getAllVaults(): StateFlow<List<IVault>>
|
||||
+ getUnlockManager(): IUnlockManager
|
||||
}
|
||||
class IStorageMetaInfo {
|
||||
+ getEncInfo(): StorageEncryptionInfo
|
||||
+ getName(): String
|
||||
+ getLastModified(): Instant
|
||||
}
|
||||
class IFile {
|
||||
+ getMetaInfo(): IMetaInfo
|
||||
}
|
||||
class IStorageInfo {
|
||||
+ isEmpty(): Flow<Boolean>
|
||||
+ getUuid(): UUID
|
||||
+ isAvailable(): StateFlow<Boolean>
|
||||
+ getSize(): StateFlow<Long>
|
||||
+ getNumberOfFiles(): StateFlow<Integer>
|
||||
+ getMetaInfo(): StateFlow<IStorageMetaInfo>
|
||||
+ isVirtualStorage(): boolean
|
||||
}
|
||||
class IStorageExplorer {
|
||||
+ getCurrentPath(): StateFlow<String>
|
||||
}
|
||||
class IVaultInfo {
|
||||
+ getAvailableSpace(): StateFlow<Integer>
|
||||
+ getType(): VaultType
|
||||
+ getTotalSpace(): StateFlow<Integer>
|
||||
+ getUuid(): UUID
|
||||
+ isAvailable(): StateFlow<Boolean>
|
||||
+ getStorages(): StateFlow<List<IStorageInfo>>
|
||||
}
|
||||
class IUnlockManager {
|
||||
+ close(IStorage): Unit
|
||||
+ close(UUID): Unit
|
||||
+ open(IStorage, EncryptKey, boolean): Unit
|
||||
+ getOpenedStorages(): StateFlow<Map<UUID, IStorage>>
|
||||
}
|
||||
class IDirectory {
|
||||
+ getMetaInfo(): IMetaInfo
|
||||
+ getElementsCount(): Integer
|
||||
}
|
||||
class IStorageAccessor {
|
||||
+ getFilesFlow(String): Flow<DataPackage<List<IFile>>>
|
||||
+ getDirs(String): Unit
|
||||
+ getDirsUpdates(): SharedFlow<DataPackage<List<IDirectory>>>
|
||||
+ touchDir(String): Unit
|
||||
+ getFiles(String): Unit
|
||||
+ getSize(): StateFlow<Long>
|
||||
+ isAvailable(): StateFlow<Boolean>
|
||||
+ getDirInfo(String): Unit
|
||||
+ openRead(String): Unit
|
||||
+ moveToTrash(String): Unit
|
||||
+ delete(String): Unit
|
||||
+ touchFile(String): Unit
|
||||
+ getAllDirs(): Unit
|
||||
+ openWrite(String): Unit
|
||||
+ getAllFiles(): Unit
|
||||
+ getDirsFlow(String): Flow<DataPackage<List<IDirectory>>>
|
||||
+ getNumberOfFiles(): StateFlow<Integer>
|
||||
+ getFilesUpdates(): SharedFlow<DataPackage<List<IFile>>>
|
||||
+ setHidden(String, boolean): Unit
|
||||
+ getFileInfo(String): Unit
|
||||
}
|
||||
class IVault {
|
||||
+ getAvailableSpace(): StateFlow<Integer>
|
||||
+ getType(): VaultType
|
||||
+ getTotalSpace(): StateFlow<Integer>
|
||||
+ remove(IStorage): Unit
|
||||
+ createStorage(StorageEncryptionInfo): Unit
|
||||
+ getUuid(): UUID
|
||||
+ isAvailable(): StateFlow<Boolean>
|
||||
+ getStorages(): StateFlow<List<IStorage>>
|
||||
+ createStorage(): Unit
|
||||
}
|
||||
}
|
||||
|
||||
package encrypt {
|
||||
class EncryptorWithStaticIv {
|
||||
+ decryptBytesbyte[](byte[])
|
||||
+ encryptStream(java.io.OutputStream): java.io.OutputStream
|
||||
+ decryptStream(java.io.InputStream): java.io.InputStream
|
||||
+ encryptBytesbyte[](byte[])
|
||||
+ dispose(): void
|
||||
+ encryptString(String): String
|
||||
+ decryptString(String): String
|
||||
}
|
||||
class Encryptor {
|
||||
+ AES_SETTINGS: String
|
||||
+ IV_LEN: int
|
||||
+ decryptBytesbyte[](byte[])
|
||||
+ encryptStream(java.io.OutputStream): java.io.OutputStream
|
||||
+ decryptStream(java.io.InputStream): java.io.InputStream
|
||||
+ encryptBytesbyte[](byte[])
|
||||
+ dispose(): void
|
||||
+ encryptString(String): String
|
||||
+ decryptString(String): String
|
||||
}
|
||||
}
|
||||
|
||||
package datatypes {
|
||||
class Tree {
|
||||
+ getValue(): Unit
|
||||
+ getChildren(): List
|
||||
+ setChildren(List): void
|
||||
}
|
||||
class EncryptKey {
|
||||
+ toAesKey(): SecretKeySpec
|
||||
+ getBytes(): byte[]
|
||||
}
|
||||
class DataPackage {
|
||||
+ getData(): Unit
|
||||
+ isError(): Boolean
|
||||
+ isLoading(): Boolean
|
||||
}
|
||||
class DataPage {
|
||||
+ getPageLength(): int
|
||||
+ getPageIndex(): int
|
||||
+ getHasNext(): Boolean
|
||||
}
|
||||
class StorageEncryptionInfo {
|
||||
+ getEncryptedTestData(): String
|
||||
+ getPathIv(): byte[]
|
||||
}
|
||||
}
|
||||
|
||||
package common.impl {
|
||||
class CommonDirectory {
|
||||
+ getElementsCount(): Integer
|
||||
+ getMetaInfo(): CommonMetaInfo
|
||||
}
|
||||
class CommonStorageMetaInfo {
|
||||
+ getEncInfo(): StorageEncryptionInfo
|
||||
+ getName(): String
|
||||
+ getLastModified(): Instant
|
||||
}
|
||||
class CommonMetaInfo {
|
||||
+ getSize(): long
|
||||
+ getPath(): String
|
||||
+ isDeleted(): boolean
|
||||
+ isHidden(): boolean
|
||||
+ getLastModified(): Instant
|
||||
}
|
||||
class CommonFile {
|
||||
+ getMetaInfo(): IMetaInfo
|
||||
}
|
||||
}
|
||||
|
||||
package auth {
|
||||
class RemoteYandexAuthResult {
|
||||
}
|
||||
class RemoteYandexSignInLauncher {
|
||||
+ launch(): void
|
||||
}
|
||||
}
|
||||
|
||||
package enums {
|
||||
class VaultType {
|
||||
+ DECRYPTED: VaultType
|
||||
+ LOCAL: VaultType
|
||||
+ YANDEX: VaultType
|
||||
+ valueOf(String): VaultType
|
||||
+ values(): VaultType[]
|
||||
+ getEntries(): EnumEntries<VaultType>
|
||||
}
|
||||
}
|
||||
|
||||
usecases.ManageStoragesEncryptionUseCase ..> interfaces.IStorageMetaInfo
|
||||
usecases.ManageStoragesEncryptionUseCase ..> interfaces.IStorageInfo
|
||||
usecases.ManageStoragesEncryptionUseCase ..> tasks.TaskProgress
|
||||
usecases.ManageStoragesEncryptionUseCase ..> datatypes.EncryptKey
|
||||
usecases.ManageStoragesEncryptionUseCase ..> encrypt.Encryptor
|
||||
usecases.ManageStoragesEncryptionUseCase ..> interfaces.IStorage
|
||||
usecases.ManageStoragesEncryptionUseCase ..> interfaces.IUnlockManager
|
||||
usecases.ManageStoragesEncryptionUseCase ..> datatypes.StorageEncryptionInfo
|
||||
usecases.RemoveStorageUseCase ..> usecases.ManageStoragesEncryptionUseCase
|
||||
usecases.RemoveStorageUseCase ..> interfaces.IStorage
|
||||
usecases.RemoveStorageUseCase ..> interfaces.IStorageInfo
|
||||
usecases.RemoveStorageUseCase ..> interfaces.IVaultsManager
|
||||
usecases.RemoveStorageUseCase ..> interfaces.IUnlockManager
|
||||
usecases.RemoveStorageUseCase ..> interfaces.IVault
|
||||
tasks.TaskLogLine ..> tasks.TaskLogLevel
|
||||
tasks.PipelineWork ..> tasks.TaskContext
|
||||
interfaces.IVault <|.. interfaces.IYandexVault
|
||||
interfaces.IVaultInfo <|.. interfaces.IYandexVault
|
||||
interfaces.IYandexVault ..> interfaces.IStorage
|
||||
interfaces.IYandexVault ..> interfaces.IVault
|
||||
interfaces.IYandexVault ..> enums.VaultType
|
||||
interfaces.IYandexVault ..> datatypes.StorageEncryptionInfo
|
||||
usecases.GetOpenedStoragesUseCase ..> interfaces.IStorageInfo
|
||||
usecases.GetOpenedStoragesUseCase ..> interfaces.IUnlockManager
|
||||
tasks.TaskContext ..> tasks.TaskLogLevel
|
||||
tasks.TaskContext ..> tasks.TaskProgress
|
||||
tasks.TaskContext ..> tasks.TaskId
|
||||
usecases.ManageLocalVaultUseCase ..> interfaces.IStorageInfo
|
||||
usecases.ManageLocalVaultUseCase ..> interfaces.IVaultsManager
|
||||
usecases.ManageLocalVaultUseCase ..> interfaces.IVault
|
||||
usecases.StorageFileManagementUseCase ..> interfaces.IFile
|
||||
usecases.StorageFileManagementUseCase ..> interfaces.IStorage
|
||||
usecases.StorageFileManagementUseCase ..> interfaces.IStorageInfo
|
||||
usecases.StorageFileManagementUseCase ..> interfaces.IDirectory
|
||||
usecases.StorageFileManagementUseCase ..> interfaces.IStorageAccessor
|
||||
interfaces.IStorageInfo <|.. interfaces.IStorage
|
||||
interfaces.IStorage ..> interfaces.IStorageMetaInfo
|
||||
interfaces.IStorage ..> interfaces.IStorageInfo
|
||||
interfaces.IStorage ..> tasks.TaskProgress
|
||||
interfaces.IStorage ..> interfaces.IStorageAccessor
|
||||
interfaces.IStorage ..> datatypes.StorageEncryptionInfo
|
||||
interfaces.IVaultsManager ..> interfaces.IStorage
|
||||
interfaces.IVaultsManager ..> interfaces.IUnlockManager
|
||||
interfaces.IVaultsManager ..> interfaces.IVault
|
||||
interfaces.IDirectory <|.. common.impl.CommonDirectory
|
||||
common.impl.CommonDirectory ..> common.impl.CommonMetaInfo
|
||||
common.impl.CommonDirectory ..> interfaces.IMetaInfo
|
||||
common.impl.CommonDirectory ..> interfaces.IDirectory
|
||||
tasks.PipelineState ..> tasks.TaskId
|
||||
tasks.PipelineState ..> tasks.PipelineTask
|
||||
interfaces.IStorageMetaInfo <|.. common.impl.CommonStorageMetaInfo
|
||||
common.impl.CommonStorageMetaInfo ..> interfaces.IStorageMetaInfo
|
||||
common.impl.CommonStorageMetaInfo ..> datatypes.StorageEncryptionInfo
|
||||
interfaces.IMetaInfo <|.. common.impl.CommonMetaInfo
|
||||
common.impl.CommonMetaInfo ..> interfaces.IMetaInfo
|
||||
interfaces.IStorageMetaInfo ..> datatypes.StorageEncryptionInfo
|
||||
interfaces.IFile ..> interfaces.IMetaInfo
|
||||
tasks.ITaskOrchestrator ..> tasks.TaskLogLine
|
||||
tasks.ITaskOrchestrator ..> tasks.PipelineState
|
||||
tasks.ITaskOrchestrator ..> tasks.TaskForegroundUiState
|
||||
tasks.ITaskOrchestrator ..> tasks.PipelineWork
|
||||
tasks.ITaskOrchestrator ..> tasks.TaskId
|
||||
interfaces.IStorageInfo ..> interfaces.IStorageMetaInfo
|
||||
interfaces.IStorageInfo ..> interfaces.IStorage
|
||||
usecases.RenameStorageUseCase ..> interfaces.IStorage
|
||||
usecases.RenameStorageUseCase ..> interfaces.IStorageInfo
|
||||
interfaces.IVaultInfo ..> interfaces.IStorageInfo
|
||||
interfaces.IVaultInfo ..> interfaces.IVault
|
||||
interfaces.IVaultInfo ..> enums.VaultType
|
||||
tasks.TaskForegroundItem ..> tasks.TaskProgress
|
||||
tasks.TaskForegroundItem ..> tasks.TaskId
|
||||
interfaces.IUnlockManager ..> interfaces.IStorage
|
||||
interfaces.IUnlockManager ..> datatypes.EncryptKey
|
||||
interfaces.IDirectory ..> interfaces.IMetaInfo
|
||||
interfaces.IStorageAccessor ..> interfaces.IFile
|
||||
interfaces.IStorageAccessor ..> interfaces.IDirectory
|
||||
interfaces.IStorageAccessor ..> datatypes.DataPackage
|
||||
interfaces.IVaultInfo <|.. interfaces.IVault
|
||||
interfaces.IVault ..> interfaces.IStorage
|
||||
interfaces.IVault ..> interfaces.IVaultInfo
|
||||
interfaces.IVault ..> enums.VaultType
|
||||
interfaces.IVault ..> datatypes.StorageEncryptionInfo
|
||||
auth.RemoteYandexSignInLauncher ..> auth.RemoteYandexAuthResult
|
||||
datatypes.DataPackage <|-- datatypes.DataPage
|
||||
datatypes.DataPage ..> datatypes.DataPackage
|
||||
interfaces.IFile <|.. common.impl.CommonFile
|
||||
common.impl.CommonFile ..> interfaces.IMetaInfo
|
||||
common.impl.CommonFile ..> interfaces.IFile
|
||||
tasks.PipelineTask ..> tasks.TaskRunState
|
||||
tasks.PipelineTask ..> tasks.TaskId
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,80 @@
|
||||
@startuml wallenc_01_start_and_sync
|
||||
' Увеличенный растр для вставки в отчёт (Word / печать)
|
||||
scale 3
|
||||
title
|
||||
Wallenc — старт приложения и параллельная синхронизация
|
||||
(проектное решение; фоновая синхронизация к реализации)
|
||||
end title
|
||||
|
||||
skinparam defaultFontName "DejaVu Sans"
|
||||
skinparam activity {
|
||||
BackgroundColor #F8F8F8
|
||||
BorderColor #333333
|
||||
DiamondBackgroundColor #E8F4FF
|
||||
}
|
||||
skinparam noteBackgroundColor #FFFDE7
|
||||
skinparam noteBorderColor #F9A825
|
||||
|
||||
start
|
||||
|
||||
:Старт приложения (Android);
|
||||
|
||||
:Инициализация Room,
|
||||
загрузка метаданных vault;
|
||||
|
||||
if (Есть сохранённые vault?) then (нет)
|
||||
:Экран «первый запуск» /
|
||||
создание локального vault;
|
||||
else (да)
|
||||
endif
|
||||
|
||||
if (Нужен удалённый провайдер
|
||||
и нет учётной записи?) then (да)
|
||||
:Экран удалённых vault /
|
||||
OAuth Яндекс;
|
||||
else (нет)
|
||||
endif
|
||||
|
||||
partition "**Основной поток (UI)**" {
|
||||
:(A) Главный экран:
|
||||
список локальных и удалённых vault;
|
||||
:Действия пользователя
|
||||
(открыть, зашифровать, содержимое…);
|
||||
}
|
||||
|
||||
fork
|
||||
partition "**Фон: синхронизация (по таймеру)**" #E8F5E9 {
|
||||
note right
|
||||
**Проектная механика (не реализовано в коде)**
|
||||
• Таймер / WorkManager Android
|
||||
• Таблица в Room: UUID **storage_id**
|
||||
для очереди синхронизации
|
||||
• Для каждого storage — **история коммитов**
|
||||
(аналог git): дерево/цепочка снимков
|
||||
• Сервис: сравнение коммитов
|
||||
локально vs удалённо → вычисление diff
|
||||
• Применение изменений →
|
||||
**одинаковое зашифрованное содержимое**
|
||||
на клиенте и у провайдера
|
||||
(ключи на сервер не передаются)
|
||||
end note
|
||||
:По срабатыванию таймера:
|
||||
запуск **SyncService** / Worker;
|
||||
:Чтение из БД списка
|
||||
**UUID storage** из очереди;
|
||||
while (Есть необработанный UUID?) is (да)
|
||||
:Загрузить историю **коммитов**
|
||||
для этого Storage (локально + у провайдера);
|
||||
:Найти расхождения
|
||||
(common ancestor / merge);
|
||||
:Свести содержимое
|
||||
к единому состоянию;
|
||||
:Обновить очередь /
|
||||
метаданные синхронизации;
|
||||
endwhile (нет)
|
||||
}
|
||||
end fork
|
||||
|
||||
stop
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,57 @@
|
||||
@startuml wallenc_02_vault_lifecycle
|
||||
scale 3
|
||||
title
|
||||
Wallenc — пользовательский поток: жизненный цикл vault
|
||||
и точки постановки в очередь синхронизации (проект)
|
||||
end title
|
||||
|
||||
skinparam defaultFontName "DejaVu Sans"
|
||||
skinparam state {
|
||||
BackgroundColor #F8F8F8
|
||||
BorderColor #333333
|
||||
}
|
||||
skinparam noteBackgroundColor #E3F2FD
|
||||
skinparam noteBorderColor #1565C0
|
||||
|
||||
state "(Б) Список vault" as List
|
||||
|
||||
List --> Create : Создать vault
|
||||
Create --> List : Vault создан
|
||||
|
||||
List --> EncryptDlg : Включить шифрование
|
||||
EncryptDlg --> Encrypting : Подтверждение, мастер-ключ
|
||||
state Encrypting {
|
||||
state "Шифрование данных + запись метаданных" as EncWork
|
||||
}
|
||||
Encrypting --> List : Готово
|
||||
|
||||
note right of Encrypting
|
||||
После успешной записи **коммита**
|
||||
в историю Storage (проект):
|
||||
UUID storage → **очередь синхронизации**
|
||||
в Room (для таймера)
|
||||
end note
|
||||
|
||||
List --> OpenDlg : Открыть зашифрованный
|
||||
OpenDlg --> Opened : Ключ верный
|
||||
OpenDlg --> List : Отмена / неверный ключ
|
||||
|
||||
state Opened {
|
||||
state "Просмотр / работа с содержимым" as Browse
|
||||
}
|
||||
Opened --> List : Закрыть vault / блокировка
|
||||
|
||||
List --> RenameDel : Переименовать / удалить
|
||||
RenameDel --> List : Подтверждение
|
||||
|
||||
note bottom of List
|
||||
**Синхронизация (проект):** любое изменение,
|
||||
порождающее новый **коммит** в Storage,
|
||||
добавляет storage UUID в таблицу очереди;
|
||||
**SyncService** по таймеру обрабатывает очередь,
|
||||
сравнивает истории коммитов с удалённой копией
|
||||
и приводит зашифрованное содержимое
|
||||
к одному состоянию (без передачи ключей).
|
||||
end note
|
||||
|
||||
@enduml
|
||||
@@ -0,0 +1,51 @@
|
||||
@startuml wallenc_03_navigation_hub
|
||||
scale 3
|
||||
title
|
||||
Wallenc — навигация от главного экрана
|
||||
и связь с фоновой синхронизацией (проект)
|
||||
end title
|
||||
|
||||
skinparam defaultFontName "DejaVu Sans"
|
||||
skinparam activityBackgroundColor #F8F8F8
|
||||
skinparam activityBorderColor #333333
|
||||
skinparam noteBackgroundColor #FCE4EC
|
||||
skinparam noteBorderColor #C2185B
|
||||
|
||||
start
|
||||
|
||||
:(A) Главный экран:
|
||||
список локальных vault;
|
||||
|
||||
repeat
|
||||
:Ожидание действия пользователя;
|
||||
backward:Назад с подэкрана;
|
||||
switch (Действие?)
|
||||
case (FAB / новый vault)
|
||||
:Создание локального vault;
|
||||
case (Выбор vault)
|
||||
:Карточка / детали vault;
|
||||
case (Удалённые vault)
|
||||
:Экран удалённых vault;
|
||||
if (Нужен OAuth Яндекс?) then (да)
|
||||
:Авторизация Яндекс;
|
||||
endif
|
||||
case (Настройки)
|
||||
:Экран настроек;
|
||||
endswitch
|
||||
repeat while (Пользователь в приложении?) is (да)
|
||||
-> нет;
|
||||
|
||||
stop
|
||||
|
||||
floating note right
|
||||
**Фон: SyncWorker (по таймеру Android) — проект**
|
||||
• Room: таблица очереди с **UUID storage**
|
||||
• Периодический запуск метода синхронизации
|
||||
• Для каждого Storage — история **коммитов** (как git)
|
||||
• Сравнение локальной и удалённой истории,
|
||||
приведение зашифрованного содержимого
|
||||
к одному состоянию (ключи на сервер не уходят)
|
||||
• Работает **независимо** от текущего экрана UI
|
||||
end note
|
||||
|
||||
@enduml
|
||||
BIN
Report/Другие отчёты Wallenc/puml/wallenc_01_start_and_sync.png
Normal file
BIN
Report/Другие отчёты Wallenc/puml/wallenc_01_start_and_sync.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 255 KiB |
BIN
Report/Другие отчёты Wallenc/puml/wallenc_02_vault_lifecycle.png
Normal file
BIN
Report/Другие отчёты Wallenc/puml/wallenc_02_vault_lifecycle.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 184 KiB |
BIN
Report/Другие отчёты Wallenc/puml/wallenc_03_navigation_hub.png
Normal file
BIN
Report/Другие отчёты Wallenc/puml/wallenc_03_navigation_hub.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 148 KiB |
122
Report/Другие отчёты Wallenc/Отчёт 1 этап.md
Normal file
122
Report/Другие отчёты Wallenc/Отчёт 1 этап.md
Normal file
@@ -0,0 +1,122 @@
|
||||
# Отчёт по 1-му предварительному этапу производственной практики
|
||||
**Проект:** Wallenc — универсальный кошелёк для безопасного хранения данных на небезопасных хранилищах без собственного сервера.
|
||||
|
||||
## Введение
|
||||
|
||||
На первом этапе практики была выполнена аналитическая и проектная подготовка к разработке мобильного приложения Wallenc. Цель этапа — сформировать техническую основу проекта: определить предметную область, выбрать архитектурный подход, описать требования к системе и зафиксировать технологические решения для последующей реализации.
|
||||
|
||||
Ключевая идея проекта заключается в том, что безопасность пользовательских данных обеспечивается на стороне клиента: данные шифруются до отправки во внешнее хранилище, а расшифрование выполняется только в приложении при наличии корректного ключа. Это позволяет использовать небезопасные или недоверенные хранилища без потери конфиденциальности и без развертывания собственного серверного backend.
|
||||
|
||||
## Анализ предметной области
|
||||
|
||||
В рамках анализа предметной области рассмотрены подходы к хранению чувствительных данных в мобильных приложениях и в облачных хранилищах. Выделены основные требования к таким системам:
|
||||
|
||||
- конфиденциальность данных при хранении и передаче;
|
||||
- отсутствие необходимости доверять инфраструктуре хранилища;
|
||||
- устойчивость к компрометации удалённого провайдера;
|
||||
- разделение логики хранения и логики криптографической защиты;
|
||||
- удобный пользовательский сценарий: создание хранилища, шифрование, открытие, работа с содержимым.
|
||||
|
||||
Сформирован вывод, что для проекта Wallenc приоритетны клиентские криптографические механизмы, унифицированный доступ к разным типам хранилищ и архитектура с чётким разделением слоёв.
|
||||
|
||||
## Обзор аналогичных решений
|
||||
|
||||
### Аналоги
|
||||
|
||||
- **Google Files Secure Folder**
|
||||
Что делает: локально прячет и защищает файлы в папке по PIN/Pattern внутри Android.
|
||||
Минусы: по сути только локальный “сейф” (без нормальной кроссплатформенной синхронизации), ограниченные сценарии переноса между устройствами, не про универсальную модель vault для разных хранилищ.
|
||||
|
||||
- **Proton Pass / Proton Drive**
|
||||
Что делает: end-to-end экосистема для паролей, заметок и файлов с облачной синхронизацией.
|
||||
Минусы: привязка к экосистеме Proton, часть полезных сценариев/объёма и функций зависит от тарифа, меньше гибкости как “универсальный клиент” под разные внешние хранилища.
|
||||
|
||||
- **Bitwarden**
|
||||
Что делает: менеджер паролей/секретов с шифрованием, синхронизацией, офлайн-доступом и self-host вариантом.
|
||||
Минусы: ориентирован в первую очередь на учетные данные и секреты, а не на общий файловый vault; офлайн-сценарии ограничены (часть операций требует синхронизации/сервера).
|
||||
|
||||
- **Cryptomator**
|
||||
Что делает: клиентское шифрование файловых vault перед хранением в облаке (zero-knowledge подход).
|
||||
Минусы: фокус именно на шифровании файлов, а не на расширенной модели “кошелька” с собственной мета-логикой и UI-сценариями; есть ограничения интеграций в зависимости от платформы/сборки и провайдера.
|
||||
|
||||
Проведён обзор классов решений (secure-folder приложения, менеджеры секретов, облачные клиенты с zero-knowledge подходом). По результатам обзора отмечены типовые сильные и слабые стороны:
|
||||
|
||||
**Преимущества аналогов:**
|
||||
|
||||
- понятный пользовательский сценарий хранения;
|
||||
- готовые механизмы шифрования файлов;
|
||||
- интеграция с несколькими хранилищами.
|
||||
|
||||
|
||||
**Ограничения аналогов:**
|
||||
|
||||
- зависимость от собственного backend или закрытой инфраструктуры;
|
||||
- недостаточная прозрачность модели ключей;
|
||||
- ограниченная переносимость между провайдерами.
|
||||
|
||||
|
||||
Это подтвердило актуальность выбранной концепции Wallenc: безопасность без собственного сервера, с переносимой архитектурой хранилищ.
|
||||
|
||||
## Формирование технического задания
|
||||
|
||||
На основании анализа подготовлена структура ТЗ в логике ГОСТ 7.32–2017: определены цели, этапы работ, основные и дополнительные задачи, ожидаемые результаты и направления тестирования.
|
||||
ТЗ синхронизировано с фактическим направлением проекта: приоритет на ядро системы хранения/шифрования и поэтапное расширение функциональности.
|
||||
|
||||
## Изучение архитектурных подходов
|
||||
|
||||
Для проекта принят архитектурный подход **MVVM + Clean Architecture**:
|
||||
|
||||
- **Domain**: интерфейсы и use-case логика (операции над vault и шифрованием);
|
||||
- **Data**: реализации хранилищ, слой доступа к данным, локальная БД, криптографические адаптеры;
|
||||
- **Presentation**: UI-слой, навигация, состояния экранов, ViewModel.
|
||||
|
||||
Такое разделение снижает связность, упрощает тестирование и позволяет независимо развивать локальные и удалённые провайдеры.
|
||||
|
||||
## Проектирование структуры системы
|
||||
|
||||
Спроектирована клиентская система, в которой:
|
||||
|
||||
- локальный vault является базовой реализацией хранения;
|
||||
- зашифрованное представление открывается через отдельный менеджер;
|
||||
- операции чтения/записи выполняются через абстракции доступа к файлам;
|
||||
- служебные данные (метаданные, связи ключей и хранилищ) отделены от пользовательского содержимого.
|
||||
|
||||
Подход ориентирован на дальнейшее подключение удалённых провайдеров (например, облачных API) без изменения доменной модели.
|
||||
|
||||
## Проектирование структуры базы данных
|
||||
|
||||
Определена структура локальной БД для служебной информации приложения:
|
||||
|
||||
- хранение соответствий между исходным хранилищем и его зашифрованным представлением;
|
||||
- хранение метаданных хранилищ;
|
||||
- поддержка восстановления состояния при запуске приложения.
|
||||
|
||||
БД используется как внутренний механизм управления состоянием vault и не хранит пользовательские данные в открытом виде.
|
||||
|
||||
## Выбор технологий
|
||||
|
||||
Для реализации первого этапа и последующей разработки определён следующий стек:
|
||||
|
||||
- **Kotlin**, **Android SDK**;
|
||||
- **Jetpack Compose** (UI);
|
||||
- **Coroutines/Flow** (асинхронность и реактивные потоки);
|
||||
- **Hilt** (dependency injection);
|
||||
- **Room** (локальная БД);
|
||||
- криптографические механизмы на стороне клиента (AES-шифрование данных и служебных атрибутов);
|
||||
- модульная структура проекта (`app`, `presentation`, `domain`, `data`).
|
||||
|
||||
Выбранный стек соответствует целям проекта и обеспечивает масштабируемость.
|
||||
|
||||
## Дополнительные исследования этапа
|
||||
|
||||
На первом этапе также проработаны дополнительные направления:
|
||||
|
||||
- варианты OAuth-аутентификации при работе с удалёнными провайдерами без собственного сервера;
|
||||
- подходы к безопасной работе с ключами шифрования и проверке корректности ключа;
|
||||
- защита структуры данных (скрытие служебных файлов/директорий, минимизация утечек через имена и пути);
|
||||
- предварительный план тестирования (unit, интеграционные и UI-сценарии).
|
||||
|
||||
## Итоги первого этапа
|
||||
|
||||
По результатам первого этапа сформирована целостная проектная база для разработки Wallenc: определены предметная область, архитектурная модель, структура данных, технологический стек и требования к безопасности.
|
||||
Подготовленные материалы позволяют переходить к реализации следующего этапа — построению функционального ядра приложения и расширению сценариев работы с хранилищами, включая синхронизацию зашифрованных данных с удалёнными провайдерами.
|
||||
229
Report/Другие отчёты Wallenc/Отчёт 2 этап.md
Normal file
229
Report/Другие отчёты Wallenc/Отчёт 2 этап.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# Отчёт по 2-му предварительному этапу производственной практики
|
||||
**Проект:** Wallenc — универсальный кошелёк для безопасного хранения данных на небезопасных хранилищах без собственного сервера.
|
||||
**Период выполнения этапа:** 29.03.2026–19.04.2026
|
||||
|
||||
## Введение
|
||||
|
||||
На втором этапе производственной практики выполнена реализация функционального ядра мобильного приложения Wallenc и ключевых пользовательских сценариев работы с локальными и удалёнными хранилищами.
|
||||
Основной фокус этапа — перевод проектных решений первого этапа в рабочий код: управление vault, шифрование, хранение метаданных и ключевой информации, а также реализация интерфейсов для повседневной работы пользователя.
|
||||
|
||||
В рамках этапа обеспечена практическая готовность базовой версии приложения к использованию: реализованы операции создания, просмотра, переименования, удаления и защиты vault, а также подготовлена инфраструктура для дальнейшего расширения удалённых сценариев.
|
||||
|
||||
Практический результат этапа выражается не только в наличии отдельных модулей, но и в их совместной работе в рамках одного приложения: пользователь может создавать хранилища, включать защиту, открывать и закрывать зашифрованные представления, видеть состояние vault в интерфейсе и выполнять основные операции сопровождения. Все ключевые сценарии выполнены в рамках единой архитектуры, выбранной на первом этапе.
|
||||
|
||||
## Краткая характеристика выполненного этапа
|
||||
|
||||
За период 29.03.2026–19.04.2026 реализован целостный набор функций, закрывающий требования второго предварительного этапа:
|
||||
|
||||
- разработано рабочее ядро управления локальными и удалёнными vault;
|
||||
- реализованы пользовательские экраны и диалоговые сценарии для основных операций;
|
||||
- добавлена авторизация в Яндекс как часть удалённого контура;
|
||||
- внедрено хранение метаданных и служебной информации в Room;
|
||||
- проведено unit-тестирование криптографии и ручное тестирование экранов.
|
||||
|
||||
Таким образом, второй этап завершён с заметным приростом прикладной готовности проекта: архитектура первого этапа переведена в рабочее приложение с подтверждённой функциональностью базового уровня.
|
||||
|
||||
## Реализация основных работ (п. 2.1 ТЗ)
|
||||
|
||||
### 1) Разработка модуля ядра приложения Wallenc
|
||||
|
||||
В рамках ядра приложения реализованы следующие возможности:
|
||||
|
||||
- создание и управление локальными vault;
|
||||
- хранение метаданных vault на устройстве;
|
||||
- включение шифрования выбранного vault с формированием параметров шифрования;
|
||||
- открытие и закрытие зашифрованного представления vault с проверкой корректности ключа;
|
||||
- доступ к содержимому vault через слой абстракции хранилищ;
|
||||
- сокрытие служебных объектов и фильтрация системных директорий при отображении данных пользователю.
|
||||
|
||||
Ядро реализовано в логике модульной архитектуры (domain/data/presentation), что обеспечило разделение бизнес-логики, доступа к данным и UI-слоя.
|
||||
|
||||
Для предотвращения конфликтов при параллельных операциях в ядре используется контроль запущенных задач (например, защита от повторного запуска шифрования одного и того же vault до завершения предыдущей операции). Это повысило устойчивость работы приложения при активном пользовательском взаимодействии.
|
||||
|
||||
**Скриншот локальных vault:**
|
||||

|
||||
|
||||
### 2) Разработка мобильного приложения на Kotlin (Android)
|
||||
|
||||
В пользовательском интерфейсе реализованы ключевые операции работы с vault:
|
||||
|
||||
- отображение списка vault;
|
||||
- переименование и удаление vault;
|
||||
- включение шифрования vault;
|
||||
- открытие зашифрованного vault с использованием мастер-ключа;
|
||||
- просмотр параметров vault (состояние, служебные сведения, статус шифрования);
|
||||
- модуль работы с содержимым vault;
|
||||
- блокировка/закрытие vault.
|
||||
|
||||
Реализованы диалоговые сценарии подтверждения и настройки операций, что повысило управляемость и предсказуемость действий пользователя.
|
||||
|
||||
Интерфейсная часть построена таким образом, чтобы пользователь видел текущее состояние хранилища (зашифровано/не зашифровано, открыто/закрыто) и мог выполнить требуемое действие без перехода в технические служебные экраны. Это улучшило эргономику взаимодействия и снизило число лишних шагов в типовых сценариях.
|
||||
|
||||
**Скриншоты диалогов и операций:**
|
||||

|
||||

|
||||

|
||||
|
||||
### 3) Реализация модуля работы с небезопасными хранилищами через адаптеры
|
||||
|
||||
Реализован модуль адаптерного взаимодействия с типами хранилищ, не требующий собственного серверного контура приложения.
|
||||
Подход через адаптеры позволил сохранить единый интерфейс работы с vault и упростить расширение списка поддерживаемых провайдеров.
|
||||
|
||||
Технически это важно для дальнейшего масштабирования проекта: добавление нового типа внешнего провайдера не требует переработки пользовательской логики и не нарушает принципы слоистой архитектуры.
|
||||
|
||||
### 4) Взаимодействие мобильного клиента с внешними провайдерами через API/SDK
|
||||
|
||||
На текущем этапе реализована авторизация через Яндекс (OAuth-сценарий) и интеграция соответствующего пользовательского потока в приложение.
|
||||
Выполнена подготовка слоя удалённых vault и связанных сущностей для дальнейшего расширения удалённых операций.
|
||||
|
||||
Важно, что реализованный контур уже обеспечивает прикладной сценарий идентификации пользователя во внешнем провайдере и формирует корректную основу для дальнейшего расширения функциональности удалённых операций.
|
||||
|
||||
**Скриншоты удалённого контура:**
|
||||

|
||||

|
||||
|
||||
### 5) Реализация хранения ключевой информации и метаданных с использованием Room
|
||||
|
||||
Реализован слой локальной БД на Room для хранения:
|
||||
|
||||
- таблиц сопоставления ключевой информации и идентификаторов хранилищ;
|
||||
- таблиц метаданных vault и состояния хранилищ;
|
||||
- данных, необходимых для восстановления состояния приложения.
|
||||
|
||||
Структура БД интегрирована в общий цикл работы приложения и используется как системный слой управления состоянием.
|
||||
|
||||
Использование Room позволило обеспечить устойчивое хранение служебной информации между запусками приложения и централизовать доступ к данным через DAO-слой.
|
||||
|
||||
**Визуализация Room БД:**
|
||||

|
||||
|
||||
### 6) Проведение тестирования приложения
|
||||
|
||||
По итогам этапа проведено тестирование реализованного функционала:
|
||||
|
||||
- выполнено unit-тестирование криптографических компонентов (проверка корректности шифрования/дешифрования и валидации ключа);
|
||||
- выполнено ручное тестирование экранов и основных пользовательских сценариев работы с vault.
|
||||
|
||||
Проведённые проверки подтвердили работоспособность базового функционального ядра второго этапа.
|
||||
|
||||
## Фрагменты реализованного кода
|
||||
|
||||
Ниже приведены показательные фрагменты кода, отражающие ключевые результаты второго этапа.
|
||||
|
||||
### 1) Room-база приложения и состав сущностей
|
||||
|
||||
```kotlin
|
||||
@Database(
|
||||
entities = [DbStorageKeyMap::class, DbStorageMetaInfo::class, DbYandexAccount::class],
|
||||
version = 4,
|
||||
exportSchema = false,
|
||||
)
|
||||
abstract class AppDb : IAppDb, RoomDatabase() {
|
||||
abstract override val storageKeyMapDao: StorageKeyMapDao
|
||||
abstract override val storageMetaInfoDao: StorageMetaInfoDao
|
||||
abstract override val yandexAccountDao: YandexAccountDao
|
||||
}
|
||||
```
|
||||
|
||||
Фрагмент демонстрирует, что на этапе реализована целевая модель хранения метаданных и удалённой учётной информации в Room с разделением доступа через DAO.
|
||||
|
||||
### 2) Логика шифрования и открытия зашифрованного vault в ViewModel
|
||||
|
||||
```kotlin
|
||||
fun enableEncryption(storage: IStorageInfo, password: String, encryptPath: Boolean) {
|
||||
val key = EncryptKey(password)
|
||||
viewModelScope.launch {
|
||||
when (manageStoragesEncryptionUseCase.canEncrypt(storage)) {
|
||||
ManageStoragesEncryptionUseCase.CanEncryptResult.Allowed -> {
|
||||
manageStoragesEncryptionUseCase.enableEncryption(storage, key, encryptPath)
|
||||
manageStoragesEncryptionUseCase.openStorage(storage, key, true)
|
||||
_messages.emit("Encryption enabled")
|
||||
}
|
||||
ManageStoragesEncryptionUseCase.CanEncryptResult.AlreadyEncrypted ->
|
||||
_messages.emit("Storage is already encrypted")
|
||||
else -> _messages.emit("Unsupported operation")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Фрагмент иллюстрирует, что включение шифрования реализовано как управляемый сценарий с валидацией состояния, запуском шифрования и последующим открытием зашифрованного представления.
|
||||
|
||||
### 3) UI-операции над удалёнными vault
|
||||
|
||||
```kotlin
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
if (!uiState.isBusy) viewModel.setAddChoiceVisible(true)
|
||||
},
|
||||
) {
|
||||
Icon(Icons.Filled.Add, contentDescription = stringResource(R.string.remote_vaults_add_cd))
|
||||
}
|
||||
...
|
||||
FilledTonalButton(
|
||||
onClick = {
|
||||
viewModel.setAddChoiceVisible(false)
|
||||
viewModel.yandexSignIn.launch { outcome ->
|
||||
when (outcome) {
|
||||
is RemoteYandexAuthResult.Success ->
|
||||
viewModel.onYandexAuthSuccess(outcome.accessToken)
|
||||
is RemoteYandexAuthResult.Failure -> { /* сообщение об ошибке */ }
|
||||
RemoteYandexAuthResult.Cancelled -> { }
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
В данном фрагменте отражён завершённый пользовательский путь запуска авторизации для удалённого провайдера непосредственно из интерфейса приложения.
|
||||
|
||||
### 4) Unit-тесты криптографического контура
|
||||
|
||||
```kotlin
|
||||
@Test
|
||||
fun `test correct key for StorageEncryptionInfo`() {
|
||||
val encInfo = Encryptor.generateEncryptionInfo(key1)
|
||||
val res = Encryptor.checkKey(key = key1, encInfo = encInfo)
|
||||
assertEquals(true, res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test incorrect key for StorageEncryptionInfo`() {
|
||||
val encInfo = Encryptor.generateEncryptionInfo(key1)
|
||||
val res = Encryptor.checkKey(key = key2, encInfo = encInfo)
|
||||
assertEquals(false, res)
|
||||
}
|
||||
```
|
||||
|
||||
Тесты подтверждают корректную проверку ключа и являются частью подтверждения работоспособности криптографической логики второго этапа.
|
||||
|
||||
## Реализация дополнительных работ (п. 2.2 ТЗ)
|
||||
|
||||
### 1) Синхронизация зашифрованных данных с удалёнными хранилищами без передачи ключей на сервер
|
||||
|
||||
В рамках второго этапа подготовлена и реализована безопасная база для удалённых сценариев:
|
||||
|
||||
- внедрён поток авторизации пользователя через Яндекс;
|
||||
- реализованы сущности и механизм учёта удалённых vault;
|
||||
- сохранён принцип клиентской защиты данных, при котором ключевая логика шифрования остаётся на стороне приложения.
|
||||
|
||||
Это обеспечивает корректную архитектурную основу для дальнейшего развития синхронизационных операций без передачи ключей на сторону внешних сервисов.
|
||||
|
||||
### 2) Тестирование пользовательского интерфейса
|
||||
|
||||
Проведено ручное тестирование пользовательского интерфейса с проверкой:
|
||||
|
||||
- отображения списков локальных и удалённых vault;
|
||||
- корректности вызова и отработки диалоговых окон;
|
||||
- стабильности выполнения операций создания, переименования, удаления, шифрования, открытия и закрытия vault.
|
||||
|
||||
Ручная проверка проводилась на последовательностях реальных пользовательских действий, что позволило проверить не только отдельные функции, но и их связную работу в рамках типового использования приложения.
|
||||
|
||||
## Итоги второго этапа
|
||||
|
||||
По результатам второго этапа сформировано и протестировано работоспособное ядро мобильного приложения Wallenc.
|
||||
Реализованы ключевые функции управления vault, включения и использования шифрования, хранения служебных данных через Room, а также пользовательские интерфейсы для локальных и удалённых сценариев.
|
||||
|
||||
Этап завершён с практическим результатом в виде функционирующего Android-приложения на Kotlin, готового к дальнейшему расширению функциональности удалённой работы с хранилищами и развитию синхронизационных механизмов.
|
||||
|
||||
В целом второй этап можно считать успешно завершённым: ключевые задачи реализованы, протестированы и представлены в форме, пригодной для демонстрации и последующего развития проекта на следующем этапе практики.
|
||||
@@ -0,0 +1,584 @@
|
||||
МИНОБРНАУКИ РОССИИ
|
||||
Федеральное государственное автономное образовательное
|
||||
учреждение высшего образования
|
||||
«ЮЖНЫЙ ФЕДЕРАЛЬНЫЙ УНИВЕРСИТЕТ»
|
||||
|
||||
Институт компьютерных технологий и информационной безопасности
|
||||
Структурное подразделение
|
||||
|
||||
Направление подготовки **09.03.04** — «Программная инженерия» (бакалавриат), профиль **«Методы и средства разработки программного обеспечения»**
|
||||
|
||||
**ОТЧЁТ**
|
||||
**о прохождении практики**
|
||||
|
||||
обучающегося **4** курса, группа **КТбо4-9**
|
||||
|
||||
**Тема:** Разработка мобильного приложения Wallenc — универсального «кошелька» для безопасного хранения данных на небезопасных хранилищах без собственного сервера (Android, Kotlin).
|
||||
|
||||
Фамилия — **Пытков**
|
||||
Имя — **Роман**
|
||||
Отчество — **Евгеньевич**
|
||||
|
||||
**Место практики:** ООО НМФ «Нейротех»
|
||||
|
||||
Вид практики: производственная
|
||||
Тип практики: технологическая (проектно-технологическая)
|
||||
Способ проведения практики: стационарная
|
||||
|
||||
**Сроки прохождения практики:** **с 09.02.2026 г. по 06.05.2026 г.** *(по приказу ЮФУ № 2191-к от 17.02.2026 г.)*
|
||||
|
||||
---
|
||||
|
||||
**Задание обучающегося на практику согласовано:**
|
||||
|
||||
| Руководитель практики от Университета | Руководитель практики от профильной организации |
|
||||
| --- | --- |
|
||||
| _______________________ Беликов Александр Николаевич | _______________________ Алексеев Дмитрий Михайлович |
|
||||
|
||||
*(По приказу: руководитель от ЮФУ — старший преподаватель кафедры системного анализа и телекоммуникаций Беликов А. Н.; от организации — технический директор Алексеев Д. М., ООО НМФ «Нейротех».)*
|
||||
|
||||
---
|
||||
|
||||
## 1. Задание обучающегося на практику
|
||||
|
||||
| № | Содержание задания (выполнялось в рамках проекта Wallenc) |
|
||||
| --- | --- |
|
||||
| 1 | **Анализ предметной области.** Требования к хранению чувствительных данных, клиентскому шифрованию и сценариям работы с vault. |
|
||||
| 2 | **Обзор аналогов.** Сравнение решений (secure-folder, менеджеры секретов, zero-knowledge); выводы для концепции Wallenc. |
|
||||
| 3 | **Техническое задание.** Структура ТЗ по ГОСТ 7.32–2017; приоритет ядра хранения и шифрования. |
|
||||
| 4 | **Архитектура.** MVVM + Clean Architecture; структура системы и абстракции хранилищ; задел под удалённые провайдеры. |
|
||||
| 5 | **Проектирование БД.** Служебные сущности Room (метаданные, соответствия, восстановление состояния); без открытого пользовательского контента. |
|
||||
| 6 | **Технологический стек.** Kotlin, Android, Compose, Coroutines/Flow, Hilt, Room, AES на клиенте; модули app / presentation / domain / data. |
|
||||
| 7 | **Доп. исследования (этап 1).** OAuth без своего сервера; ключи и проверка ключа; защита путей/имён; план тестов. |
|
||||
| 8 | **Ядро приложения (этап 2).** Vault, метаданные, шифрование/открытие/закрытие, абстракция хранилищ, защита от гонок при длительных операциях. |
|
||||
| 9 | **Интерфейс (Compose).** Списки vault, диалоги операций, состояние шифрования/открытия, содержимое vault. |
|
||||
| 10 | **Адаптеры хранилищ.** Единый интерфейс vault без серверного backend приложения. |
|
||||
| 11 | **Внешний провайдер.** OAuth Яндекс; слой удалённых vault. |
|
||||
| 12 | **Room.** Таблицы метаданных, ключей, учётных записей; DAO и жизненный цикл приложения. |
|
||||
| 13 | **Доп. (этап 2).** Проект синхронизации без передачи ключей на сервер; ручное UI-тестирование сценариев. |
|
||||
| 14 | **Тестирование.** Unit-тесты криптографии; ручные прогоны экранов. |
|
||||
| 15 | **Отчётность.** Отчёты по этапам и итоговый отчёт с иллюстрациями. |
|
||||
|
||||
---
|
||||
|
||||
## 2. Инструктаж по охране труда, технике безопасности, пожарной безопасности, правилам внутреннего распорядка
|
||||
|
||||
*(До начала практики проводится собрание и вводный инструктаж в соответствии с приказом ЮФУ от 14.10.2016 № 1513 «Об обеспечении безопасности жизни и здоровья обучающихся, работников при проведении учебных и производственных практик» — см. приказ № 2191-к.)*
|
||||
|
||||
| | Инструктаж проведён | Ознакомлен |
|
||||
| --- | --- | --- |
|
||||
| по требованиям охраны труда | ________________________________ «\_\_» \_\_\_\_\_\_\_\_\_ 2026 г. | ________________________________ «\_\_» \_\_\_\_\_\_\_\_\_ 2026 г. |
|
||||
| по технике безопасности | | |
|
||||
| по пожарной безопасности | | |
|
||||
| по правилам внутреннего трудового распорядка | | |
|
||||
|
||||
---
|
||||
|
||||
## 3. Дневник практики
|
||||
|
||||
| Дата | Выполненные мероприятия в соответствии с заданием на практику |
|
||||
| --- | --- |
|
||||
| **09.02.2026** | Начало производственной практики по приказу ЮФУ. Участие во вводном инструктаже по требованиям охраны труда, техники безопасности, пожарной безопасности и правилам внутреннего распорядка (в соответствии с приказом ЮФУ от 14.10.2016 № 1513). Ознакомление с целями практики и проектом Wallenc. |
|
||||
| **10.02.2026 — 16.02.2026** | **Анализ предметной области (этап 1):** изучение подходов к хранению чувствительных данных в мобильных приложениях и облачных хранилищах. Формулировка требований: конфиденциальность при хранении и передаче; отсутствие необходимости доверять инфраструктуре хранилища; устойчивость к компрометации удалённого провайдера; разделение логики хранения и криптографической защиты; пользовательские сценарии (создание хранилища, шифрование, открытие, работа с содержимым). Вывод о приоритете клиентских криптографических механизмов, унифицированного доступа к разным типам хранилищ и архитектуры с чётким разделением слоёв. |
|
||||
| **17.02.2026 — 28.02.2026** | **Обзор аналогов (этап 1):** детальный разбор **Google Files Secure Folder** (локальный «сейф» по PIN/Pattern; ограничения по кроссплатформенной синхронизации и универсальной модели vault); **Proton Pass / Proton Drive** (E2E-экосистема; привязка к экосистеме Proton, тарифные ограничения, меньшая гибкость как универсального клиента); **Bitwarden** (менеджер секретов; ориентация на учётные данные, ограничения офлайн-сценариев); **Cryptomator** (клиентское шифрование vault в облаке; фокус на файлах, ограничения интеграций). Обобщение по классам решений; фиксация типовых преимуществ (понятный сценарий, готовое шифрование файлов, интеграция с хранилищами) и ограничений (зависимость от backend, прозрачность модели ключей, переносимость). Подтверждение актуальности концепции Wallenc: безопасность без собственного сервера и переносимая архитектура хранилищ. |
|
||||
| **01.03.2026 — 14.03.2026** | **ТЗ и архитектура (этап 1):** подготовка структуры технического задания по логике **ГОСТ 7.32–2017** (цели, этапы, основные и дополнительные задачи, ожидаемые результаты, направления тестирования), синхронизация ТЗ с приоритетом ядра хранения/шифрования. Изучение и закрепление выбора **MVVM + Clean Architecture** с разбиением на Domain (интерфейсы и use case — операции над vault и шифрованием), Data (реализации хранилищ, доступ к данным, локальная БД, криптоадаптеры), Presentation (UI, навигация, состояния экранов, ViewModel). Обоснование снижения связности и возможности независимого развития локальных и удалённых провайдеров. |
|
||||
| **15.03.2026 — 28.03.2026** | **Проектирование системы и БД, стек (этап 1):** проектирование клиентской системы — локальный vault как базовая реализация; отдельный менеджер для открытия зашифрованного представления; операции чтения/записи через абстракции доступа к файлам; отделение служебных данных (метаданные, связи ключей и хранилищ) от пользовательского содержимого; закладка на подключение удалённых провайдеров без изменения доменной модели. Проектирование локальной БД: соответствия исходное/зашифрованное хранилище, метаданные, восстановление состояния при запуске; принцип — БД не хранит пользовательские данные в открытом виде. Выбор стека: Kotlin, Android SDK, Jetpack Compose, Coroutines/Flow, Hilt, Room, AES на клиенте, модули `app`, `presentation`, `domain`, `data`. **Доп. исследования:** OAuth без собственного сервера; работа с ключами и проверка ключа; скрытие служебных путей и имён; план unit/интеграционных/UI-тестов. |
|
||||
| **29.03.2026 — 19.04.2026** | **Реализация (2-й этап, период 29.03.2026–19.04.2026):** разработка ядра — CRUD локальных vault, метаданные, шифрование и параметры, открытие/закрытие с проверкой ключа, абстракция хранилищ, фильтрация системных каталогов и скрытие служебных объектов в UI, защита от повторного запуска шифрования. UI на Compose: списки, диалоги переименования/удаления/шифрования/открытия, параметры vault, содержимое, блокировка; отображение состояния шифрования и открытия. Адаптеры типов хранилищ. **Яндекс OAuth**, слой удалённых vault. **Room:** `DbStorageKeyMap`, `DbStorageMetaInfo`, `DbYandexAccount`, DAO, версия схемы 4. **Тесты:** unit-тесты криптографии (`Encryptor.generateEncryptionInfo`, `checkKey` для верного и неверного ключа); ручные прогоны экранов и сценариев. **Дополнительно:** основа для синхронизации без передачи ключей на сервер; ручное UI-тестирование связных цепочек действий. |
|
||||
| **21.04.2026 — 05.05.2026** | Подготовка иллюстраций к отчёту: скриншоты приложения, схема Room, экран задач и уведомление о статусах размещены в **приложении 1** непосредственно в соответствующих подразделах (пп. 3.2–3.7); подготовка диаграмм user flow и доменной модели (заготовки в п. 5 приложения). Систематизация результатов практики, оформление отчётной документации. |
|
||||
| **06.05.2026** | Завершение практики, защита результатов *(дата окончания по приказу)*. |
|
||||
|
||||
---
|
||||
|
||||
## 4. Анализ проведённой работы в период прохождения практики обучающимся
|
||||
|
||||
| № п/п | Выполненные мероприятия | Анализ проведённой работы |
|
||||
| --- | --- | --- |
|
||||
| 1 | Анализ предметной области | Выполнен систематический обзор требований к системам хранения чувствительных данных: учтены конфиденциальность, модель «недоверенное хранилище», разделение криптографии и хранения, пользовательские сценарии. Сформирован вывод о необходимости клиентского шифрования до выгрузки данных и единой абстракции над разными провайдерами — это определило дальнейшую архитектуру Wallenc. |
|
||||
| 2 | Обзор аналогов | По каждому из рассмотренных решений зафиксированы назначение и ограничения (локальный сейф, привязка к экосистеме, ориентация на секреты вместо файлового vault, ограничения интеграций). Сравнение по классам продуктов позволило отделить идею Wallenc от «ещё одного облачного клиента» и обосновать фокус на переносимости и отсутствии собственного backend. |
|
||||
| 3 | Техническое задание | Структура ТЗ по ГОСТ 7.32–2017 обеспечила связность целей, этапов и критериев тестирования. Приоритет ядра хранения и шифрования зафиксирован в ТЗ и затем последовательно реализован на втором этапе без смещения фокуса на второстепенные функции. |
|
||||
| 4 | Архитектура MVVM + Clean Architecture | Разделение на domain/data/presentation снизило связность компонентов: сценарии шифрования и работы с vault выражены через use case, доступ к файлам и Room изолирован в data, интерфейс и состояние — в presentation. Это упростило поэтапное внедрение удалённого контура (Яндекс) без переписывания доменной логики. |
|
||||
| 5 | Проектирование структуры системы и БД | Спроектированные абстракции (локальный vault, менеджер зашифрованного представления, файловые абстракции) соответствуют реализованному коду второго этапа. Роль Room как хранилища **только** служебной информации соблюдена: пользовательский контент в открытом виде в БД не сохраняется, что согласуется с моделью угроз. |
|
||||
| 6 | Выбор технологий и доп. исследования | Стек Kotlin/Compose/Room/Hilt/Coroutines оказался достаточным для реализации всех запланированных на этапе функций. Отдельно проработанные темы OAuth без своего сервера, проверка ключа и минимизация утечек через пути подготовили реализацию Яндекс-авторизации и криптографического контура без противоречий с общей моделью безопасности. |
|
||||
| 7 | Реализация ядра приложения | Реализован полный набор операций жизненного цикла vault и шифрования; контроль параллельных задач снизил риск некорректного состояния при повторных нажатиях. Сокрытие служебных объектов и фильтрация системных директорий улучшили безопасность и удобство отображения содержимого. |
|
||||
| 8 | Пользовательский интерфейс | Интерфейс отражает состояние хранилища (шифрование, открытие) на основных экранах; диалоги подтверждения снижают риск деструктивных ошибок. Реализованы как локальный, так и удалённый контуры в одном приложении. |
|
||||
| 9 | Адаптеры и интеграция с Яндекс | Адаптерный подход подтвердил расширяемость: добавление провайдера не требует переписывания пользовательских сценариев. OAuth-интеграция доведена до рабочего пользовательского потока; подготовлены сущности для развития удалённых операций с сохранением принципа отсутствия передачи ключей на внешние сервисы приложения. |
|
||||
| 10 | Room и метаданные | Внедрение сущностей для ключей, метаданных и учётных записей Яндекс с раздельными DAO обеспечило устойчивое состояние между сеансами и ясную границу ответственности слоя данных. |
|
||||
| 11 | Тестирование | Unit-тесты на `Encryptor` подтверждают корректную генерацию параметров шифрования и валидацию ключа; ручное тестирование покрыло типовые цепочки и взаимодействие модулей в сборке приложения. |
|
||||
| 12 | Документирование | Промежуточные отчёты по этапам зафиксировали содержание работ; итоговый отчёт объединяет аналитику, проектирование, реализацию и результаты проверок с иллюстрациями. |
|
||||
|
||||
---
|
||||
|
||||
## 5. Отзыв руководителя практики от профильной организации
|
||||
|
||||
В процессе прохождения практики обучающийся **Пытков Роман Евгеньевич** выполнил анализ предметной области защищённого хранения данных и обзор аналогичных решений, на основе которых сформулировал требования к мобильному приложению **Wallenc** (клиентское шифрование, работа с недоверенными хранилищами без собственного сервера приложения). Подготовил структуру технического задания и спроектировал архитектуру **MVVM + Clean Architecture**, модель служебных данных и использование **Room** для метаданных и учётных записей внешних провайдеров.
|
||||
|
||||
На втором этапе практики реализовал функциональное ядро: управление **vault**, включение и использование шифрования, пользовательский интерфейс на **Kotlin** и **Jetpack Compose**, адаптерный слой хранилищ и интеграцию **OAuth Яндекс** для удалённого контура. Провёл **unit**-тестирование криптографического модуля и ручную проверку пользовательских сценариев. Все поставленные задачи в рамках практики выполнены **в срок**, с **самостоятельной** постановкой работ и добросовестным отношением к вопросам безопасности и качества кода.
|
||||
|
||||
За время практики зарекомендовал себя **дисциплинированным, ответственным, исполнительным** студентом. Программа производственной (технологической) практики выполнена **своевременно**, **в полном объёме** и с **высоким** качеством результатов.
|
||||
|
||||
**Рекомендуемая оценка — «отлично».**
|
||||
|
||||
Руководитель практики
|
||||
от профильной организации ______________________ / **Алексеев Дмитрий Михайлович** /
|
||||
|
||||
подпись расшифровка подписи
|
||||
|
||||
---
|
||||
|
||||
## 6. Отзыв руководителя практики от университета
|
||||
|
||||
Студент **4** курса группы **КТбо4-9** **Пытков Роман Евгеньевич**, направления подготовки **09.03.04** «Программная инженерия» (профиль «Методы и средства разработки программного обеспечения»), в **8** семестре прошёл стационарную **производственную** практику в компании **ООО НМФ «Нейротех»**.
|
||||
|
||||
В период практики **Пытков Р. Е.** работал в качестве **разработчика программного обеспечения** (мобильное приложение **Wallenc** на платформе **Android**). Им были решены следующие задачи:
|
||||
|
||||
1. Анализ предметной области: требования к хранению чувствительных данных, клиентскому шифрованию и сценариям работы с vault.
|
||||
2. Обзор аналогов (secure-folder, менеджеры секретов, zero-knowledge) и выводы для концепции проекта.
|
||||
3. Подготовка структуры технического задания по **ГОСТ 7.32–2017** с приоритетом ядра хранения и шифрования.
|
||||
4. Проектирование архитектуры **MVVM + Clean Architecture**, структуры системы и абстракций хранилищ; задел под удалённые провайдеры.
|
||||
5. Проектирование локальной **БД (Room)**: служебные сущности без хранения пользовательского контента в открытом виде.
|
||||
6. Выбор и обоснование технологического стека (**Kotlin**, **Compose**, **Coroutines/Flow**, **Hilt**, **Room**, криптография на клиенте).
|
||||
7. Дополнительные исследования: **OAuth** без собственного сервера, работа с ключами, минимизация утечек по путям и именам, план тестирования.
|
||||
8. Реализация **ядра приложения**: vault, метаданные, шифрование и открытие/закрытие, защита от гонок при длительных операциях.
|
||||
9. Разработка пользовательского **интерфейса** (**Jetpack Compose**): списки vault, диалоги операций, отображение состояния шифрования и доступа к содержимому.
|
||||
10. Реализация **адаптеров** типов хранилищ с единым интерфейсом без серверного backend приложения.
|
||||
11. Интеграция с внешним провайдером: **OAuth Яндекс**, слой удалённых vault.
|
||||
12. Внедрение **Room**: таблицы метаданных, ключей, учётных записей; **DAO** и использование в жизненном цикле приложения.
|
||||
13. Проектирование контура **синхронизации** зашифрованных данных без передачи ключей на сервер; ручное **UI**-тестирование сценариев.
|
||||
14. **Тестирование**: unit-тесты криптографии; ручные прогоны экранов и пользовательских цепочек.
|
||||
15. **Отчётность**: отчёты по предварительным этапам и итоговый отчёт с иллюстрациями и диаграммами пользовательских потоков.
|
||||
|
||||
За время прохождения практики **Пытков Р. Е.** показал **высокий** уровень теоретической подготовки, **высокую** степень умения и навыков применять знания, полученные в университете, для решения практических задач.
|
||||
|
||||
**Пытковым Р. Е.** проявлены следующие личностные и профессиональные качества: самостоятельность, ответственность, системность в подходе к проектированию, внимание к требованиям информационной безопасности, умение работать с технической документацией и оформлять результаты работы.
|
||||
|
||||
Считаю, что проявленные профессиональные качества **полностью** удовлетворяют потребностям профильной организации, программа практики выполнена **в полном объёме**, сроки выполнения заданий соблюдены **полностью**.
|
||||
|
||||
**Рекомендуемая оценка — «отлично».**
|
||||
|
||||
Руководитель практики
|
||||
от Университета ______________________ / **Беликов Александр Николаевич** /
|
||||
|
||||
подпись расшифровка подписи
|
||||
|
||||
---
|
||||
|
||||
# ПРИЛОЖЕНИЕ 1
|
||||
|
||||
**ОТЧЁТ**
|
||||
**о прохождении производственной практики, технологической практики на тему**
|
||||
|
||||
«Разработка мобильного приложения Wallenc — универсального «кошелька» для безопасного хранения данных на небезопасных хранилищах без собственного сервера (Android, Kotlin)»
|
||||
|
||||
Студента гр. **КТбо4-9** **Пыткова Романа Евгеньевича**
|
||||
|
||||
---
|
||||
|
||||
## Содержание
|
||||
|
||||
1. [Введение](#введение)
|
||||
2. [Результаты первого предварительного этапа (аналитика и проектирование)](#2-результаты-первого-предварительного-этапа-аналитика-и-проектирование)
|
||||
3. [Результаты второго предварительного этапа (реализация и тестирование)](#3-результаты-второго-предварительного-этапа-реализация-и-тестирование)
|
||||
4. [Фрагменты реализованного кода и пояснения](#4-фрагменты-реализованного-кода-и-пояснения)
|
||||
5. [Дополнительные иллюстрации (диаграммы)](#5-дополнительные-иллюстрации-диаграммы)
|
||||
6. [Заключение](#заключение)
|
||||
7. [Список условных обозначений и сокращений](#список-условных-обозначений-и-сокращений)
|
||||
8. [Использованные источники](#использованные-источники)
|
||||
|
||||
---
|
||||
|
||||
## Введение
|
||||
|
||||
**Wallenc** — мобильное приложение-кошелёк для безопасного хранения пользовательских данных на недоверенных или потенциально небезопасных хранилищах **без развёртывания собственного сервера** приложения. Ключевая идея: безопасность обеспечивается **на стороне клиента** — данные **шифруются до отправки** во внешнее хранилище, расшифрование выполняется **только в приложении** при наличии корректного ключа. Это позволяет использовать небезопасные или недоверенные хранилища без потери конфиденциальности и без серверного backend приложения.
|
||||
|
||||
**Цель практики** — пройти полный цикл технологической (проектно-технологической) работы: от анализа предметной области и оформления требований до работающего Android-приложения на Kotlin с проверкой ключевых сценариев и тестированием.
|
||||
|
||||
**Задачи:** исследовать предметную область и аналоги; подготовить ТЗ и спроектировать архитектуру и данные; реализовать ядро, UI и интеграцию с внешним провайдером; провести тестирование; оформить отчётность.
|
||||
|
||||
**Объект исследования** — методы клиентской защиты данных в мобильных приложениях.
|
||||
**Предмет исследования** — проектные и программные решения приложения Wallenc.
|
||||
|
||||
---
|
||||
|
||||
## 2. Результаты первого предварительного этапа (аналитика и проектирование)
|
||||
|
||||
На первом этапе практики выполнена **аналитическая и проектная** подготовка к разработке Wallenc: определена предметная область, выбран архитектурный подход, описаны требования к системе и зафиксированы технологические решения для последующей реализации.
|
||||
|
||||
### 2.1. Анализ предметной области
|
||||
|
||||
Рассмотрены подходы к хранению чувствительных данных в мобильных приложениях и облачных хранилищах. Выделены требования:
|
||||
|
||||
- конфиденциальность данных при хранении и передаче;
|
||||
- отсутствие необходимости доверять инфраструктуре хранилища;
|
||||
- устойчивость к компрометации удалённого провайдера;
|
||||
- разделение логики хранения и логики криптографической защиты;
|
||||
- удобный пользовательский сценарий: создание хранилища, шифрование, открытие, работа с содержимым.
|
||||
|
||||
**Вывод:** для Wallenc приоритетны клиентские криптографические механизмы, унифицированный доступ к разным типам хранилищ и архитектура с чётким разделением слоёв.
|
||||
|
||||
### 2.2. Обзор аналогичных решений
|
||||
|
||||
**Google Files Secure Folder** — локально прячет и защищает файлы в папке по PIN/Pattern внутри Android. **Ограничения:** по сути только локальный «сейф» без полноценной кроссплатформенной синхронизации; ограниченные сценарии переноса между устройствами; не универсальная модель vault для разных хранилищ.
|
||||
|
||||
**Proton Pass / Proton Drive** — E2E-экосистема для паролей, заметок и файлов с облачной синхронизацией. **Ограничения:** привязка к экосистеме Proton; часть сценариев и объёма зависит от тарифа; меньше гибкости как универсального клиента под разные внешние хранилища.
|
||||
|
||||
**Bitwarden** — менеджер паролей/секретов с шифрованием, синхронизацией, офлайн-доступом и вариантом self-host. **Ограничения:** ориентация на учётные данные и секреты, а не на общий файловый vault; офлайн-сценарии ограничены (часть операций требует синхронизации/сервера).
|
||||
|
||||
**Cryptomator** — клиентское шифрование файловых vault перед хранением в облаке (zero-knowledge). **Ограничения:** фокус на шифровании файлов, а не на расширенной модели «кошелька» с собственной мета-логикой и UI-сценариями; ограничения интеграций в зависимости от платформы/сборки и провайдера.
|
||||
|
||||
Проведён обзор **классов решений:** secure-folder приложения, менеджеры секретов, облачные клиенты с zero-knowledge.
|
||||
|
||||
**Типовые преимущества аналогов:** понятный пользовательский сценарий хранения; готовые механизмы шифрования файлов; интеграция с несколькими хранилищами.
|
||||
|
||||
**Типовые ограничения:** зависимость от собственного backend или закрытой инфраструктуры; недостаточная прозрачность модели ключей; ограниченная переносимость между провайдерами.
|
||||
|
||||
Это **подтвердило актуальность** концепции Wallenc: безопасность без собственного сервера и **переносимая** архитектура хранилищ.
|
||||
|
||||
### 2.3. Формирование технического задания
|
||||
|
||||
На основании анализа подготовлена **структура ТЗ** в логике **ГОСТ 7.32–2017**: цели, этапы работ, основные и дополнительные задачи, ожидаемые результаты, направления тестирования. ТЗ **синхронизировано** с направлением проекта: приоритет на ядро системы хранения и шифрования и **поэтапное** расширение функциональности.
|
||||
|
||||
### 2.4. Изучение архитектурных подходов
|
||||
|
||||
Принят подход **MVVM + Clean Architecture**:
|
||||
|
||||
- **Domain** — интерфейсы и use case (операции над vault и шифрованием);
|
||||
- **Data** — реализации хранилищ, слой доступа к данным, локальная БД, криптографические адаптеры;
|
||||
- **Presentation** — UI-слой, навигация, состояния экранов, ViewModel.
|
||||
|
||||
Такое разделение **снижает связность**, **упрощает тестирование** и позволяет **независимо** развивать локальные и удалённые провайдеры.
|
||||
|
||||
### 2.5. Проектирование структуры системы
|
||||
|
||||
Спроектирована клиентская система, в которой:
|
||||
|
||||
- локальный vault — базовая реализация хранения;
|
||||
- зашифрованное представление открывается через **отдельный менеджер**;
|
||||
- операции чтения/записи выполняются через **абстракции доступа к файлам**;
|
||||
- служебные данные (метаданные, связи ключей и хранилищ) **отделены** от пользовательского содержимого.
|
||||
|
||||
Подход ориентирован на дальнейшее подключение удалённых провайдеров (в т.ч. облачных API) **без изменения доменной модели**.
|
||||
|
||||
### 2.6. Проектирование структуры базы данных
|
||||
|
||||
Определена структура локальной БД для **служебной** информации:
|
||||
|
||||
- соответствия между исходным хранилищем и зашифрованным представлением;
|
||||
- метаданные хранилищ;
|
||||
- поддержка восстановления состояния при запуске приложения.
|
||||
|
||||
БД используется как внутренний механизм управления состоянием vault и **не хранит** пользовательские данные в открытом виде.
|
||||
|
||||
### 2.7. Выбор технологий
|
||||
|
||||
Определён стек:
|
||||
|
||||
- **Kotlin**, **Android SDK**;
|
||||
- **Jetpack Compose** (UI);
|
||||
- **Coroutines/Flow** (асинхронность и реактивные потоки);
|
||||
- **Hilt** (внедрение зависимостей);
|
||||
- **Room** (локальная БД);
|
||||
- криптография на клиенте (**AES** для данных и служебных атрибутов);
|
||||
- модульная структура: `app`, `presentation`, `domain`, `data`.
|
||||
|
||||
Стек согласован с целями проекта и обеспечивает **масштабируемость**.
|
||||
|
||||
### 2.8. Дополнительные исследования первого этапа
|
||||
|
||||
- варианты **OAuth** при работе с удалёнными провайдерами без собственного сервера;
|
||||
- безопасная работа с **ключами** шифрования и **проверка корректности** ключа;
|
||||
- защита структуры данных: скрытие служебных файлов и каталогов, минимизация утечек через имена и пути;
|
||||
- предварительный план тестирования: **unit**, интеграционные и **UI**-сценарии.
|
||||
|
||||
### 2.9. Итоги первого этапа
|
||||
|
||||
Сформирована целостная **проектная база**: предметная область, архитектурная модель, структура данных, технологический стек, требования к безопасности. Материалы позволили перейти к **реализации функционального ядра** и расширению сценариев, включая синхронизацию зашифрованных данных с удалёнными провайдерами.
|
||||
|
||||
---
|
||||
|
||||
## 3. Результаты второго предварительного этапа (реализация и тестирование)
|
||||
|
||||
**Период выполнения этапа:** 29.03.2026–19.04.2026.
|
||||
|
||||
На втором этапе выполнена **реализация функционального ядра** Wallenc и ключевых пользовательских сценариев для локальных и удалённых хранилищ. Основной фокус — **перевод проектных решений первого этапа в рабочий код**: управление vault, шифрование, метаданные и ключевая информация, интерфейсы для повседневной работы пользователя.
|
||||
|
||||
Обеспечена **практическая готовность** базовой версии: операции создания, просмотра, переименования, удаления и защиты vault; инфраструктура для расширения удалённых сценариев. Модули работают **совместно** в одном приложении: пользователь создаёт хранилища, включает защиту, открывает и закрывает зашифрованные представления, видит состояние vault в интерфейсе, выполняет операции сопровождения. Сценарии реализованы в рамках **единой архитектуры**, выбранной на первом этапе.
|
||||
|
||||
### 3.1. Краткая характеристика выполненного этапа
|
||||
|
||||
За период **29.03.2026–19.04.2026** реализовано:
|
||||
|
||||
- рабочее ядро управления **локальными и удалёнными** vault;
|
||||
- пользовательские экраны и **диалоговые** сценарии основных операций;
|
||||
- авторизация в **Яндекс** как часть удалённого контура;
|
||||
- хранение метаданных и служебной информации в **Room**;
|
||||
- **unit**-тестирование криптографии и **ручное** тестирование экранов.
|
||||
|
||||
Этап завершён с заметным приростом прикладной готовности: архитектура первого этапа переведена в **работающее приложение** с подтверждённой функциональностью базового уровня.
|
||||
|
||||
### 3.2. Разработка модуля ядра приложения Wallenc
|
||||
|
||||
Реализованы возможности:
|
||||
|
||||
- создание и управление **локальными** vault;
|
||||
- хранение **метаданных** vault на устройстве;
|
||||
- включение **шифрования** выбранного vault с формированием параметров шифрования;
|
||||
- **открытие и закрытие** зашифрованного представления с проверкой корректности ключа;
|
||||
- доступ к содержимому через **слой абстракции** хранилищ;
|
||||
- **сокрытие** служебных объектов и **фильтрация** системных директорий при отображении данных пользователю.
|
||||
|
||||
Ядро реализовано в логике **domain / data / presentation**. Для предотвращения конфликтов при параллельных операциях используется **контроль запущенных задач** (в частности, защита от повторного запуска шифрования одного и того же vault до завершения предыдущей операции), что повышает устойчивость при активном взаимодействии пользователя с приложением.
|
||||
|
||||
**Иллюстрация — список локальных vault в приложении:**
|
||||
|
||||

|
||||
|
||||
### 3.3. Разработка мобильного приложения на Kotlin (Android)
|
||||
|
||||
В UI реализованы операции:
|
||||
|
||||
- отображение **списка** vault;
|
||||
- **переименование** и **удаление**;
|
||||
- **включение шифрования**;
|
||||
- **открытие** зашифрованного vault с использованием мастер-ключа;
|
||||
- **просмотр параметров** vault (состояние, служебные сведения, статус шифрования);
|
||||
- модуль работы с **содержимым** vault;
|
||||
- **блокировка/закрытие** vault.
|
||||
|
||||
Реализованы **диалоговые** сценарии подтверждения и настройки операций. Интерфейс построен так, чтобы пользователь видел текущее состояние (зашифровано/не зашифровано, открыто/закрыто) и выполнял действия **без** перехода на технические служебные экраны — это улучшает эргономику и снижает число лишних шагов.
|
||||
|
||||
**Иллюстрации — диалоги включения шифрования, открытия/закрытия зашифрованного vault, переименования и удаления:**
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 3.4. Модуль работы с небезопасными хранилищами через адаптеры
|
||||
|
||||
Реализован модуль **адаптерного** взаимодействия с типами хранилищ **без** собственного серверного контура приложения. Подход через адаптеры сохраняет **единый интерфейс** работы с vault и упрощает расширение списка провайдеров. Добавление нового типа внешнего провайдера **не требует** переработки пользовательской логики и **не нарушает** слоистую архитектуру.
|
||||
|
||||
### 3.5. Взаимодействие с внешними провайдерами через API/SDK
|
||||
|
||||
Реализована **авторизация через Яндекс** (OAuth) и интеграция пользовательского потока в приложение. Подготовлен слой **удалённых** vault и связанных сущностей для дальнейшего расширения удалённых операций. Реализованный контур обеспечивает сценарий **идентификации** пользователя во внешнем провайдере и формирует основу для расширения удалённой функциональности.
|
||||
|
||||
**Иллюстрации — экран удалённых vault и сценарий добавления с авторизацией через Яндекс:**
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
### 3.6. Хранение ключевой информации и метаданных (Room)
|
||||
|
||||
Реализован слой локальной БД для:
|
||||
|
||||
- таблиц сопоставления **ключевой информации** и идентификаторов хранилищ;
|
||||
- таблиц **метаданных** vault и состояния хранилищ;
|
||||
- данных, необходимых для **восстановления состояния** приложения.
|
||||
|
||||
Структура БД интегрирована в общий цикл работы приложения как **системный** слой управления состоянием. Room обеспечивает устойчивое хранение служебной информации между запусками и **централизованный** доступ через DAO.
|
||||
|
||||
**Иллюстрация — визуализация схемы Room (служебные сущности приложения):**
|
||||
|
||||

|
||||
|
||||
### 3.7. Тестирование приложения
|
||||
|
||||
Параллельно с ручной проверкой экранов велся учёт задач в трекере: на экране задач отображались этапы работ по проекту, а push-уведомление фиксировало обновление статусов — это упорядочивало регрессию сценариев и снижало риск пропустить проверку после изменений в коде.
|
||||
|
||||
**Иллюстрации — экран задач и уведомление о статусе задач:**
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
- **Unit-тесты** криптографических компонентов: проверка корректности шифрования/дешифрования и валидации ключа.
|
||||
- **Ручное** тестирование экранов и основных пользовательских сценариев работы с vault.
|
||||
|
||||
Проверки подтвердили **работоспособность** базового функционального ядра второго этапа.
|
||||
|
||||
### 3.8. Дополнительные работы по п. 2.2 ТЗ
|
||||
|
||||
**Синхронизация зашифрованных данных с удалёнными хранилищами без передачи ключей на сервер:** подготовлена безопасная база — поток авторизации через Яндекс; сущности и механизм учёта удалённых vault; сохранён принцип клиентской защиты (ключевая логика шифрования на стороне приложения). Это даёт архитектурную основу для дальнейшего развития синхронизации **без** передачи ключей на сторону внешних сервисов.
|
||||
|
||||
**Тестирование пользовательского интерфейса:** проверены отображение списков локальных и удалённых vault, корректность вызова и отработки диалогов, стабильность операций создания, переименования, удаления, шифрования, открытия и закрытия vault. Проверка велась на **последовательностях реальных пользовательских действий**, что позволило оценить связную работу функций в типовом использовании.
|
||||
|
||||
### 3.9. Итоги второго этапа
|
||||
|
||||
Сформировано и протестировано **работоспособное ядро** Wallenc: управление vault, шифрование, Room, UI для локальных и удалённых сценариев. Получен **функционирующий** Android-проект на Kotlin, готовый к дальнейшему расширению удалённой работы с хранилищами и синхронизационных механизмов. Этап можно считать **успешно завершённым**: ключевые задачи реализованы, протестированы и представлены в форме, пригодной для демонстрации и развития проекта.
|
||||
|
||||
---
|
||||
|
||||
## 4. Фрагменты реализованного кода и пояснения
|
||||
|
||||
### 4.1. Room-база приложения и состав сущностей
|
||||
|
||||
```kotlin
|
||||
@Database(
|
||||
entities = [DbStorageKeyMap::class, DbStorageMetaInfo::class, DbYandexAccount::class],
|
||||
version = 4,
|
||||
exportSchema = false,
|
||||
)
|
||||
abstract class AppDb : IAppDb, RoomDatabase() {
|
||||
abstract override val storageKeyMapDao: StorageKeyMapDao
|
||||
abstract override val storageMetaInfoDao: StorageMetaInfoDao
|
||||
abstract override val yandexAccountDao: YandexAccountDao
|
||||
}
|
||||
```
|
||||
|
||||
**Пояснение:** на этапе реализована целевая модель хранения метаданных и удалённой учётной информации в Room с разделением доступа через DAO.
|
||||
|
||||
### 4.2. Логика шифрования и открытия зашифрованного vault в ViewModel
|
||||
|
||||
```kotlin
|
||||
fun enableEncryption(storage: IStorageInfo, password: String, encryptPath: Boolean) {
|
||||
val key = EncryptKey(password)
|
||||
viewModelScope.launch {
|
||||
when (manageStoragesEncryptionUseCase.canEncrypt(storage)) {
|
||||
ManageStoragesEncryptionUseCase.CanEncryptResult.Allowed -> {
|
||||
manageStoragesEncryptionUseCase.enableEncryption(storage, key, encryptPath)
|
||||
manageStoragesEncryptionUseCase.openStorage(storage, key, true)
|
||||
_messages.emit("Encryption enabled")
|
||||
}
|
||||
ManageStoragesEncryptionUseCase.CanEncryptResult.AlreadyEncrypted ->
|
||||
_messages.emit("Storage is already encrypted")
|
||||
else -> _messages.emit("Unsupported operation")
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Пояснение:** включение шифрования — управляемый сценарий с валидацией состояния, запуском шифрования и последующим открытием зашифрованного представления.
|
||||
|
||||
### 4.3. UI-операции над удалёнными vault (фрагмент)
|
||||
|
||||
```kotlin
|
||||
FloatingActionButton(
|
||||
onClick = {
|
||||
if (!uiState.isBusy) viewModel.setAddChoiceVisible(true)
|
||||
},
|
||||
) {
|
||||
Icon(Icons.Filled.Add, contentDescription = stringResource(R.string.remote_vaults_add_cd))
|
||||
}
|
||||
...
|
||||
FilledTonalButton(
|
||||
onClick = {
|
||||
viewModel.setAddChoiceVisible(false)
|
||||
viewModel.yandexSignIn.launch { outcome ->
|
||||
when (outcome) {
|
||||
is RemoteYandexAuthResult.Success ->
|
||||
viewModel.onYandexAuthSuccess(outcome.accessToken)
|
||||
is RemoteYandexAuthResult.Failure -> { /* сообщение об ошибке */ }
|
||||
RemoteYandexAuthResult.Cancelled -> { }
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
**Пояснение:** отражён пользовательский путь запуска авторизации для удалённого провайдера из интерфейса приложения.
|
||||
|
||||
### 4.4. Unit-тесты криптографического контура
|
||||
|
||||
```kotlin
|
||||
@Test
|
||||
fun `test correct key for StorageEncryptionInfo`() {
|
||||
val encInfo = Encryptor.generateEncryptionInfo(key1)
|
||||
val res = Encryptor.checkKey(key = key1, encInfo = encInfo)
|
||||
assertEquals(true, res)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test incorrect key for StorageEncryptionInfo`() {
|
||||
val encInfo = Encryptor.generateEncryptionInfo(key1)
|
||||
val res = Encryptor.checkKey(key = key2, encInfo = encInfo)
|
||||
assertEquals(false, res)
|
||||
}
|
||||
```
|
||||
|
||||
**Пояснение:** тесты подтверждают корректную проверку ключа и работоспособность криптографической логики.
|
||||
|
||||
---
|
||||
|
||||
## 5. Дополнительные иллюстрации (диаграммы)
|
||||
|
||||
Скриншоты работы приложения, схема Room, экран задач и уведомление о статусах приведены **в соответствующих подразделах** раздела 3 приложения (пп. 3.2–3.7).
|
||||
|
||||
Ниже — **три диаграммы пользовательских потоков** для Wallenc в стиле отчёта по практике (аналог блок-схем из примера коллеги). На всех схемах отдельно показана **проектная** (ещё не реализованная в коде) механика **синхронизации**: в Room хранятся записи с **UUID** хранилищ (`storage`), подлежащих синхронизации; по **таймеру** (или периодическому Worker’у Android) запускается сервис синхронизации; у каждого **Storage** ведётся **история коммитов** (аналог git); сервис **находит отличия** между локальной и удалённой историей и **приводит зашифрованное содержимое** к одному состоянию **без передачи ключей** на сторону провайдера.
|
||||
|
||||
Исходники **PlantUML**: каталог `Отчёт практика/puml/` (`wallenc-01-start-and-sync.puml`, `wallenc-02-vault-lifecycle.puml`, `wallenc-03-navigation-hub.puml`). Растеризация: PNG сгенерированы командой
|
||||
`/usr/lib/jvm/java-21-openjdk/bin/java -jar plantuml.jar -tpng …`
|
||||
(системный вызов `plantuml` в среде может требовать Java 11+).
|
||||
|
||||
### 5.1. Старт приложения и параллельная синхронизация
|
||||
|
||||

|
||||
|
||||
### 5.2. Жизненный цикл vault и постановка в очередь синхронизации
|
||||
|
||||

|
||||
|
||||
### 5.3. Навигация с главного экрана и независимый SyncWorker
|
||||
|
||||

|
||||
|
||||
### 5.4. Диаграмма классов модуля `domain`
|
||||
|
||||
Сгенерирована из **скомпилированных** `.class` (Kotlin → JVM) плагином **code-atlas** (`./gradlew :domain:generateDiagrams`), исходник PlantUML: `puml/domain-classdiagram.puml`.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Заключение
|
||||
|
||||
По результатам **первого** этапа сформирована проектная база Wallenc: предметная область, обзор аналогов, ТЗ по ГОСТ 7.32–2017, архитектура MVVM + Clean Architecture, структура системы и локальной БД, технологический стек и дополнительные исследования по OAuth, ключам и тестированию.
|
||||
|
||||
По результатам **второго** этапа получено **работоспособное** ядро приложения на Kotlin/Android с Jetpack Compose, Hilt и Room, реализованы ключевые пользовательские сценарии, интеграция с Яндекс OAuth и контур тестирования (unit и ручной).
|
||||
|
||||
Итог практики соответствует целям направления и приказу о прохождении практики **с 09.02.2026 по 06.05.2026** и создаёт основу для дальнейшего развития синхронизации зашифрованных данных и поддержки дополнительных провайдеров хранения.
|
||||
|
||||
---
|
||||
|
||||
## Список условных обозначений и сокращений
|
||||
|
||||
| Обозначение | Расшифровка |
|
||||
| --- | --- |
|
||||
| API | Application Programming Interface |
|
||||
| DAO | Data Access Object |
|
||||
| E2E | End-to-end |
|
||||
| MVVM | Model — View — ViewModel |
|
||||
| OAuth | протокол авторизации |
|
||||
| UI | пользовательский интерфейс |
|
||||
| CRUD | создание, чтение, изменение, удаление |
|
||||
|
||||
---
|
||||
|
||||
## Использованные источники
|
||||
|
||||
Ниже приведены нормативные документы, официальная техническая документация и иные материалы, на которые опирались при подготовке отчёта и реализации проекта Wallenc. Для страниц в сети Интернет указана **дата обращения: 23.04.2026**.
|
||||
|
||||
1. ГОСТ 7.32—2017. Система стандартов по информации, библиотечному и издательскому делу. Отчёт о научно-исследовательской работе. Структура и правила оформления. — М.: Стандартинформ, 2017. — Публичная электронная копия официального издания для ознакомления [Электронный ресурс] // Томский государственный университет. — URL: https://tsu.ru/upload/medialibrary/235/gost_7.32_2017.pdf (дата обращения: 23.04.2026).
|
||||
2. Приказ ректора ЮФУ № 2191-к от 17.02.2026 г. о прохождении практики *(внутренний нормативный документ университета; текст — в распоряжении кафедры и обучающегося)*.
|
||||
3. Kotlin Documentation [Электронный ресурс] // JetBrains и Kotlin Foundation. — URL: https://kotlinlang.org/docs/home.html (дата обращения: 23.04.2026).
|
||||
4. Guide to app architecture [Электронный ресурс] // Android Developers. — URL: https://developer.android.com/topic/architecture (дата обращения: 23.04.2026).
|
||||
5. ViewModel overview [Электронный ресурс] // Android Developers. — URL: https://developer.android.com/topic/libraries/architecture/viewmodel (дата обращения: 23.04.2026).
|
||||
6. Get started with Jetpack Compose [Электронный ресурс] // Android Developers. — URL: https://developer.android.com/develop/ui/compose/documentation (дата обращения: 23.04.2026).
|
||||
7. Save data in a local database using Room [Электронный ресурс] // Android Developers. — URL: https://developer.android.com/training/data-storage/room (дата обращения: 23.04.2026).
|
||||
8. Hilt [Электронный ресурс] // Android Developers. — URL: https://developer.android.com/training/dependency-injection/hilt-android (дата обращения: 23.04.2026).
|
||||
9. Kotlin coroutines [Электронный ресурс] // Kotlin Documentation. — URL: https://kotlinlang.org/docs/coroutines-overview.html (дата обращения: 23.04.2026).
|
||||
10. Hardt D. The OAuth 2.0 Authorization Framework. RFC 6749 [Электронный ресурс] // IETF. — октябрь 2012. — URL: https://datatracker.ietf.org/doc/html/rfc6749 (дата обращения: 23.04.2026).
|
||||
11. OAuth для сервисов Яндекса [Электронный ресурс] // Яндекс ID для разработчиков. — URL: https://yandex.ru/dev/id/doc/ru/ (дата обращения: 23.04.2026).
|
||||
12. NIST FIPS 197. Advanced Encryption Standard (AES) [Электронный ресурс]. — URL: https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.197.pdf (дата обращения: 23.04.2026).
|
||||
13. NIST Special Publication 800-38A. Recommendation for Block Cipher Modes of Operation: Methods and Techniques [Электронный ресурс] // NIST CSRC. — URL: https://csrc.nist.gov/pubs/sp/800/38/a/final (дата обращения: 23.04.2026).
|
||||
14. PlantUML Language Reference Guide — Class diagram [Электронный ресурс]. — URL: https://plantuml.com/class-diagram (дата обращения: 23.04.2026).
|
||||
15. Plugin: io.github.euledge.code-atlas [Электронный ресурс] // Gradle Plugin Portal. — URL: https://plugins.gradle.org/plugin/io.github.euledge.code-atlas (дата обращения: 23.04.2026).
|
||||
16. euledge/code-atlas [Электронный ресурс] // GitHub. — URL: https://github.com/euledge/code-atlas (дата обращения: 23.04.2026).
|
||||
17. Use a PIN to lock your Safe folder in Files by Google [Электронный ресурс] // Google Help. — URL: https://support.google.com/files/answer/9935263 (дата обращения: 23.04.2026).
|
||||
18. Cryptomator Documentation [Электронный ресурс]. — URL: https://docs.cryptomator.org/en/latest/ (дата обращения: 23.04.2026).
|
||||
19. Bitwarden Help Center [Электронный ресурс]. — URL: https://bitwarden.com/help/ (дата обращения: 23.04.2026).
|
||||
20. Martin R. C. The Clean Architecture [Электронный ресурс] // blog.cleancoder.com. — 13.08.2012. — URL: https://blog.cleancoder.com/uncle-bob/2012/08/13/the-clean-architecture.html (дата обращения: 23.04.2026).
|
||||
|
||||
---
|
||||
|
||||
*Конец отчёта.*
|
||||
Reference in New Issue
Block a user