Refactor unlock and cloud authentication from UseCase to Activity

This commit is contained in:
Julian Raufelder 2021-03-09 13:47:04 +01:00
parent c8e9616894
commit 36fd5b2a8a
No known key found for this signature in database
GPG Key ID: 17EE71F6634E381D
27 changed files with 674 additions and 781 deletions

View File

@ -90,11 +90,14 @@ public class CryptoCloudFactory {
throw new CancellationException();
}
cryptoCloudContentRepositoryFactory.registerCryptor(impl.getVault(), cryptor);
return aCopyOf(token.getVault()) //
Vault vault = aCopyOf(token.getVault()) //
.withVersion(impl.getKeyFile().getVersion()) //
.withUnlocked(true) //
.build();
cryptoCloudContentRepositoryFactory.registerCryptor(vault, cryptor);
return vault;
}
public UnlockTokenImpl createUnlockToken(Vault vault) throws BackendException {

View File

@ -1,13 +0,0 @@
package org.cryptomator.domain.repository;
import org.cryptomator.domain.Cloud;
import org.cryptomator.domain.exception.BackendException;
public interface CloudAuthenticationService {
boolean isAuthenticated(Cloud cloud) throws BackendException;
boolean canAuthenticate(Cloud cloud);
Cloud updateAuthenticatedCloud(Cloud cloud);
}

View File

@ -97,10 +97,11 @@
</intent-filter>
</activity>
<activity android:name=".ui.activity.UnlockVaultActivity"
android:theme="@style/TransparentAlertDialogCustom"
android:label=""/>
<activity android:name=".ui.activity.EmptyDirIdFileInfoActivity" />
<!-- Settings -->
<activity android:name=".ui.activity.BiometricAuthSettingsActivity" />
<activity android:name=".ui.activity.CloudConnectionListActivity" />

View File

@ -21,6 +21,7 @@ import org.cryptomator.presentation.ui.activity.SettingsActivity;
import org.cryptomator.presentation.ui.activity.SharedFilesActivity;
import org.cryptomator.presentation.ui.activity.SplashActivity;
import org.cryptomator.presentation.ui.activity.TextEditorActivity;
import org.cryptomator.presentation.ui.activity.UnlockVaultActivity;
import org.cryptomator.presentation.ui.activity.VaultListActivity;
import org.cryptomator.presentation.ui.activity.WebDavAddOrChangeActivity;
import org.cryptomator.presentation.ui.fragment.AutoUploadChooseVaultFragment;
@ -34,6 +35,7 @@ import org.cryptomator.presentation.ui.fragment.ImagePreviewFragment;
import org.cryptomator.presentation.ui.fragment.SetPasswordFragment;
import org.cryptomator.presentation.ui.fragment.SharedFilesFragment;
import org.cryptomator.presentation.ui.fragment.TextEditorFragment;
import org.cryptomator.presentation.ui.fragment.UnlockVaultFragment;
import org.cryptomator.presentation.ui.fragment.VaultListFragment;
import org.cryptomator.presentation.ui.fragment.WebDavAddOrChangeFragment;
import org.cryptomator.presentation.workflow.AddExistingVaultWorkflow;
@ -114,4 +116,8 @@ public interface ActivityComponent {
void inject(AutoUploadChooseVaultFragment autoUploadChooseVaultFragment);
void inject(LicenseCheckActivity licenseCheckActivity);
void inject(UnlockVaultActivity unlockVaultActivity);
void inject(UnlockVaultFragment unlockVaultFragment);
}

View File

@ -0,0 +1,21 @@
package org.cryptomator.presentation.intent;
import org.cryptomator.generator.Intent;
import org.cryptomator.presentation.model.VaultModel;
import org.cryptomator.presentation.ui.activity.UnlockVaultActivity;
@Intent(UnlockVaultActivity.class)
public interface UnlockVaultIntent {
VaultModel vaultModel();
VaultAction vaultAction();
enum VaultAction {
UNLOCK,
UNLOCK_FOR_BIOMETRIC_AUTH,
ENCRYPT_PASSWORD,
CHANGE_PASSWORD
}
}

View File

@ -15,6 +15,8 @@ class VaultModel(private val vault: Vault) : Serializable {
get() = !vault.isUnlocked
val position: Int
get() = vault.position
val version: Int
get() = vault.version
fun toVault(): Vault {
return vault

View File

@ -7,17 +7,14 @@ import org.cryptomator.domain.di.PerView
import org.cryptomator.domain.usecases.GetDecryptedCloudForVaultUseCase
import org.cryptomator.domain.usecases.cloud.GetRootFolderUseCase
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase
import org.cryptomator.domain.usecases.vault.RemoveStoredVaultPasswordsUseCase
import org.cryptomator.domain.usecases.vault.UnlockVaultUseCase
import org.cryptomator.domain.usecases.vault.VaultOrUnlockToken
import org.cryptomator.generator.Callback
import org.cryptomator.presentation.R
import org.cryptomator.presentation.exception.ExceptionHandlers
import org.cryptomator.presentation.intent.ChooseCloudNodeSettings
import org.cryptomator.presentation.intent.Intents
import org.cryptomator.presentation.intent.UnlockVaultIntent
import org.cryptomator.presentation.model.CloudFolderModel
import org.cryptomator.presentation.model.CloudModel
import org.cryptomator.presentation.model.ProgressModel
import org.cryptomator.presentation.model.VaultModel
import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper
import org.cryptomator.presentation.ui.activity.view.AutoUploadChooseVaultView
@ -32,8 +29,6 @@ class AutoUploadChooseVaultPresenter @Inject constructor( //
private val getVaultListUseCase: GetVaultListUseCase, //
private val getRootFolderUseCase: GetRootFolderUseCase, //
private val getDecryptedCloudForVaultUseCase: GetDecryptedCloudForVaultUseCase, //
private val unlockVaultUseCase: UnlockVaultUseCase, //
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase, //
private val cloudFolderModelMapper: CloudFolderModelMapper, //
private val sharedPreferencesHandler: SharedPreferencesHandler, //
private val authenticationExceptionHandler: AuthenticationExceptionHandler, //
@ -83,11 +78,24 @@ class AutoUploadChooseVaultPresenter @Inject constructor( //
decryptedCloudFor(authenticatedVault)
} else {
if (!isPaused) {
view?.showEnterPasswordDialog(VaultModel(authenticatedVault))
requestActivityResult( //
ActivityResultCallbacks.vaultUnlockedAutoUpload(), //
Intents.unlockVaultIntent().withVaultModel(VaultModel(authenticatedVault)).withVaultAction(UnlockVaultIntent.VaultAction.UNLOCK))
}
}
}
@Callback
fun vaultUnlockedAutoUpload(result: ActivityResult) {
val cloud = result.intent().getSerializableExtra(SINGLE_RESULT) as Cloud
when {
result.isResultOk -> rootFolderFor(cloud)
else -> TODO("Not yet implemented")
}
}
private fun decryptedCloudFor(vault: Vault) {
getDecryptedCloudForVaultUseCase //
.withVault(vault) //
@ -151,49 +159,11 @@ class AutoUploadChooseVaultPresenter @Inject constructor( //
location?.let { view?.showChosenLocation(it) }
}
fun onUnlockCanceled() {
unlockVaultUseCase.cancel()
}
fun onUnlockPressed(vaultModel: VaultModel, password: String?) {
view?.showProgress(ProgressModel.GENERIC)
unlockVaultUseCase //
.withVaultOrUnlockToken(VaultOrUnlockToken.from(vaultModel.toVault())) //
.andPassword(password) //
.run(object : DefaultResultHandler<Cloud>() {
override fun onSuccess(cloud: Cloud) {
view?.showProgress(ProgressModel.COMPLETED)
rootFolderFor(cloud)
}
override fun onError(e: Throwable) {
if (!authenticationExceptionHandler.handleAuthenticationException( //
this@AutoUploadChooseVaultPresenter, //
e, //
ActivityResultCallbacks.unlockVaultAfterAuth(vaultModel.toVault(), password))) {
showError(e)
}
}
})
}
fun onBiometricKeyInvalidated(vaultModel: VaultModel?) {
removeStoredVaultPasswordsUseCase.run(object : DefaultResultHandler<Void?>() {
override fun onSuccess(void: Void?) {
view?.showBiometricAuthKeyInvalidatedDialog()
}
})
}
fun useConfirmationInFaceUnlockBiometricAuthentication(): Boolean {
return sharedPreferencesHandler.useConfirmationInFaceUnlockBiometricAuthentication()
}
enum class AuthenticationState {
CHOOSE_LOCATION, INIT_ROOT
}
init {
unsubscribeOnDestroy(getVaultListUseCase)
unsubscribeOnDestroy(getVaultListUseCase, getRootFolderUseCase, getDecryptedCloudForVaultUseCase)
}
}

View File

@ -2,21 +2,21 @@ package org.cryptomator.presentation.presenter
import android.content.Intent
import android.provider.Settings
import org.cryptomator.cryptolib.api.InvalidPassphraseException
import org.cryptomator.domain.Cloud
import org.cryptomator.domain.Vault
import org.cryptomator.domain.di.PerView
import org.cryptomator.domain.usecases.vault.*
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase
import org.cryptomator.domain.usecases.vault.LockVaultUseCase
import org.cryptomator.domain.usecases.vault.SaveVaultUseCase
import org.cryptomator.generator.Callback
import org.cryptomator.presentation.exception.ExceptionHandlers
import org.cryptomator.presentation.model.CloudModel
import org.cryptomator.presentation.model.ProgressModel
import org.cryptomator.presentation.intent.Intents
import org.cryptomator.presentation.intent.UnlockVaultIntent
import org.cryptomator.presentation.model.VaultModel
import org.cryptomator.presentation.ui.activity.view.BiometricAuthSettingsView
import org.cryptomator.presentation.workflow.ActivityResult
import org.cryptomator.presentation.workflow.AuthenticationExceptionHandler
import org.cryptomator.util.SharedPreferencesHandler
import java.util.*
import java.util.ArrayList
import javax.inject.Inject
import timber.log.Timber
@ -24,13 +24,9 @@ import timber.log.Timber
class BiometricAuthSettingsPresenter @Inject constructor( //
private val getVaultListUseCase: GetVaultListUseCase, //
private val saveVaultUseCase: SaveVaultUseCase, //
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase, //
private val checkVaultPasswordUseCase: CheckVaultPasswordUseCase, //
private val unlockVaultUseCase: UnlockVaultUseCase, //
private val lockVaultUseCase: LockVaultUseCase, //
exceptionMappings: ExceptionHandlers, //
private val sharedPreferencesHandler: SharedPreferencesHandler, //
private val authenticationExceptionHandler: AuthenticationExceptionHandler) : Presenter<BiometricAuthSettingsView>(exceptionMappings) {
private val sharedPreferencesHandler: SharedPreferencesHandler) : Presenter<BiometricAuthSettingsView>(exceptionMappings) {
fun loadVaultList() {
updateVaultListView()
@ -49,92 +45,56 @@ class BiometricAuthSettingsPresenter @Inject constructor( //
fun updateVaultEntityWithChangedBiometricAuthSettings(vaultModel: VaultModel, useBiometricAuth: Boolean) {
if (useBiometricAuth) {
view?.showEnterPasswordDialog(VaultModel(vaultModel.toVault()))
verifyPassword(vaultModel)
} else {
removePasswordAndSave(vaultModel.toVault())
}
}
fun verifyPassword(vaultModel: VaultModel) {
private fun verifyPassword(vaultModel: VaultModel) {
Timber.tag("BiomtricAuthSettngsPres").i("Checking entered vault password")
if (vaultModel.isLocked) {
unlockVault(vaultModel)
requestActivityResult( //
ActivityResultCallbacks.vaultUnlockedBiometricAuthPres(vaultModel), //
Intents.unlockVaultIntent().withVaultModel(vaultModel).withVaultAction(UnlockVaultIntent.VaultAction.UNLOCK_FOR_BIOMETRIC_AUTH))
} else {
checkPassword(vaultModel)
lockVaultUseCase
.withVault(vaultModel.toVault())
.run(object : DefaultResultHandler<Vault>() {
override fun onSuccess(vault: Vault) {
super.onSuccess(vault)
requestActivityResult( //
ActivityResultCallbacks.vaultUnlockedBiometricAuthPres(vaultModel), //
Intents.unlockVaultIntent().withVaultModel(vaultModel).withVaultAction(UnlockVaultIntent.VaultAction.UNLOCK_FOR_BIOMETRIC_AUTH))
}
})
}
}
private fun checkPassword(vaultModel: VaultModel) {
view?.showProgress(ProgressModel.GENERIC)
checkVaultPasswordUseCase //
.withVault(vaultModel.toVault()) //
.andPassword(vaultModel.password) //
.run(object : DefaultResultHandler<Boolean>() {
override fun onSuccess(passwordCorrect: Boolean) {
if (passwordCorrect) {
Timber.tag("BiomtricAuthSettngsPres").i("Password is correct")
onPasswordCheckSucceeded(vaultModel)
} else {
Timber.tag("BiomtricAuthSettngsPres").i("Password is wrong")
showError(InvalidPassphraseException())
}
}
override fun onError(e: Throwable) {
super.onError(e)
Timber.tag("BiomtricAuthSettngsPres").e(e, "Password check failed")
}
})
}
private fun unlockVault(vaultModel: VaultModel) {
view?.showProgress(ProgressModel.GENERIC)
unlockVaultUseCase //
.withVaultOrUnlockToken(VaultOrUnlockToken.from(vaultModel.toVault())) //
.andPassword(vaultModel.password) //
.run(object : DefaultResultHandler<Cloud>() {
override fun onSuccess(cloud: Cloud) {
Timber.tag("BiomtricAuthSettngsPres").i("Password is correct")
onUnlockSucceeded(vaultModel)
}
override fun onError(e: Throwable) {
if (!authenticationExceptionHandler.handleAuthenticationException(this@BiometricAuthSettingsPresenter, e, ActivityResultCallbacks.unlockVaultAfterAuth(vaultModel.toVault()))) {
showError(e)
Timber.tag("BiomtricAuthSettngsPres").e(e, "Password check failed")
}
}
})
}
private fun onUnlockSucceeded(vaultModel: VaultModel) {
lockVaultUseCase
.withVault(vaultModel.toVault())
.run(object : DefaultResultHandler<Vault>() {
override fun onSuccess(vault: Vault) {
super.onSuccess(vault)
onPasswordCheckSucceeded(vaultModel)
}
override fun onError(e: Throwable) {
Timber.tag("BiomtricAuthSettngsPres").e(e, "Locking vault after unlocking failed but continue to save changes")
onPasswordCheckSucceeded(vaultModel)
}
})
@Callback
fun vaultUnlockedBiometricAuthPres(result: ActivityResult, vaultModel: VaultModel) {
val cloud = result.intent().getSerializableExtra(SINGLE_RESULT) as Cloud
val password = result.intent().getStringExtra(UnlockVaultPresenter.PASSWORD)
val vault = Vault.aCopyOf(vaultModel.toVault()).withCloud(cloud).withSavedPassword(password).build()
when {
result.isResultOk -> requestActivityResult( //
ActivityResultCallbacks.encryptVaultPassword(vaultModel), //
Intents.unlockVaultIntent().withVaultModel(VaultModel(vault)).withVaultAction(UnlockVaultIntent.VaultAction.ENCRYPT_PASSWORD))
else -> TODO("Not yet implemented")
}
}
@Callback
fun unlockVaultAfterAuth(result: ActivityResult, vault: Vault?) {
val cloud = result.getSingleResult(CloudModel::class.java).toCloud()
val vaultWithUpdatedCloud = Vault.aCopyOf(vault).withCloud(cloud).build()
unlockVault(VaultModel(vaultWithUpdatedCloud))
fun encryptVaultPassword(result: ActivityResult, vaultModel: VaultModel) {
val tmpVault = result.intent().getSerializableExtra(SINGLE_RESULT) as VaultModel
val vault = Vault.aCopyOf(vaultModel.toVault()).withSavedPassword(tmpVault.password).build()
when {
result.isResultOk -> saveVault(vault)
else -> TODO("Not yet implemented")
}
}
private fun onPasswordCheckSucceeded(vaultModel: VaultModel) {
view?.showBiometricAuthenticationDialog(vaultModel)
}
fun saveVault(vault: Vault?) {
private fun saveVault(vault: Vault?) {
saveVaultUseCase //
.withVault(vault) //
.run(object : ProgressCompletingResultHandler<Vault>() {
@ -145,8 +105,7 @@ class BiometricAuthSettingsPresenter @Inject constructor( //
}
fun switchedGeneralBiometricAuthSettings(isChecked: Boolean) {
sharedPreferencesHandler //
.changeUseBiometricAuthentication(isChecked)
sharedPreferencesHandler.changeUseBiometricAuthentication(isChecked)
if (isChecked) {
loadVaultList()
} else {
@ -173,32 +132,15 @@ class BiometricAuthSettingsPresenter @Inject constructor( //
fun onSetupBiometricAuthInSystemClicked() {
val openSecuritySettings = Intent(Settings.ACTION_SECURITY_SETTINGS)
requestActivityResult(ActivityResultCallbacks.onSetupFingerCompleted(), openSecuritySettings)
requestActivityResult(ActivityResultCallbacks.onSetupBiometricAuthInSystemCompleted(), openSecuritySettings)
}
@Callback
fun onSetupFingerCompleted(result: ActivityResult?) {
fun onSetupBiometricAuthInSystemCompleted(result: ActivityResult?) {
view?.showSetupBiometricAuthDialog()
}
fun onBiometricAuthKeyInvalidated(vaultModel: VaultModel?) {
removeStoredVaultPasswordsUseCase.run(object : DefaultResultHandler<Void?>() {
override fun onSuccess(void: Void?) {
view?.showBiometricAuthKeyInvalidatedDialog()
}
})
}
fun onUnlockCanceled() {
unlockVaultUseCase.cancel()
loadVaultList()
}
fun useConfirmationInFaceUnlockBiometricAuthentication(): Boolean {
return sharedPreferencesHandler.useConfirmationInFaceUnlockBiometricAuthentication()
}
init {
unsubscribeOnDestroy(getVaultListUseCase, saveVaultUseCase, checkVaultPasswordUseCase, removeStoredVaultPasswordsUseCase, unlockVaultUseCase)
unsubscribeOnDestroy(getVaultListUseCase, saveVaultUseCase, lockVaultUseCase)
}
}

View File

@ -8,15 +8,13 @@ import org.cryptomator.domain.di.PerView
import org.cryptomator.domain.usecases.GetDecryptedCloudForVaultUseCase
import org.cryptomator.domain.usecases.cloud.*
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase
import org.cryptomator.domain.usecases.vault.RemoveStoredVaultPasswordsUseCase
import org.cryptomator.domain.usecases.vault.UnlockVaultUseCase
import org.cryptomator.domain.usecases.vault.VaultOrUnlockToken
import org.cryptomator.generator.Callback
import org.cryptomator.generator.InstanceState
import org.cryptomator.presentation.R
import org.cryptomator.presentation.exception.ExceptionHandlers
import org.cryptomator.presentation.intent.ChooseCloudNodeSettings
import org.cryptomator.presentation.intent.Intents
import org.cryptomator.presentation.intent.UnlockVaultIntent
import org.cryptomator.presentation.model.*
import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper
import org.cryptomator.presentation.model.mappers.ProgressModelMapper
@ -27,7 +25,6 @@ import org.cryptomator.presentation.workflow.ActivityResult
import org.cryptomator.presentation.workflow.AuthenticationExceptionHandler
import org.cryptomator.presentation.workflow.PermissionsResult
import org.cryptomator.util.Optional
import org.cryptomator.util.SharedPreferencesHandler
import org.cryptomator.util.file.FileCacheUtils
import java.util.*
import javax.inject.Inject
@ -36,14 +33,11 @@ import timber.log.Timber
@PerView
class SharedFilesPresenter @Inject constructor( //
private val getVaultListUseCase: GetVaultListUseCase, //
private val unlockVaultUseCase: UnlockVaultUseCase, //
private val getRootFolderUseCase: GetRootFolderUseCase, //
private val getDecryptedCloudForVaultUseCase: GetDecryptedCloudForVaultUseCase, //
private val uploadFilesUseCase: UploadFilesUseCase, //
private val getCloudListUseCase: GetCloudListUseCase, //
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase, //
private val contentResolverUtil: ContentResolverUtil, //
private val sharedPreferencesHandler: SharedPreferencesHandler, //
private val fileCacheUtils: FileCacheUtils, //
private val authenticationExceptionHandler: AuthenticationExceptionHandler, //
private val cloudFolderModelMapper: CloudFolderModelMapper, //
@ -128,6 +122,28 @@ class SharedFilesPresenter @Inject constructor( //
vaultModel?.let { onCloudOfVaultAuthenticated(it.toVault()) }
}
private fun onCloudOfVaultAuthenticated(authenticatedVault: Vault) {
if (authenticatedVault.isUnlocked) {
decryptedCloudFor(authenticatedVault)
} else {
if (!isPaused) {
requestActivityResult( //
ActivityResultCallbacks.vaultUnlockedSharedFiles(), //
Intents.unlockVaultIntent().withVaultModel(VaultModel(authenticatedVault)).withVaultAction(UnlockVaultIntent.VaultAction.UNLOCK))
}
}
}
@Callback
fun vaultUnlockedSharedFiles(result: ActivityResult) {
val cloud = result.intent().getSerializableExtra(SINGLE_RESULT) as Cloud
when {
result.isResultOk -> rootFolderFor(cloud)
else -> TODO("Not yet implemented")
}
}
private fun decryptedCloudFor(vault: Vault) {
getDecryptedCloudForVaultUseCase //
.withVault(vault) //
@ -172,32 +188,6 @@ class SharedFilesPresenter @Inject constructor( //
}
}
fun onUnlockPressed(vaultModel: VaultModel, password: String?) {
view?.showProgress(ProgressModel.GENERIC)
unlockVaultUseCase //
.withVaultOrUnlockToken(VaultOrUnlockToken.from(vaultModel.toVault())) //
.andPassword(password) //
.run(object : DefaultResultHandler<Cloud>() {
override fun onSuccess(cloud: Cloud) {
view?.showProgress(ProgressModel.COMPLETED)
rootFolderFor(cloud)
}
override fun onError(e: Throwable) {
if (!authenticationExceptionHandler.handleAuthenticationException(this@SharedFilesPresenter, e, ActivityResultCallbacks.unlockVaultAfterAuth(vaultModel.toVault(), password))) {
showError(e)
}
}
})
}
@Callback
fun unlockVaultAfterAuth(result: ActivityResult, vault: Vault?, password: String?) {
val cloud = result.getSingleResult(CloudModel::class.java).toCloud()
val vaultWithUpdatedCloud = Vault.aCopyOf(vault).withCloud(cloud).build()
onUnlockPressed(VaultModel(vaultWithUpdatedCloud), password)
}
private fun setLocation(location: CloudFolderModel) {
this.location = location
}
@ -372,16 +362,6 @@ class SharedFilesPresenter @Inject constructor( //
}
}
private fun onCloudOfVaultAuthenticated(authenticatedVault: Vault) {
if (authenticatedVault.isUnlocked) {
decryptedCloudFor(authenticatedVault)
} else {
if (!isPaused) {
view?.showEnterPasswordDialog(VaultModel(authenticatedVault))
}
}
}
fun onChooseLocationPressed() {
authenticate(selectedVault)
}
@ -410,25 +390,6 @@ class SharedFilesPresenter @Inject constructor( //
view?.closeDialog()
}
fun onBiometricAuthKeyInvalidated() {
removeStoredVaultPasswordsUseCase.run(object : DefaultResultHandler<Void?>() {
override fun onFinished() {
view?.showBiometricAuthKeyInvalidatedDialog()
}
})
selectedVault?.let {
selectedVault = VaultModel(Vault.aCopyOf(it.toVault()).withSavedPassword(null).build())
}
}
fun onUnlockCanceled() {
unlockVaultUseCase.cancel()
}
fun useConfirmationInFaceUnlockBiometricAuthentication(): Boolean {
return sharedPreferencesHandler.useConfirmationInFaceUnlockBiometricAuthentication()
}
private enum class AuthenticationState {
CHOOSE_LOCATION, INIT_ROOT
}
@ -444,11 +405,9 @@ class SharedFilesPresenter @Inject constructor( //
init {
unsubscribeOnDestroy( //
getRootFolderUseCase, //
unlockVaultUseCase, //
getVaultListUseCase, //
getDecryptedCloudForVaultUseCase, //
uploadFilesUseCase, //
getCloudListUseCase, //
removeStoredVaultPasswordsUseCase)
getCloudListUseCase)
}
}

View File

@ -0,0 +1,357 @@
package org.cryptomator.presentation.presenter
import android.os.Handler
import androidx.biometric.BiometricManager
import org.cryptomator.domain.Cloud
import org.cryptomator.domain.Vault
import org.cryptomator.domain.di.PerView
import org.cryptomator.domain.exception.NetworkConnectionException
import org.cryptomator.domain.exception.authentication.AuthenticationException
import org.cryptomator.domain.usecases.vault.ChangePasswordUseCase
import org.cryptomator.domain.usecases.vault.LockVaultUseCase
import org.cryptomator.domain.usecases.vault.PrepareUnlockUseCase
import org.cryptomator.domain.usecases.vault.RemoveStoredVaultPasswordsUseCase
import org.cryptomator.domain.usecases.vault.SaveVaultUseCase
import org.cryptomator.domain.usecases.vault.UnlockToken
import org.cryptomator.domain.usecases.vault.UnlockVaultUseCase
import org.cryptomator.domain.usecases.vault.VaultOrUnlockToken
import org.cryptomator.generator.Callback
import org.cryptomator.generator.InjectIntent
import org.cryptomator.presentation.R
import org.cryptomator.presentation.exception.ExceptionHandlers
import org.cryptomator.presentation.intent.UnlockVaultIntent
import org.cryptomator.presentation.model.CloudModel
import org.cryptomator.presentation.model.ProgressModel
import org.cryptomator.presentation.model.ProgressStateModel
import org.cryptomator.presentation.model.VaultModel
import org.cryptomator.presentation.ui.activity.view.UnlockVaultView
import org.cryptomator.presentation.workflow.ActivityResult
import org.cryptomator.presentation.workflow.AuthenticationExceptionHandler
import org.cryptomator.util.SharedPreferencesHandler
import java.io.Serializable
import javax.inject.Inject
import timber.log.Timber
@PerView
class UnlockVaultPresenter @Inject constructor(
private val changePasswordUseCase: ChangePasswordUseCase,
private val lockVaultUseCase: LockVaultUseCase,
private val unlockVaultUseCase: UnlockVaultUseCase,
private val prepareUnlockUseCase: PrepareUnlockUseCase,
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase,
private val saveVaultUseCase: SaveVaultUseCase,
private val authenticationExceptionHandler: AuthenticationExceptionHandler,
private val sharedPreferencesHandler: SharedPreferencesHandler,
exceptionMappings: ExceptionHandlers) : Presenter<UnlockVaultView>(exceptionMappings) {
private var startedUsingPrepareUnlock = false
private var retryUnlockHandler: Handler? = null
private var pendingUnlock: PendingUnlock? = null
@InjectIntent
lateinit var intent: UnlockVaultIntent
@Volatile
private var running: Boolean = false
override fun destroyed() {
super.destroyed()
if (retryUnlockHandler != null) {
running = false
retryUnlockHandler?.removeCallbacks(null)
}
}
fun setup() {
when (intent.vaultAction()) {
UnlockVaultIntent.VaultAction.ENCRYPT_PASSWORD -> view?.getEncryptedPasswordWithBiometricAuthentication(intent.vaultModel())
UnlockVaultIntent.VaultAction.UNLOCK, UnlockVaultIntent.VaultAction.UNLOCK_FOR_BIOMETRIC_AUTH -> unlockVault(intent.vaultModel())
UnlockVaultIntent.VaultAction.CHANGE_PASSWORD -> view?.showChangePasswordDialog(intent.vaultModel())
else -> TODO("Not yet implemented")
}
}
private fun unlockVault(vaultModel: VaultModel) {
if (canUseBiometricOn(vaultModel)) {
if (startedUsingPrepareUnlock) {
startPrepareUnlockUseCase(vaultModel.toVault())
}
view?.showBiometricDialog(vaultModel)
} else {
startPrepareUnlockUseCase(vaultModel.toVault())
view?.showEnterPasswordDialog(vaultModel)
}
}
fun onWindowFocusChanged(hasFocus: Boolean) {
if (hasFocus) {
if (retryUnlockHandler != null) {
running = false
retryUnlockHandler?.removeCallbacks(null)
}
}
}
private fun pendingUnlockFor(vault: Vault): PendingUnlock? {
if (pendingUnlock == null) {
pendingUnlock = PendingUnlock(vault)
}
return if (pendingUnlock?.belongsTo(vault) == true) {
pendingUnlock
} else {
PendingUnlock.NO_OP_PENDING_UNLOCK
}
}
private fun canUseBiometricOn(vault: VaultModel): Boolean {
return vault.password != null && BiometricManager.from(context()).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS
}
fun onUnlockCanceled() {
prepareUnlockUseCase.unsubscribe()
unlockVaultUseCase.cancel()
finish()
}
fun startPrepareUnlockUseCase(vault: Vault) {
pendingUnlock = null
prepareUnlockUseCase //
.withVault(vault) //
.run(object : DefaultResultHandler<UnlockToken>() {
override fun onSuccess(unlockToken: UnlockToken) {
if (!startedUsingPrepareUnlock && vault.password != null) {
doUnlock(unlockToken, vault.password)
} else {
unlockTokenObtained(unlockToken)
}
}
override fun onError(e: Throwable) {
if (e is AuthenticationException) {
view?.cancelBasicAuthIfRunning()
}
if (!authenticationExceptionHandler.handleAuthenticationException(this@UnlockVaultPresenter, e, ActivityResultCallbacks.authenticatedAfterUnlock(vault))) {
super.onError(e)
if (e is NetworkConnectionException) {
running = true
retryUnlockHandler = Handler()
restartUnlockUseCase(vault)
}
}
}
})
}
@Callback(dispatchResultOkOnly = false)
fun authenticatedAfterUnlock(result: ActivityResult, vault: Vault) {
if (result.isResultOk) {
val cloud = result.getSingleResult(CloudModel::class.java).toCloud()
if (startedUsingPrepareUnlock) {
startPrepareUnlockUseCase(Vault.aCopyOf(vault).withCloud(cloud).build())
if (view?.stoppedBiometricAuthDuringCloudAuthentication() == true) {
view?.showBiometricDialog(VaultModel(vault))
}
} else {
view?.showProgress(ProgressModel.GENERIC)
startPrepareUnlockUseCase(vault)
}
} else {
view?.closeDialog()
val error = result.getSingleResult(Throwable::class.java)
error?.let { showError(it) }
}
}
private fun restartUnlockUseCase(vault: Vault) {
retryUnlockHandler?.postDelayed({
if (running) {
prepareUnlockUseCase //
.withVault(vault) //
.run(object : DefaultResultHandler<UnlockToken>() {
override fun onSuccess(unlockToken: UnlockToken) {
if (!startedUsingPrepareUnlock && vault.password != null) {
doUnlock(unlockToken, vault.password)
} else {
unlockTokenObtained(unlockToken)
}
}
override fun onError(e: Throwable) {
if (e is NetworkConnectionException) {
restartUnlockUseCase(vault)
}
}
})
}
}, 1000)
}
private fun unlockTokenObtained(unlockToken: UnlockToken) {
pendingUnlockFor(unlockToken.vault)?.setUnlockToken(unlockToken, this)
}
fun onUnlockClick(vault: VaultModel, password: String?) {
view?.showProgress(ProgressModel.GENERIC)
pendingUnlockFor(vault.toVault())?.setPassword(password, this)
}
private fun doUnlock(token: UnlockToken, password: String) {
unlockVaultUseCase //
.withVaultOrUnlockToken(VaultOrUnlockToken.from(token)) //
.andPassword(password) //
.run(object : DefaultResultHandler<Cloud>() {
override fun onSuccess(cloud: Cloud) {
when (intent.vaultAction()) {
UnlockVaultIntent.VaultAction.ENCRYPT_PASSWORD, UnlockVaultIntent.VaultAction.UNLOCK_FOR_BIOMETRIC_AUTH -> {
handleUnlockVaultSuccess(token.vault, cloud, password)
}
UnlockVaultIntent.VaultAction.UNLOCK -> finishWithResult(cloud)
else -> TODO("Not yet implemented")
}
}
})
}
private fun handleUnlockVaultSuccess(vault: Vault, cloud: Cloud, password: String) {
lockVaultUseCase.withVault(vault).run(object : DefaultResultHandler<Vault>() {
override fun onFinished() {
finishWithResultAndExtra(cloud, PASSWORD, password)
}
})
}
fun startedUsingPrepareUnlock(): Boolean {
return startedUsingPrepareUnlock
}
fun useConfirmationInFaceUnlockBiometricAuthentication(): Boolean {
return sharedPreferencesHandler.useConfirmationInFaceUnlockBiometricAuthentication()
}
fun onBiometricKeyInvalidated() {
removeStoredVaultPasswordsUseCase.run(object : DefaultResultHandler<Void?>() {
override fun onSuccess(void: Void?) {
view?.showBiometricAuthKeyInvalidatedDialog()
}
override fun onError(e: Throwable) {
Timber.tag("VaultListPresenter").e(e, "Error while removing vault passwords")
}
})
}
fun onBiometricAuthenticationSucceeded(vaultModel: VaultModel) {
when (intent.vaultAction()) {
UnlockVaultIntent.VaultAction.ENCRYPT_PASSWORD -> finishWithResult(vaultModel)
UnlockVaultIntent.VaultAction.UNLOCK, UnlockVaultIntent.VaultAction.UNLOCK_FOR_BIOMETRIC_AUTH -> {
if (startedUsingPrepareUnlock) {
onUnlockClick(vaultModel, vaultModel.password)
} else {
view?.showProgress(ProgressModel.GENERIC)
startPrepareUnlockUseCase(vaultModel.toVault())
}
}
UnlockVaultIntent.VaultAction.CHANGE_PASSWORD -> {
saveVaultUseCase //
.withVault(vaultModel.toVault()) //
.run(object : DefaultResultHandler<Vault>() {
override fun onSuccess(vault: Vault) {
finishWithResult(vaultModel)
}
})
}
else -> TODO("Not yet implemented")
}
}
fun onChangePasswordClick(vaultModel: VaultModel, oldPassword: String, newPassword: String) {
view?.showProgress(ProgressModel(ProgressStateModel.CHANGING_PASSWORD))
changePasswordUseCase.withVault(vaultModel.toVault()) //
.andOldPassword(oldPassword) //
.andNewPassword(newPassword) //
.run(object : DefaultResultHandler<Void?>() {
override fun onSuccess(void: Void?) {
view?.showProgress(ProgressModel.COMPLETED)
view?.showMessage(R.string.screen_vault_list_change_password_successful)
if (canUseBiometricOn(vaultModel)) {
view?.getEncryptedPasswordWithBiometricAuthentication(VaultModel( //
Vault.aCopyOf(vaultModel.toVault()) //
.withSavedPassword(newPassword) //
.build()))
} else {
finishWithResult(vaultModel)
}
}
override fun onError(e: Throwable) {
if (!authenticationExceptionHandler.handleAuthenticationException( //
this@UnlockVaultPresenter, e, //
ActivityResultCallbacks.changePasswordAfterAuthentication(vaultModel.toVault(), oldPassword, newPassword))) {
showError(e)
}
}
})
}
@Callback
fun changePasswordAfterAuthentication(result: ActivityResult, vault: Vault, oldPassword: String, newPassword: String) {
val cloud = result.getSingleResult(CloudModel::class.java).toCloud()
val vaultWithUpdatedCloud = Vault.aCopyOf(vault).withCloud(cloud).build()
onChangePasswordClick(VaultModel(vaultWithUpdatedCloud), oldPassword, newPassword)
}
fun saveVaultAfterChangePasswordButFailedBiometricAuth(vault: Vault) {
Timber.tag("UnlockVaultPresenter").e("Save vault without password because biometric auth failed after changing vault password")
saveVaultUseCase //
.withVault(vault) //
.run(object : DefaultResultHandler<Vault>() {
override fun onSuccess(vault: Vault) {
finishWithResult(vault)
}
})
}
private open class PendingUnlock(private val vault: Vault?) : Serializable {
private var unlockToken: UnlockToken? = null
private var password: String? = null
fun setUnlockToken(unlockToken: UnlockToken?, presenter: UnlockVaultPresenter) {
this.unlockToken = unlockToken
continueIfComplete(presenter)
}
fun setPassword(password: String?, presenter: UnlockVaultPresenter) {
this.password = password
continueIfComplete(presenter)
}
open fun continueIfComplete(presenter: UnlockVaultPresenter) {
unlockToken?.let { token -> password?.let { password -> presenter.doUnlock(token, password) } }
}
fun belongsTo(vault: Vault): Boolean {
return vault == this.vault
}
companion object {
val NO_OP_PENDING_UNLOCK: PendingUnlock = object : PendingUnlock(null) {
override fun continueIfComplete(presenter: UnlockVaultPresenter) {
// empty
}
}
}
}
companion object {
const val PASSWORD = "password"
}
init {
unsubscribeOnDestroy(changePasswordUseCase, lockVaultUseCase, unlockVaultUseCase, prepareUnlockUseCase, removeStoredVaultPasswordsUseCase, saveVaultUseCase)
}
}

View File

@ -6,16 +6,12 @@ import android.content.ActivityNotFoundException
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.os.Handler
import androidx.biometric.BiometricManager
import org.cryptomator.data.cloud.crypto.CryptoCloud
import org.cryptomator.data.util.NetworkConnectionCheck
import org.cryptomator.domain.Cloud
import org.cryptomator.domain.CloudFolder
import org.cryptomator.domain.Vault
import org.cryptomator.domain.di.PerView
import org.cryptomator.domain.exception.NetworkConnectionException
import org.cryptomator.domain.exception.authentication.AuthenticationException
import org.cryptomator.domain.exception.license.LicenseNotValidException
import org.cryptomator.domain.exception.update.SSLHandshakePreAndroid5UpdateCheckException
import org.cryptomator.domain.usecases.DoLicenseCheckUseCase
@ -26,27 +22,21 @@ import org.cryptomator.domain.usecases.LicenseCheck
import org.cryptomator.domain.usecases.NoOpResultHandler
import org.cryptomator.domain.usecases.UpdateCheck
import org.cryptomator.domain.usecases.cloud.GetRootFolderUseCase
import org.cryptomator.domain.usecases.vault.ChangePasswordUseCase
import org.cryptomator.domain.usecases.vault.DeleteVaultUseCase
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase
import org.cryptomator.domain.usecases.vault.LockVaultUseCase
import org.cryptomator.domain.usecases.vault.MoveVaultPositionUseCase
import org.cryptomator.domain.usecases.vault.PrepareUnlockUseCase
import org.cryptomator.domain.usecases.vault.RemoveStoredVaultPasswordsUseCase
import org.cryptomator.domain.usecases.vault.RenameVaultUseCase
import org.cryptomator.domain.usecases.vault.SaveVaultUseCase
import org.cryptomator.domain.usecases.vault.UnlockToken
import org.cryptomator.domain.usecases.vault.UnlockVaultUseCase
import org.cryptomator.domain.usecases.vault.VaultOrUnlockToken
import org.cryptomator.generator.Callback
import org.cryptomator.presentation.BuildConfig
import org.cryptomator.presentation.R
import org.cryptomator.presentation.exception.ExceptionHandlers
import org.cryptomator.presentation.intent.Intents
import org.cryptomator.presentation.intent.UnlockVaultIntent
import org.cryptomator.presentation.model.CloudModel
import org.cryptomator.presentation.model.CloudTypeModel
import org.cryptomator.presentation.model.ProgressModel
import org.cryptomator.presentation.model.ProgressStateModel
import org.cryptomator.presentation.model.VaultModel
import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper
import org.cryptomator.presentation.service.AutoUploadService
@ -65,7 +55,6 @@ import org.cryptomator.presentation.workflow.CreateNewVaultWorkflow
import org.cryptomator.presentation.workflow.Workflow
import org.cryptomator.util.Optional
import org.cryptomator.util.SharedPreferencesHandler
import java.io.Serializable
import javax.inject.Inject
import timber.log.Timber
@ -76,15 +65,11 @@ class VaultListPresenter @Inject constructor( //
private val renameVaultUseCase: RenameVaultUseCase, //
private val lockVaultUseCase: LockVaultUseCase, //
private val getDecryptedCloudForVaultUseCase: GetDecryptedCloudForVaultUseCase, //
private val prepareUnlockUseCase: PrepareUnlockUseCase, //
private val unlockVaultUseCase: UnlockVaultUseCase, //
private val getRootFolderUseCase: GetRootFolderUseCase, //
private val addExistingVaultWorkflow: AddExistingVaultWorkflow, //
private val createNewVaultWorkflow: CreateNewVaultWorkflow, //
private val saveVaultUseCase: SaveVaultUseCase, //
private val moveVaultPositionUseCase: MoveVaultPositionUseCase, //
private val changePasswordUseCase: ChangePasswordUseCase, //
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase, //
private val licenseCheckUseCase: DoLicenseCheckUseCase, //
private val updateCheckUseCase: DoUpdateCheckUseCase, //
private val updateUseCase: DoUpdateUseCase, //
@ -96,31 +81,14 @@ class VaultListPresenter @Inject constructor( //
exceptionMappings: ExceptionHandlers) : Presenter<VaultListView>(exceptionMappings) {
private var vaultAction: VaultAction? = null
private var changedVaultPassword = false
private var startedUsingPrepareUnlock = false
private var retryUnlockHandler: Handler? = null
@Volatile
private var running = false
override fun workflows(): Iterable<Workflow<*>> {
return listOf(addExistingVaultWorkflow, createNewVaultWorkflow)
}
override fun destroyed() {
super.destroyed()
if (retryUnlockHandler != null) {
running = false
retryUnlockHandler?.removeCallbacks(null)
}
}
fun onWindowFocusChanged(hasFocus: Boolean) {
if (hasFocus) {
loadVaultList()
if (retryUnlockHandler != null) {
running = false
retryUnlockHandler?.removeCallbacks(null)
}
}
}
@ -257,11 +225,6 @@ class VaultListPresenter @Inject constructor( //
renameVault(VaultModel(vaultWithUpdatedCloud), newVaultName)
}
fun onUnlockCanceled() {
prepareUnlockUseCase.unsubscribe()
unlockVaultUseCase.cancel()
}
private fun browseFilesOf(vault: VaultModel) {
getDecryptedCloudForVaultUseCase //
.withVault(vault.toVault()) //
@ -318,7 +281,6 @@ class VaultListPresenter @Inject constructor( //
}
fun onVaultClicked(vault: VaultModel) {
startedUsingPrepareUnlock = sharedPreferencesHandler.backgroundUnlockPreparation()
startVaultAction(vault, VaultAction.UNLOCK)
}
@ -378,7 +340,6 @@ class VaultListPresenter @Inject constructor( //
when (vaultAction) {
VaultAction.UNLOCK -> requireUserAuthentication(authenticatedVaultModel)
VaultAction.RENAME -> view?.showRenameDialog(authenticatedVaultModel)
VaultAction.CHANGE_PASSWORD -> view?.showChangePasswordDialog(authenticatedVaultModel)
}
vaultAction = null
}
@ -387,83 +348,22 @@ class VaultListPresenter @Inject constructor( //
view?.addOrUpdateVault(authenticatedVault)
if (authenticatedVault.isLocked) {
if (!isPaused) {
if (canUseBiometricOn(authenticatedVault)) {
if (startedUsingPrepareUnlock) {
startPrepareUnlockUseCase(authenticatedVault.toVault())
}
view?.showBiometricDialog(authenticatedVault)
} else {
startPrepareUnlockUseCase(authenticatedVault.toVault())
view?.showEnterPasswordDialog(authenticatedVault)
}
requestActivityResult( //
ActivityResultCallbacks.vaultUnlockedVaultList(), //
Intents.unlockVaultIntent().withVaultModel(authenticatedVault).withVaultAction(UnlockVaultIntent.VaultAction.UNLOCK))
}
} else {
browseFilesOf(authenticatedVault)
}
}
fun startPrepareUnlockUseCase(vault: Vault) {
pendingUnlock = null
prepareUnlockUseCase //
.withVault(vault) //
.run(object : DefaultResultHandler<UnlockToken>() {
override fun onSuccess(unlockToken: UnlockToken) {
if (!startedUsingPrepareUnlock && vault.password != null) {
doUnlock(unlockToken, vault.password)
} else {
unlockTokenObtained(unlockToken)
}
}
override fun onError(e: Throwable) {
if (e is AuthenticationException) {
view?.cancelBasicAuthIfRunning()
}
if (!authenticationExceptionHandler.handleAuthenticationException(this@VaultListPresenter, e, ActivityResultCallbacks.authenticatedAfterUnlock(vault))) {
super.onError(e)
if (e is NetworkConnectionException) {
running = true
retryUnlockHandler = Handler()
restartUnlockUseCase(vault)
}
}
}
})
}
private fun restartUnlockUseCase(vault: Vault) {
retryUnlockHandler?.postDelayed({
if (running) {
prepareUnlockUseCase //
.withVault(vault) //
.run(object : DefaultResultHandler<UnlockToken>() {
override fun onSuccess(unlockToken: UnlockToken) {
if (!startedUsingPrepareUnlock && vault.password != null) {
doUnlock(unlockToken, vault.password)
} else {
unlockTokenObtained(unlockToken)
}
}
override fun onError(e: Throwable) {
if (e is NetworkConnectionException) {
restartUnlockUseCase(vault)
}
}
})
}
}, 1000)
}
private fun doUnlock(token: UnlockToken, password: String) {
unlockVaultUseCase //
.withVaultOrUnlockToken(VaultOrUnlockToken.from(token)) //
.andPassword(password) //
.run(object : DefaultResultHandler<Cloud>() {
override fun onSuccess(cloud: Cloud) {
navigateToVaultContent(cloud)
}
})
@Callback
fun vaultUnlockedVaultList(result: ActivityResult) {
val cloud = result.intent().getSerializableExtra(SINGLE_RESULT) as Cloud
when {
result.isResultOk -> navigateToVaultContent(cloud)
else -> TODO("Not yet implemented")
}
}
private fun navigateToVaultContent(cloud: Cloud) {
@ -488,51 +388,6 @@ class VaultListPresenter @Inject constructor( //
} else false
}
private fun unlockTokenObtained(unlockToken: UnlockToken) {
pendingUnlockFor(unlockToken.vault)?.setUnlockToken(unlockToken, this)
}
fun onUnlockClick(vault: VaultModel, password: String?) {
view?.showProgress(ProgressModel.GENERIC)
pendingUnlockFor(vault.toVault())?.setPassword(password, this)
}
private var pendingUnlock: PendingUnlock? = null
private fun pendingUnlockFor(vault: Vault): PendingUnlock? {
if (pendingUnlock == null) {
pendingUnlock = PendingUnlock(vault)
}
return if (pendingUnlock?.belongsTo(vault) == true) {
pendingUnlock
} else {
PendingUnlock.NO_OP_PENDING_UNLOCK
}
}
@Callback(dispatchResultOkOnly = false)
fun authenticatedAfterUnlock(result: ActivityResult, vault: Vault) {
if (result.isResultOk) {
val cloud = result.getSingleResult(CloudModel::class.java).toCloud()
if (startedUsingPrepareUnlock) {
startPrepareUnlockUseCase(Vault.aCopyOf(vault).withCloud(cloud).build())
if (view?.stoppedBiometricAuthDuringCloudAuthentication() == true) {
view?.showBiometricDialog(VaultModel(vault))
}
} else {
view?.showProgress(ProgressModel.GENERIC)
startPrepareUnlockUseCase(vault)
}
} else {
view?.closeDialog()
val error = result.getSingleResult(Throwable::class.java)
error?.let { showError(it) }
}
}
private fun canUseBiometricOn(vault: VaultModel): Boolean {
return vault.password != null && BiometricManager.from(context()).canAuthenticate() == BiometricManager.BIOMETRIC_SUCCESS
}
fun onAddExistingVault() {
addExistingVaultWorkflow.start()
}
@ -548,60 +403,11 @@ class VaultListPresenter @Inject constructor( //
}
fun onChangePasswordClicked(vaultModel: VaultModel) {
startVaultAction(vaultModel, VaultAction.CHANGE_PASSWORD)
}
fun onChangePasswordClicked(vaultModel: VaultModel, oldPassword: String?, newPassword: String?) {
view?.showProgress(ProgressModel(ProgressStateModel.CHANGING_PASSWORD))
changePasswordUseCase.withVault(vaultModel.toVault()) //
.andOldPassword(oldPassword) //
.andNewPassword(newPassword) //
.run(object : DefaultResultHandler<Void?>() {
override fun onSuccess(void: Void?) {
view?.showProgress(ProgressModel.COMPLETED)
view?.showMessage(R.string.screen_vault_list_change_password_successful)
if (canUseBiometricOn(vaultModel)) {
changedVaultPassword = true
view?.getEncryptedPasswordWithBiometricAuthentication(VaultModel( //
Vault.aCopyOf(vaultModel.toVault()) //
.withSavedPassword(newPassword) //
.build()))
}
}
override fun onError(e: Throwable) {
if (!authenticationExceptionHandler.handleAuthenticationException( //
this@VaultListPresenter, e, //
ActivityResultCallbacks.changePasswordAfterAuthentication(vaultModel.toVault(), oldPassword, newPassword))) {
showError(e)
}
}
})
}
@Callback
fun changePasswordAfterAuthentication(result: ActivityResult, vault: Vault?, oldPassword: String?, newPassword: String?) {
val cloud = result.getSingleResult(CloudModel::class.java).toCloud()
val vaultWithUpdatedCloud = Vault.aCopyOf(vault).withCloud(cloud).build()
onChangePasswordClicked(VaultModel(vaultWithUpdatedCloud), oldPassword, newPassword)
}
private fun save(vaultModel: VaultModel) {
saveVaultUseCase //
.withVault(vaultModel.toVault()) //
.run(DefaultResultHandler())
}
fun onBiometricKeyInvalidated() {
removeStoredVaultPasswordsUseCase.run(object : DefaultResultHandler<Void?>() {
override fun onSuccess(void: Void?) {
view?.showBiometricAuthKeyInvalidatedDialog()
}
override fun onError(e: Throwable) {
Timber.tag("VaultListPresenter").e(e, "Error while removing vault passwords")
}
})
Intents
.unlockVaultIntent()
.withVaultModel(vaultModel)
.withVaultAction(UnlockVaultIntent.VaultAction.CHANGE_PASSWORD)
.startActivity(this)
}
fun onVaultSettingsClicked(vaultModel: VaultModel) {
@ -654,26 +460,8 @@ class VaultListPresenter @Inject constructor( //
})
}
fun onBiometricAuthenticationSucceeded(vaultModel: VaultModel) {
if (changedVaultPassword) {
changedVaultPassword = false
save(vaultModel)
} else {
if (startedUsingPrepareUnlock) {
onUnlockClick(vaultModel, vaultModel.password)
} else {
view?.showProgress(ProgressModel.GENERIC)
startPrepareUnlockUseCase(vaultModel.toVault())
}
}
}
fun useConfirmationInFaceUnlockBiometricAuthentication(): Boolean {
return sharedPreferencesHandler.useConfirmationInFaceUnlockBiometricAuthentication()
}
private enum class VaultAction {
UNLOCK, RENAME, CHANGE_PASSWORD
UNLOCK, RENAME
}
fun installUpdate() {
@ -698,43 +486,6 @@ class VaultListPresenter @Inject constructor( //
})
}
fun startedUsingPrepareUnlock(): Boolean {
return startedUsingPrepareUnlock
}
private open class PendingUnlock(private val vault: Vault?) : Serializable {
private var unlockToken: UnlockToken? = null
private var password: String? = null
fun setUnlockToken(unlockToken: UnlockToken?, presenter: VaultListPresenter) {
this.unlockToken = unlockToken
continueIfComplete(presenter)
}
fun setPassword(password: String?, presenter: VaultListPresenter) {
this.password = password
continueIfComplete(presenter)
}
open fun continueIfComplete(presenter: VaultListPresenter) {
unlockToken?.let { token -> password?.let { password -> presenter.doUnlock(token, password) } }
}
fun belongsTo(vault: Vault): Boolean {
return vault == this.vault
}
companion object {
val NO_OP_PENDING_UNLOCK: PendingUnlock = object : PendingUnlock(null) {
override fun continueIfComplete(presenter: VaultListPresenter) {
// empty
}
}
}
}
init {
unsubscribeOnDestroy( //
deleteVaultUseCase, //
@ -743,9 +494,6 @@ class VaultListPresenter @Inject constructor( //
getVaultListUseCase, //
saveVaultUseCase, //
moveVaultPositionUseCase, //
removeStoredVaultPasswordsUseCase, //
unlockVaultUseCase, //
prepareUnlockUseCase, //
licenseCheckUseCase, //
updateCheckUseCase, //
updateUseCase)

View File

@ -1,7 +1,5 @@
package org.cryptomator.presentation.ui.activity
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment
import org.cryptomator.generator.Activity
import org.cryptomator.presentation.R
@ -9,20 +7,15 @@ import org.cryptomator.presentation.model.CloudFolderModel
import org.cryptomator.presentation.model.VaultModel
import org.cryptomator.presentation.presenter.AutoUploadChooseVaultPresenter
import org.cryptomator.presentation.ui.activity.view.AutoUploadChooseVaultView
import org.cryptomator.presentation.ui.dialog.BiometricAuthKeyInvalidatedDialog
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
import org.cryptomator.presentation.ui.dialog.NotEnoughVaultsDialog
import org.cryptomator.presentation.ui.fragment.AutoUploadChooseVaultFragment
import org.cryptomator.presentation.util.BiometricAuthentication
import javax.inject.Inject
import kotlinx.android.synthetic.main.toolbar_layout.toolbar
@Activity
class AutoUploadChooseVaultActivity : BaseActivity(), //
AutoUploadChooseVaultView, //
NotEnoughVaultsDialog.Callback, //
EnterPasswordDialog.Callback,
BiometricAuthentication.Callback {
NotEnoughVaultsDialog.Callback {
@Inject
lateinit var presenter: AutoUploadChooseVaultPresenter
@ -40,7 +33,7 @@ class AutoUploadChooseVaultActivity : BaseActivity(), //
}
}
override fun createFragment(): Fragment? = AutoUploadChooseVaultFragment()
override fun createFragment(): Fragment = AutoUploadChooseVaultFragment()
override fun displayVaults(vaults: List<VaultModel>) {
@ -69,41 +62,5 @@ class AutoUploadChooseVaultActivity : BaseActivity(), //
autoUploadChooseVaultFragment().showChosenLocation(location)
}
override fun onUnlockCanceled() {
presenter.onUnlockCanceled()
}
override fun onUnlockClick(vaultModel: VaultModel, password: String) {
presenter.onUnlockPressed(vaultModel, password)
}
@RequiresApi(api = Build.VERSION_CODES.M)
override fun showEnterPasswordDialog(vaultModel: VaultModel) {
if (vaultWithBiometricAuthEnabled(vaultModel)) {
BiometricAuthentication(this, context(), BiometricAuthentication.CryptoMode.DECRYPT, presenter.useConfirmationInFaceUnlockBiometricAuthentication())
.startListening(autoUploadChooseVaultFragment(), vaultModel)
} else {
showDialog(EnterPasswordDialog.newInstance(vaultModel))
}
}
override fun onBiometricAuthenticated(vault: VaultModel) {
presenter.onUnlockPressed(vault, vault.password)
}
override fun onBiometricAuthenticationFailed(vault: VaultModel) {
showDialog(EnterPasswordDialog.newInstance(vault))
}
override fun onBiometricKeyInvalidated(vault: VaultModel) {
presenter.onBiometricKeyInvalidated(vault)
}
override fun showBiometricAuthKeyInvalidatedDialog() {
showDialog(BiometricAuthKeyInvalidatedDialog.newInstance())
}
private fun vaultWithBiometricAuthEnabled(vault: VaultModel): Boolean = vault.password != null
private fun autoUploadChooseVaultFragment(): AutoUploadChooseVaultFragment = getCurrentFragment(R.id.fragmentContainer) as AutoUploadChooseVaultFragment
}

View File

@ -1,28 +1,20 @@
package org.cryptomator.presentation.ui.activity
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager
import androidx.fragment.app.Fragment
import org.cryptomator.domain.Vault
import org.cryptomator.generator.Activity
import org.cryptomator.presentation.R
import org.cryptomator.presentation.model.VaultModel
import org.cryptomator.presentation.presenter.BiometricAuthSettingsPresenter
import org.cryptomator.presentation.ui.activity.view.BiometricAuthSettingsView
import org.cryptomator.presentation.ui.dialog.BiometricAuthKeyInvalidatedDialog
import org.cryptomator.presentation.ui.dialog.EnrollSystemBiometricDialog
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
import org.cryptomator.presentation.ui.fragment.BiometricAuthSettingsFragment
import org.cryptomator.presentation.util.BiometricAuthentication
import javax.inject.Inject
import kotlinx.android.synthetic.main.toolbar_layout.toolbar
@Activity
class BiometricAuthSettingsActivity : BaseActivity(), //
EnterPasswordDialog.Callback, //
BiometricAuthSettingsView, //
BiometricAuthentication.Callback, //
EnrollSystemBiometricDialog.Callback {
@Inject
@ -42,10 +34,6 @@ class BiometricAuthSettingsActivity : BaseActivity(), //
}
}
override fun showBiometricAuthKeyInvalidatedDialog() {
showDialog(BiometricAuthKeyInvalidatedDialog.newInstance())
}
override fun createFragment(): Fragment? = BiometricAuthSettingsFragment()
override fun renderVaultList(vaultModelCollection: List<VaultModel>) {
@ -56,30 +44,6 @@ class BiometricAuthSettingsActivity : BaseActivity(), //
biometricAuthSettingsFragment().clearVaultList()
}
override fun showEnterPasswordDialog(vaultModel: VaultModel) {
showDialog(EnterPasswordDialog.newInstance(vaultModel))
}
override fun onUnlockClick(vaultModel: VaultModel, password: String) {
val vaultModelWithSavedPassword = VaultModel( //
Vault //
.aCopyOf(vaultModel.toVault()) //
.withSavedPassword(password) //
.build())
presenter.verifyPassword(vaultModelWithSavedPassword)
}
override fun onUnlockCanceled() {
presenter.onUnlockCanceled()
}
@RequiresApi(api = Build.VERSION_CODES.M)
override fun showBiometricAuthenticationDialog(vaultModel: VaultModel) {
BiometricAuthentication(this, context(), BiometricAuthentication.CryptoMode.ENCRYPT, presenter.useConfirmationInFaceUnlockBiometricAuthentication())
.startListening(biometricAuthSettingsFragment(), vaultModel)
}
private fun biometricAuthSettingsFragment(): BiometricAuthSettingsFragment = getCurrentFragment(R.id.fragmentContainer) as BiometricAuthSettingsFragment
override fun onSetupBiometricAuthInSystemClicked() {
@ -89,17 +53,4 @@ class BiometricAuthSettingsActivity : BaseActivity(), //
override fun onCancelSetupBiometricAuthInSystemClicked() {
finish()
}
override fun onBiometricAuthenticated(vault: VaultModel) {
presenter.saveVault(vault.toVault())
}
override fun onBiometricAuthenticationFailed(vault: VaultModel) {
showError(getString(R.string.error_biometric_auth_aborted))
biometricAuthSettingsFragment().addOrUpdateVault(vault)
}
override fun onBiometricKeyInvalidated(vault: VaultModel) {
presenter.onBiometricAuthKeyInvalidated(vault)
}
}

View File

@ -5,8 +5,6 @@ import android.content.Intent
import android.content.Intent.ACTION_SEND
import android.content.Intent.ACTION_SEND_MULTIPLE
import android.net.Uri
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment
import org.cryptomator.generator.Activity
import org.cryptomator.presentation.R
@ -16,13 +14,10 @@ import org.cryptomator.presentation.model.SharedFileModel
import org.cryptomator.presentation.model.VaultModel
import org.cryptomator.presentation.presenter.SharedFilesPresenter
import org.cryptomator.presentation.ui.activity.view.SharedFilesView
import org.cryptomator.presentation.ui.dialog.BiometricAuthKeyInvalidatedDialog
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
import org.cryptomator.presentation.ui.dialog.NotEnoughVaultsDialog
import org.cryptomator.presentation.ui.dialog.ReplaceDialog
import org.cryptomator.presentation.ui.dialog.UploadCloudFileDialog
import org.cryptomator.presentation.ui.fragment.SharedFilesFragment
import org.cryptomator.presentation.util.BiometricAuthentication
import java.lang.String.format
import java.util.ArrayList
import javax.inject.Inject
@ -32,8 +27,6 @@ import timber.log.Timber
@Activity
class SharedFilesActivity : BaseActivity(), //
SharedFilesView, //
EnterPasswordDialog.Callback, //
BiometricAuthentication.Callback, //
ReplaceDialog.Callback, //
NotEnoughVaultsDialog.Callback, //
UploadCloudFileDialog.Callback {
@ -126,7 +119,7 @@ class SharedFilesActivity : BaseActivity(), //
}
}
override fun createFragment(): Fragment? = SharedFilesFragment()
override fun createFragment(): Fragment = SharedFilesFragment()
public override fun onMenuItemSelected(itemId: Int): Boolean = when (itemId) {
android.R.id.home -> {
@ -150,16 +143,6 @@ class SharedFilesActivity : BaseActivity(), //
private fun sharedFilesFragment(): SharedFilesFragment = getCurrentFragment(R.id.fragmentContainer) as SharedFilesFragment
@RequiresApi(api = Build.VERSION_CODES.M)
override fun showEnterPasswordDialog(vault: VaultModel) {
if (vaultWithBiometricAuthEnabled(vault)) {
BiometricAuthentication(this, context(), BiometricAuthentication.CryptoMode.DECRYPT, presenter.useConfirmationInFaceUnlockBiometricAuthentication())
.startListening(sharedFilesFragment(), vault)
} else {
showDialog(EnterPasswordDialog.newInstance(vault))
}
}
override fun showReplaceDialog(existingFiles: List<String>, size: Int) {
ReplaceDialog.withContext(this).show(existingFiles, size)
}
@ -168,22 +151,10 @@ class SharedFilesActivity : BaseActivity(), //
sharedFilesFragment().showChosenLocation(folder)
}
override fun showBiometricAuthKeyInvalidatedDialog() {
showDialog(BiometricAuthKeyInvalidatedDialog.newInstance())
}
override fun showUploadDialog(uploadingFiles: Int) {
showDialog(UploadCloudFileDialog.newInstance(uploadingFiles))
}
override fun onUnlockClick(vaultModel: VaultModel, password: String) {
presenter.onUnlockPressed(vaultModel, password)
}
override fun onUnlockCanceled() {
presenter.onUnlockCanceled()
}
override fun onReplacePositiveClicked() {
presenter.onReplaceExistingFilesPressed()
}
@ -209,20 +180,6 @@ class SharedFilesActivity : BaseActivity(), //
finish()
}
override fun onBiometricAuthenticated(vault: VaultModel) {
presenter.onUnlockPressed(vault, vault.password)
}
override fun onBiometricAuthenticationFailed(vault: VaultModel) {
showDialog(EnterPasswordDialog.newInstance(vault))
}
override fun onBiometricKeyInvalidated(vault: VaultModel) {
presenter.onBiometricAuthKeyInvalidated()
}
private fun vaultWithBiometricAuthEnabled(vault: VaultModel): Boolean = vault.password != null
override fun onUploadCanceled() {
presenter.onUploadCanceled()
}

View File

@ -0,0 +1,109 @@
package org.cryptomator.presentation.ui.activity
import android.os.Build
import androidx.annotation.RequiresApi
import org.cryptomator.domain.Vault
import org.cryptomator.generator.Activity
import org.cryptomator.generator.InjectIntent
import org.cryptomator.presentation.R
import org.cryptomator.presentation.intent.UnlockVaultIntent
import org.cryptomator.presentation.model.VaultModel
import org.cryptomator.presentation.presenter.UnlockVaultPresenter
import org.cryptomator.presentation.ui.activity.view.UnlockVaultView
import org.cryptomator.presentation.ui.dialog.BiometricAuthKeyInvalidatedDialog
import org.cryptomator.presentation.ui.dialog.ChangePasswordDialog
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
import org.cryptomator.presentation.ui.fragment.UnlockVaultFragment
import org.cryptomator.presentation.util.BiometricAuthentication
import javax.inject.Inject
@Activity(layout = R.layout.activity_unlock_vault)
class UnlockVaultActivity : BaseActivity(), //
UnlockVaultView, //
BiometricAuthentication.Callback,
ChangePasswordDialog.Callback {
@Inject
lateinit var presenter: UnlockVaultPresenter
@InjectIntent
lateinit var unlockVaultIntent: UnlockVaultIntent
private lateinit var biometricAuthentication: BiometricAuthentication
override fun finish() {
super.finish()
overridePendingTransition(0, 0)
}
override fun showEnterPasswordDialog(vault: VaultModel) {
showDialog(EnterPasswordDialog.newInstance(vault))
}
@RequiresApi(api = Build.VERSION_CODES.M)
override fun showBiometricDialog(vault: VaultModel) {
biometricAuthentication = BiometricAuthentication(this, context(), BiometricAuthentication.CryptoMode.DECRYPT, presenter.useConfirmationInFaceUnlockBiometricAuthentication())
biometricAuthentication.startListening(unlockVaultFragment(), vault)
}
@RequiresApi(api = Build.VERSION_CODES.M)
override fun getEncryptedPasswordWithBiometricAuthentication(vaultModel: VaultModel) {
biometricAuthentication = BiometricAuthentication(this, context(), BiometricAuthentication.CryptoMode.ENCRYPT, presenter.useConfirmationInFaceUnlockBiometricAuthentication())
biometricAuthentication.startListening(unlockVaultFragment(), vaultModel)
}
override fun showBiometricAuthKeyInvalidatedDialog() {
showDialog(BiometricAuthKeyInvalidatedDialog.newInstance())
}
@RequiresApi(api = Build.VERSION_CODES.M)
override fun cancelBasicAuthIfRunning() {
biometricAuthentication.stopListening()
}
@RequiresApi(api = Build.VERSION_CODES.M)
override fun stoppedBiometricAuthDuringCloudAuthentication(): Boolean {
return biometricAuthentication.stoppedBiometricAuthDuringCloudAuthentication()
}
override fun onUnlockClick(vaultModel: VaultModel, password: String) {
presenter.onUnlockClick(vaultModel, password)
}
override fun onUnlockCanceled() {
presenter.onUnlockCanceled()
}
override fun onBiometricAuthenticated(vault: VaultModel) {
presenter.onBiometricAuthenticationSucceeded(vault)
}
override fun onBiometricAuthenticationFailed(vault: VaultModel) {
val vaultWithoutPassword = Vault.aCopyOf(vault.toVault()).withSavedPassword(null).build()
when(unlockVaultIntent.vaultAction()) {
UnlockVaultIntent.VaultAction.CHANGE_PASSWORD -> presenter.saveVaultAfterChangePasswordButFailedBiometricAuth(vaultWithoutPassword)
else -> {
if (!presenter.startedUsingPrepareUnlock()) {
presenter.startPrepareUnlockUseCase(vaultWithoutPassword)
}
showEnterPasswordDialog(VaultModel(vaultWithoutPassword))
}
}
}
override fun onBiometricKeyInvalidated(vault: VaultModel) {
presenter.onBiometricKeyInvalidated()
}
private fun unlockVaultFragment(): UnlockVaultFragment = //
getCurrentFragment(R.id.fragmentContainer) as UnlockVaultFragment
override fun showChangePasswordDialog(vaultModel: VaultModel) {
showDialog(ChangePasswordDialog.newInstance(vaultModel))
}
override fun onChangePasswordClick(vaultModel: VaultModel, oldPassword: String, newPassword: String) {
presenter.onChangePasswordClick(vaultModel, oldPassword, newPassword)
}
}

View File

@ -2,9 +2,7 @@ package org.cryptomator.presentation.ui.activity
import android.content.Intent
import android.net.Uri
import android.os.Build
import android.view.View
import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment
import org.cryptomator.domain.Vault
import org.cryptomator.generator.Activity
@ -25,9 +23,6 @@ import org.cryptomator.presentation.ui.bottomsheet.SettingsVaultBottomSheet
import org.cryptomator.presentation.ui.callback.VaultListCallback
import org.cryptomator.presentation.ui.dialog.AskForLockScreenDialog
import org.cryptomator.presentation.ui.dialog.BetaConfirmationDialog
import org.cryptomator.presentation.ui.dialog.BiometricAuthKeyInvalidatedDialog
import org.cryptomator.presentation.ui.dialog.ChangePasswordDialog
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
import org.cryptomator.presentation.ui.dialog.UpdateAppAvailableDialog
import org.cryptomator.presentation.ui.dialog.UpdateAppDialog
import org.cryptomator.presentation.ui.dialog.VaultDeleteConfirmationDialog
@ -35,7 +30,6 @@ import org.cryptomator.presentation.ui.dialog.VaultNotFoundDialog
import org.cryptomator.presentation.ui.dialog.VaultRenameDialog
import org.cryptomator.presentation.ui.fragment.VaultListFragment
import org.cryptomator.presentation.ui.layout.ObscuredAwareCoordinatorLayout.Listener
import org.cryptomator.presentation.util.BiometricAuthentication
import java.util.Locale
import javax.inject.Inject
import kotlinx.android.synthetic.main.activity_layout_obscure_aware.activityRootView
@ -45,10 +39,8 @@ import kotlinx.android.synthetic.main.toolbar_layout.toolbar
class VaultListActivity : BaseActivity(), //
VaultListView, //
VaultListCallback, //
BiometricAuthentication.Callback, //
AskForLockScreenDialog.Callback, //
ChangePasswordDialog.Callback, //
VaultNotFoundDialog.Callback,
VaultNotFoundDialog.Callback, //
UpdateAppAvailableDialog.Callback, //
UpdateAppDialog.Callback, //
BetaConfirmationDialog.Callback {
@ -59,8 +51,6 @@ class VaultListActivity : BaseActivity(), //
@InjectIntent
lateinit var vaultListIntent: VaultListIntent
private var biometricAuthentication: BiometricAuthentication? = null
override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus)
vaultListPresenter.onWindowFocusChanged(hasFocus)
@ -125,40 +115,6 @@ class VaultListActivity : BaseActivity(), //
showDialog(VaultRenameDialog.newInstance(vaultModel))
}
override fun showEnterPasswordDialog(vault: VaultModel) {
showDialog(EnterPasswordDialog.newInstance(vault))
}
@RequiresApi(api = Build.VERSION_CODES.M)
override fun showBiometricDialog(vault: VaultModel) {
biometricAuthentication = BiometricAuthentication(this, context(), BiometricAuthentication.CryptoMode.DECRYPT, vaultListPresenter.useConfirmationInFaceUnlockBiometricAuthentication())
biometricAuthentication?.startListening(vaultListFragment(), vault)
}
override fun showChangePasswordDialog(vaultModel: VaultModel) {
showDialog(ChangePasswordDialog.newInstance(vaultModel))
}
@RequiresApi(api = Build.VERSION_CODES.M)
override fun getEncryptedPasswordWithBiometricAuthentication(vaultModel: VaultModel) {
biometricAuthentication = BiometricAuthentication(this, context(), BiometricAuthentication.CryptoMode.ENCRYPT, vaultListPresenter.useConfirmationInFaceUnlockBiometricAuthentication())
biometricAuthentication?.startListening(vaultListFragment(), vaultModel)
}
override fun showBiometricAuthKeyInvalidatedDialog() {
showDialog(BiometricAuthKeyInvalidatedDialog.newInstance())
}
@RequiresApi(api = Build.VERSION_CODES.M)
override fun cancelBasicAuthIfRunning() {
biometricAuthentication?.stopListening()
}
@RequiresApi(api = Build.VERSION_CODES.M)
override fun stoppedBiometricAuthDuringCloudAuthentication(): Boolean {
return biometricAuthentication?.stoppedBiometricAuthDuringCloudAuthentication() == true
}
override fun rowMoved(fromPosition: Int, toPosition: Int) {
vaultListFragment().rowMoved(fromPosition, toPosition)
}
@ -209,14 +165,6 @@ class VaultListActivity : BaseActivity(), //
vaultListPresenter.onCreateVault()
}
override fun onUnlockClick(vaultModel: VaultModel, password: String) {
vaultListPresenter.onUnlockClick(vaultModel, password)
}
override fun onUnlockCanceled() {
vaultListPresenter.onUnlockCanceled()
}
override fun onDeleteVaultClick(vaultModel: VaultModel) {
VaultDeleteConfirmationDialog.newInstance(vaultModel) //
.show(supportFragmentManager, "VaultDeleteConfirmationDialog")
@ -249,10 +197,6 @@ class VaultListActivity : BaseActivity(), //
private fun vaultListFragment(): VaultListFragment = //
getCurrentFragment(R.id.fragmentContainer) as VaultListFragment
override fun onChangePasswordClick(vaultModel: VaultModel, oldPassword: String, newPassword: String) {
vaultListPresenter.onChangePasswordClicked(vaultModel, oldPassword, newPassword)
}
override fun onDeleteMissingVaultClicked(vault: Vault) {
vaultListPresenter.onDeleteMissingVaultClicked(vault)
}
@ -279,21 +223,4 @@ class VaultListActivity : BaseActivity(), //
override fun onAskForBetaConfirmationFinished() {
sharedPreferencesHandler.setBetaScreenDialogAlreadyShown()
}
override fun onBiometricAuthenticated(vault: VaultModel) {
vaultListPresenter.onBiometricAuthenticationSucceeded(vault)
}
override fun onBiometricAuthenticationFailed(vault: VaultModel) {
val vaultWithoutPassword = Vault.aCopyOf(vault.toVault()).withSavedPassword(null).build()
if (!vaultListPresenter.startedUsingPrepareUnlock()) {
vaultListPresenter.startPrepareUnlockUseCase(vaultWithoutPassword)
}
showEnterPasswordDialog(VaultModel(vaultWithoutPassword))
}
override fun onBiometricKeyInvalidated(vault: VaultModel) {
vaultListPresenter.onBiometricKeyInvalidated()
}
}

View File

@ -8,7 +8,5 @@ interface AutoUploadChooseVaultView : View {
fun displayDialogUnableToUploadFiles()
fun displayVaults(vaults: List<VaultModel>)
fun showChosenLocation(location: CloudFolderModel)
fun showEnterPasswordDialog(vaultModel: VaultModel)
fun showBiometricAuthKeyInvalidatedDialog()
}

View File

@ -6,9 +6,6 @@ interface BiometricAuthSettingsView : View {
fun renderVaultList(vaultModelCollection: List<VaultModel>)
fun clearVaultList()
fun showBiometricAuthenticationDialog(vaultModel: VaultModel)
fun showEnterPasswordDialog(vaultModel: VaultModel)
fun showSetupBiometricAuthDialog()
fun showBiometricAuthKeyInvalidatedDialog()
}

View File

@ -11,10 +11,8 @@ interface SharedFilesView : View {
fun displayVaults(vaults: List<VaultModel>)
fun displayFilesToUpload(sharedFiles: List<SharedFileModel>)
fun displayDialogUnableToUploadFiles()
fun showEnterPasswordDialog(vault: VaultModel)
fun showReplaceDialog(existingFiles: List<String>, size: Int)
fun showChosenLocation(folder: CloudFolderModel)
fun showBiometricAuthKeyInvalidatedDialog()
fun showUploadDialog(uploadingFiles: Int)
}

View File

@ -0,0 +1,16 @@
package org.cryptomator.presentation.ui.activity.view
import org.cryptomator.presentation.model.VaultModel
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
interface UnlockVaultView : View, EnterPasswordDialog.Callback {
fun showEnterPasswordDialog(vault: VaultModel)
fun showBiometricDialog(vault: VaultModel)
fun getEncryptedPasswordWithBiometricAuthentication(vaultModel: VaultModel)
fun showBiometricAuthKeyInvalidatedDialog()
fun cancelBasicAuthIfRunning()
fun stoppedBiometricAuthDuringCloudAuthentication(): Boolean
fun showChangePasswordDialog(vaultModel: VaultModel)
}

View File

@ -12,17 +12,10 @@ interface VaultListView : View {
fun addOrUpdateVault(vault: VaultModel)
fun renameVault(vaultModel: VaultModel)
fun navigateToVaultContent(vault: VaultModel, decryptedRoot: CloudFolderModel)
fun showEnterPasswordDialog(vault: VaultModel)
fun showBiometricDialog(vault: VaultModel)
fun showChangePasswordDialog(vaultModel: VaultModel)
fun getEncryptedPasswordWithBiometricAuthentication(vaultModel: VaultModel)
fun showVaultSettingsDialog(vaultModel: VaultModel)
fun showAddVaultBottomSheet()
fun showRenameDialog(vaultModel: VaultModel)
fun showBiometricAuthKeyInvalidatedDialog()
fun isVaultLocked(vaultModel: VaultModel): Boolean
fun cancelBasicAuthIfRunning()
fun stoppedBiometricAuthDuringCloudAuthentication(): Boolean
fun rowMoved(fromPosition: Int, toPosition: Int)
fun vaultMoved(vaults: List<VaultModel>)

View File

@ -2,8 +2,11 @@ package org.cryptomator.presentation.ui.callback
import org.cryptomator.presentation.ui.bottomsheet.AddVaultBottomSheet
import org.cryptomator.presentation.ui.bottomsheet.SettingsVaultBottomSheet
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
import org.cryptomator.presentation.ui.dialog.VaultDeleteConfirmationDialog
import org.cryptomator.presentation.ui.dialog.VaultRenameDialog
interface VaultListCallback : AddVaultBottomSheet.Callback, EnterPasswordDialog.Callback, SettingsVaultBottomSheet.Callback, VaultDeleteConfirmationDialog.Callback, VaultRenameDialog.Callback
// FIXME delete this file and add this interfaces to VaultListView.kt
interface VaultListCallback : AddVaultBottomSheet.Callback, //
SettingsVaultBottomSheet.Callback, //
VaultDeleteConfirmationDialog.Callback, //
VaultRenameDialog.Callback

View File

@ -0,0 +1,17 @@
package org.cryptomator.presentation.ui.fragment
import org.cryptomator.generator.Fragment
import org.cryptomator.presentation.R
import org.cryptomator.presentation.presenter.UnlockVaultPresenter
import javax.inject.Inject
@Fragment(R.layout.fragment_unlock_vault)
class UnlockVaultFragment : BaseFragment() {
@Inject
lateinit var presenter: UnlockVaultPresenter
override fun setupView() {
presenter.setup()
}
}

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activityRootView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragmentContainer"
android:name="org.cryptomator.presentation.ui.fragment.UnlockVaultFragment"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:tag="UnlockVaultFragment" />
</LinearLayout>

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>

View File

@ -29,6 +29,10 @@
<item name="colorAccent">@color/colorPrimary</item>
</style>
<style name="TransparentAlertDialogCustom" parent="AlertDialogCustom">
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<style name="Toolbar.Theme" parent="ThemeOverlay.AppCompat.Dark.ActionBar">
<item name="colorAccent">@color/textColorWhite</item>
</style>

View File

@ -13,17 +13,13 @@ import org.cryptomator.domain.usecases.DoUpdateUseCase;
import org.cryptomator.domain.usecases.GetDecryptedCloudForVaultUseCase;
import org.cryptomator.domain.usecases.ResultHandler;
import org.cryptomator.domain.usecases.cloud.GetRootFolderUseCase;
import org.cryptomator.domain.usecases.vault.ChangePasswordUseCase;
import org.cryptomator.domain.usecases.vault.DeleteVaultUseCase;
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase;
import org.cryptomator.domain.usecases.vault.LockVaultUseCase;
import org.cryptomator.domain.usecases.vault.MoveVaultPositionUseCase;
import org.cryptomator.domain.usecases.vault.PrepareUnlockUseCase;
import org.cryptomator.domain.usecases.vault.RemoveStoredVaultPasswordsUseCase;
import org.cryptomator.domain.usecases.vault.RenameVaultUseCase;
import org.cryptomator.domain.usecases.vault.SaveVaultUseCase;
import org.cryptomator.domain.usecases.vault.UnlockToken;
import org.cryptomator.domain.usecases.vault.UnlockVaultUseCase;
import org.cryptomator.presentation.exception.ExceptionHandlers;
import org.cryptomator.presentation.model.VaultModel;
import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper;
@ -98,17 +94,12 @@ public class VaultListPresenterTest {
private LockVaultUseCase lockVaultUseCase = Mockito.mock(LockVaultUseCase.class);
private LockVaultUseCase.Launcher lockVaultUseCaseLauncher = Mockito.mock(LockVaultUseCase.Launcher.class);
private GetDecryptedCloudForVaultUseCase getDecryptedCloudForVaultUseCase = Mockito.mock(GetDecryptedCloudForVaultUseCase.class);
private PrepareUnlockUseCase prepareUnlockUseCase = Mockito.mock(PrepareUnlockUseCase.class);
private PrepareUnlockUseCase.Launcher prepareUnlockUseCaseLauncher = Mockito.mock(PrepareUnlockUseCase.Launcher.class);
private UnlockToken unlockToken = Mockito.mock(UnlockToken.class);
private UnlockVaultUseCase unlockVaultUseCase = Mockito.mock(UnlockVaultUseCase.class);
private GetRootFolderUseCase getRootFolderUseCase = Mockito.mock(GetRootFolderUseCase.class);
private AddExistingVaultWorkflow addExistingVaultWorkflow = Mockito.mock(AddExistingVaultWorkflow.class);
private CreateNewVaultWorkflow createNewVaultWorkflow = Mockito.mock(CreateNewVaultWorkflow.class);
private SaveVaultUseCase saveVaultUseCase = Mockito.mock(SaveVaultUseCase.class);
private MoveVaultPositionUseCase moveVaultPositionUseCase = Mockito.mock(MoveVaultPositionUseCase.class);
private ChangePasswordUseCase changePasswordUseCase = Mockito.mock(ChangePasswordUseCase.class);
private RemoveStoredVaultPasswordsUseCase removeStoredVaultPasswordsUseCase = Mockito.mock(RemoveStoredVaultPasswordsUseCase.class);
private DoLicenseCheckUseCase doLicenceCheckUsecase = Mockito.mock(DoLicenseCheckUseCase.class);
private DoUpdateCheckUseCase updateCheckUseCase = Mockito.mock(DoUpdateCheckUseCase.class);
private DoUpdateUseCase updateUseCase = Mockito.mock(DoUpdateUseCase.class);
@ -126,15 +117,11 @@ public class VaultListPresenterTest {
renameVaultUseCase, //
lockVaultUseCase, //
getDecryptedCloudForVaultUseCase, //
prepareUnlockUseCase, //
unlockVaultUseCase, //
getRootFolderUseCase, //
addExistingVaultWorkflow, //
createNewVaultWorkflow, //
saveVaultUseCase, //
moveVaultPositionUseCase, //
changePasswordUseCase, //
removeStoredVaultPasswordsUseCase, //
doLicenceCheckUsecase, //
updateCheckUseCase, //
updateUseCase, //
@ -241,43 +228,4 @@ public class VaultListPresenterTest {
Mockito.any());
}
@Test
public void testOnUnlockCanceled() {
inTest.onUnlockCanceled();
verify(prepareUnlockUseCase).unsubscribe();
verify(unlockVaultUseCase).cancel();
}
@Test
public void testOnVaultLockedClicked() {
ArgumentCaptor<ResultHandler<Vault>> captor = ArgumentCaptor.forClass(ResultHandler.class);
when(lockVaultUseCase.withVault(AN_UNLOCKED_VAULT_MODEL.toVault())).thenReturn(lockVaultUseCaseLauncher);
inTest.onVaultLockClicked(AN_UNLOCKED_VAULT_MODEL);
verify(lockVaultUseCaseLauncher).run(captor.capture());
captor.getValue().onSuccess(AN_UNLOCKED_VAULT_MODEL.toVault());
verify(vaultListView).addOrUpdateVault(AN_UNLOCKED_VAULT_MODEL);
}
@Test
public void onVaultClickedWithCloudAndLocked() {
ArgumentCaptor<ResultHandler<UnlockToken>> captor = ArgumentCaptor.forClass(ResultHandler.class);
when(prepareUnlockUseCase.withVault(ANOTHER_VAULT_MODEL_WITH_CLOUD.toVault())) //
.thenReturn(prepareUnlockUseCaseLauncher);
when(unlockToken.getVault()) //
.thenReturn(ANOTHER_VAULT_MODEL_WITH_CLOUD.toVault());
inTest.onVaultClicked(ANOTHER_VAULT_MODEL_WITH_CLOUD);
verify(prepareUnlockUseCaseLauncher).run(captor.capture());
captor.getValue().onSuccess(unlockToken);
verify(vaultListView).addOrUpdateVault(ANOTHER_VAULT_MODEL_WITH_CLOUD);
verify(vaultListView).showEnterPasswordDialog(ANOTHER_VAULT_MODEL_WITH_CLOUD);
}
}