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(); throw new CancellationException();
} }
cryptoCloudContentRepositoryFactory.registerCryptor(impl.getVault(), cryptor); Vault vault = aCopyOf(token.getVault()) //
return aCopyOf(token.getVault()) //
.withVersion(impl.getKeyFile().getVersion()) // .withVersion(impl.getKeyFile().getVersion()) //
.withUnlocked(true) //
.build(); .build();
cryptoCloudContentRepositoryFactory.registerCryptor(vault, cryptor);
return vault;
} }
public UnlockTokenImpl createUnlockToken(Vault vault) throws BackendException { 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> </intent-filter>
</activity> </activity>
<activity android:name=".ui.activity.UnlockVaultActivity"
android:theme="@style/TransparentAlertDialogCustom"
android:label=""/>
<activity android:name=".ui.activity.EmptyDirIdFileInfoActivity" /> <activity android:name=".ui.activity.EmptyDirIdFileInfoActivity" />
<!-- Settings --> <!-- Settings -->
<activity android:name=".ui.activity.BiometricAuthSettingsActivity" /> <activity android:name=".ui.activity.BiometricAuthSettingsActivity" />
<activity android:name=".ui.activity.CloudConnectionListActivity" /> <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.SharedFilesActivity;
import org.cryptomator.presentation.ui.activity.SplashActivity; import org.cryptomator.presentation.ui.activity.SplashActivity;
import org.cryptomator.presentation.ui.activity.TextEditorActivity; 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.VaultListActivity;
import org.cryptomator.presentation.ui.activity.WebDavAddOrChangeActivity; import org.cryptomator.presentation.ui.activity.WebDavAddOrChangeActivity;
import org.cryptomator.presentation.ui.fragment.AutoUploadChooseVaultFragment; 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.SetPasswordFragment;
import org.cryptomator.presentation.ui.fragment.SharedFilesFragment; import org.cryptomator.presentation.ui.fragment.SharedFilesFragment;
import org.cryptomator.presentation.ui.fragment.TextEditorFragment; 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.VaultListFragment;
import org.cryptomator.presentation.ui.fragment.WebDavAddOrChangeFragment; import org.cryptomator.presentation.ui.fragment.WebDavAddOrChangeFragment;
import org.cryptomator.presentation.workflow.AddExistingVaultWorkflow; import org.cryptomator.presentation.workflow.AddExistingVaultWorkflow;
@ -114,4 +116,8 @@ public interface ActivityComponent {
void inject(AutoUploadChooseVaultFragment autoUploadChooseVaultFragment); void inject(AutoUploadChooseVaultFragment autoUploadChooseVaultFragment);
void inject(LicenseCheckActivity licenseCheckActivity); 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 get() = !vault.isUnlocked
val position: Int val position: Int
get() = vault.position get() = vault.position
val version: Int
get() = vault.version
fun toVault(): Vault { fun toVault(): Vault {
return 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.GetDecryptedCloudForVaultUseCase
import org.cryptomator.domain.usecases.cloud.GetRootFolderUseCase import org.cryptomator.domain.usecases.cloud.GetRootFolderUseCase
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase 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.Callback
import org.cryptomator.presentation.R import org.cryptomator.presentation.R
import org.cryptomator.presentation.exception.ExceptionHandlers import org.cryptomator.presentation.exception.ExceptionHandlers
import org.cryptomator.presentation.intent.ChooseCloudNodeSettings import org.cryptomator.presentation.intent.ChooseCloudNodeSettings
import org.cryptomator.presentation.intent.Intents import org.cryptomator.presentation.intent.Intents
import org.cryptomator.presentation.intent.UnlockVaultIntent
import org.cryptomator.presentation.model.CloudFolderModel import org.cryptomator.presentation.model.CloudFolderModel
import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.model.CloudModel
import org.cryptomator.presentation.model.ProgressModel
import org.cryptomator.presentation.model.VaultModel import org.cryptomator.presentation.model.VaultModel
import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper
import org.cryptomator.presentation.ui.activity.view.AutoUploadChooseVaultView import org.cryptomator.presentation.ui.activity.view.AutoUploadChooseVaultView
@ -32,8 +29,6 @@ class AutoUploadChooseVaultPresenter @Inject constructor( //
private val getVaultListUseCase: GetVaultListUseCase, // private val getVaultListUseCase: GetVaultListUseCase, //
private val getRootFolderUseCase: GetRootFolderUseCase, // private val getRootFolderUseCase: GetRootFolderUseCase, //
private val getDecryptedCloudForVaultUseCase: GetDecryptedCloudForVaultUseCase, // private val getDecryptedCloudForVaultUseCase: GetDecryptedCloudForVaultUseCase, //
private val unlockVaultUseCase: UnlockVaultUseCase, //
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase, //
private val cloudFolderModelMapper: CloudFolderModelMapper, // private val cloudFolderModelMapper: CloudFolderModelMapper, //
private val sharedPreferencesHandler: SharedPreferencesHandler, // private val sharedPreferencesHandler: SharedPreferencesHandler, //
private val authenticationExceptionHandler: AuthenticationExceptionHandler, // private val authenticationExceptionHandler: AuthenticationExceptionHandler, //
@ -83,11 +78,24 @@ class AutoUploadChooseVaultPresenter @Inject constructor( //
decryptedCloudFor(authenticatedVault) decryptedCloudFor(authenticatedVault)
} else { } else {
if (!isPaused) { 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) { private fun decryptedCloudFor(vault: Vault) {
getDecryptedCloudForVaultUseCase // getDecryptedCloudForVaultUseCase //
.withVault(vault) // .withVault(vault) //
@ -151,49 +159,11 @@ class AutoUploadChooseVaultPresenter @Inject constructor( //
location?.let { view?.showChosenLocation(it) } 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 { enum class AuthenticationState {
CHOOSE_LOCATION, INIT_ROOT CHOOSE_LOCATION, INIT_ROOT
} }
init { init {
unsubscribeOnDestroy(getVaultListUseCase) unsubscribeOnDestroy(getVaultListUseCase, getRootFolderUseCase, getDecryptedCloudForVaultUseCase)
} }
} }

View File

@ -2,21 +2,21 @@ package org.cryptomator.presentation.presenter
import android.content.Intent import android.content.Intent
import android.provider.Settings import android.provider.Settings
import org.cryptomator.cryptolib.api.InvalidPassphraseException
import org.cryptomator.domain.Cloud import org.cryptomator.domain.Cloud
import org.cryptomator.domain.Vault import org.cryptomator.domain.Vault
import org.cryptomator.domain.di.PerView 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.generator.Callback
import org.cryptomator.presentation.exception.ExceptionHandlers import org.cryptomator.presentation.exception.ExceptionHandlers
import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.intent.Intents
import org.cryptomator.presentation.model.ProgressModel import org.cryptomator.presentation.intent.UnlockVaultIntent
import org.cryptomator.presentation.model.VaultModel import org.cryptomator.presentation.model.VaultModel
import org.cryptomator.presentation.ui.activity.view.BiometricAuthSettingsView import org.cryptomator.presentation.ui.activity.view.BiometricAuthSettingsView
import org.cryptomator.presentation.workflow.ActivityResult import org.cryptomator.presentation.workflow.ActivityResult
import org.cryptomator.presentation.workflow.AuthenticationExceptionHandler
import org.cryptomator.util.SharedPreferencesHandler import org.cryptomator.util.SharedPreferencesHandler
import java.util.* import java.util.ArrayList
import javax.inject.Inject import javax.inject.Inject
import timber.log.Timber import timber.log.Timber
@ -24,13 +24,9 @@ import timber.log.Timber
class BiometricAuthSettingsPresenter @Inject constructor( // class BiometricAuthSettingsPresenter @Inject constructor( //
private val getVaultListUseCase: GetVaultListUseCase, // private val getVaultListUseCase: GetVaultListUseCase, //
private val saveVaultUseCase: SaveVaultUseCase, // private val saveVaultUseCase: SaveVaultUseCase, //
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase, //
private val checkVaultPasswordUseCase: CheckVaultPasswordUseCase, //
private val unlockVaultUseCase: UnlockVaultUseCase, //
private val lockVaultUseCase: LockVaultUseCase, // private val lockVaultUseCase: LockVaultUseCase, //
exceptionMappings: ExceptionHandlers, // exceptionMappings: ExceptionHandlers, //
private val sharedPreferencesHandler: SharedPreferencesHandler, // private val sharedPreferencesHandler: SharedPreferencesHandler) : Presenter<BiometricAuthSettingsView>(exceptionMappings) {
private val authenticationExceptionHandler: AuthenticationExceptionHandler) : Presenter<BiometricAuthSettingsView>(exceptionMappings) {
fun loadVaultList() { fun loadVaultList() {
updateVaultListView() updateVaultListView()
@ -49,92 +45,56 @@ class BiometricAuthSettingsPresenter @Inject constructor( //
fun updateVaultEntityWithChangedBiometricAuthSettings(vaultModel: VaultModel, useBiometricAuth: Boolean) { fun updateVaultEntityWithChangedBiometricAuthSettings(vaultModel: VaultModel, useBiometricAuth: Boolean) {
if (useBiometricAuth) { if (useBiometricAuth) {
view?.showEnterPasswordDialog(VaultModel(vaultModel.toVault())) verifyPassword(vaultModel)
} else { } else {
removePasswordAndSave(vaultModel.toVault()) removePasswordAndSave(vaultModel.toVault())
} }
} }
fun verifyPassword(vaultModel: VaultModel) { private fun verifyPassword(vaultModel: VaultModel) {
Timber.tag("BiomtricAuthSettngsPres").i("Checking entered vault password") Timber.tag("BiomtricAuthSettngsPres").i("Checking entered vault password")
if (vaultModel.isLocked) { if (vaultModel.isLocked) {
unlockVault(vaultModel) requestActivityResult( //
ActivityResultCallbacks.vaultUnlockedBiometricAuthPres(vaultModel), //
Intents.unlockVaultIntent().withVaultModel(vaultModel).withVaultAction(UnlockVaultIntent.VaultAction.UNLOCK_FOR_BIOMETRIC_AUTH))
} else { } 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) { @Callback
view?.showProgress(ProgressModel.GENERIC) fun vaultUnlockedBiometricAuthPres(result: ActivityResult, vaultModel: VaultModel) {
checkVaultPasswordUseCase // val cloud = result.intent().getSerializableExtra(SINGLE_RESULT) as Cloud
.withVault(vaultModel.toVault()) // val password = result.intent().getStringExtra(UnlockVaultPresenter.PASSWORD)
.andPassword(vaultModel.password) // val vault = Vault.aCopyOf(vaultModel.toVault()).withCloud(cloud).withSavedPassword(password).build()
.run(object : DefaultResultHandler<Boolean>() { when {
override fun onSuccess(passwordCorrect: Boolean) { result.isResultOk -> requestActivityResult( //
if (passwordCorrect) { ActivityResultCallbacks.encryptVaultPassword(vaultModel), //
Timber.tag("BiomtricAuthSettngsPres").i("Password is correct") Intents.unlockVaultIntent().withVaultModel(VaultModel(vault)).withVaultAction(UnlockVaultIntent.VaultAction.ENCRYPT_PASSWORD))
onPasswordCheckSucceeded(vaultModel) else -> TODO("Not yet implemented")
} 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 @Callback
fun unlockVaultAfterAuth(result: ActivityResult, vault: Vault?) { fun encryptVaultPassword(result: ActivityResult, vaultModel: VaultModel) {
val cloud = result.getSingleResult(CloudModel::class.java).toCloud() val tmpVault = result.intent().getSerializableExtra(SINGLE_RESULT) as VaultModel
val vaultWithUpdatedCloud = Vault.aCopyOf(vault).withCloud(cloud).build() val vault = Vault.aCopyOf(vaultModel.toVault()).withSavedPassword(tmpVault.password).build()
unlockVault(VaultModel(vaultWithUpdatedCloud)) when {
result.isResultOk -> saveVault(vault)
else -> TODO("Not yet implemented")
}
} }
private fun onPasswordCheckSucceeded(vaultModel: VaultModel) { private fun saveVault(vault: Vault?) {
view?.showBiometricAuthenticationDialog(vaultModel)
}
fun saveVault(vault: Vault?) {
saveVaultUseCase // saveVaultUseCase //
.withVault(vault) // .withVault(vault) //
.run(object : ProgressCompletingResultHandler<Vault>() { .run(object : ProgressCompletingResultHandler<Vault>() {
@ -145,8 +105,7 @@ class BiometricAuthSettingsPresenter @Inject constructor( //
} }
fun switchedGeneralBiometricAuthSettings(isChecked: Boolean) { fun switchedGeneralBiometricAuthSettings(isChecked: Boolean) {
sharedPreferencesHandler // sharedPreferencesHandler.changeUseBiometricAuthentication(isChecked)
.changeUseBiometricAuthentication(isChecked)
if (isChecked) { if (isChecked) {
loadVaultList() loadVaultList()
} else { } else {
@ -173,32 +132,15 @@ class BiometricAuthSettingsPresenter @Inject constructor( //
fun onSetupBiometricAuthInSystemClicked() { fun onSetupBiometricAuthInSystemClicked() {
val openSecuritySettings = Intent(Settings.ACTION_SECURITY_SETTINGS) val openSecuritySettings = Intent(Settings.ACTION_SECURITY_SETTINGS)
requestActivityResult(ActivityResultCallbacks.onSetupFingerCompleted(), openSecuritySettings) requestActivityResult(ActivityResultCallbacks.onSetupBiometricAuthInSystemCompleted(), openSecuritySettings)
} }
@Callback @Callback
fun onSetupFingerCompleted(result: ActivityResult?) { fun onSetupBiometricAuthInSystemCompleted(result: ActivityResult?) {
view?.showSetupBiometricAuthDialog() 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 { 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.GetDecryptedCloudForVaultUseCase
import org.cryptomator.domain.usecases.cloud.* import org.cryptomator.domain.usecases.cloud.*
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase 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.Callback
import org.cryptomator.generator.InstanceState import org.cryptomator.generator.InstanceState
import org.cryptomator.presentation.R import org.cryptomator.presentation.R
import org.cryptomator.presentation.exception.ExceptionHandlers import org.cryptomator.presentation.exception.ExceptionHandlers
import org.cryptomator.presentation.intent.ChooseCloudNodeSettings import org.cryptomator.presentation.intent.ChooseCloudNodeSettings
import org.cryptomator.presentation.intent.Intents import org.cryptomator.presentation.intent.Intents
import org.cryptomator.presentation.intent.UnlockVaultIntent
import org.cryptomator.presentation.model.* import org.cryptomator.presentation.model.*
import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper
import org.cryptomator.presentation.model.mappers.ProgressModelMapper 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.AuthenticationExceptionHandler
import org.cryptomator.presentation.workflow.PermissionsResult import org.cryptomator.presentation.workflow.PermissionsResult
import org.cryptomator.util.Optional import org.cryptomator.util.Optional
import org.cryptomator.util.SharedPreferencesHandler
import org.cryptomator.util.file.FileCacheUtils import org.cryptomator.util.file.FileCacheUtils
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -36,14 +33,11 @@ import timber.log.Timber
@PerView @PerView
class SharedFilesPresenter @Inject constructor( // class SharedFilesPresenter @Inject constructor( //
private val getVaultListUseCase: GetVaultListUseCase, // private val getVaultListUseCase: GetVaultListUseCase, //
private val unlockVaultUseCase: UnlockVaultUseCase, //
private val getRootFolderUseCase: GetRootFolderUseCase, // private val getRootFolderUseCase: GetRootFolderUseCase, //
private val getDecryptedCloudForVaultUseCase: GetDecryptedCloudForVaultUseCase, // private val getDecryptedCloudForVaultUseCase: GetDecryptedCloudForVaultUseCase, //
private val uploadFilesUseCase: UploadFilesUseCase, // private val uploadFilesUseCase: UploadFilesUseCase, //
private val getCloudListUseCase: GetCloudListUseCase, // private val getCloudListUseCase: GetCloudListUseCase, //
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase, //
private val contentResolverUtil: ContentResolverUtil, // private val contentResolverUtil: ContentResolverUtil, //
private val sharedPreferencesHandler: SharedPreferencesHandler, //
private val fileCacheUtils: FileCacheUtils, // private val fileCacheUtils: FileCacheUtils, //
private val authenticationExceptionHandler: AuthenticationExceptionHandler, // private val authenticationExceptionHandler: AuthenticationExceptionHandler, //
private val cloudFolderModelMapper: CloudFolderModelMapper, // private val cloudFolderModelMapper: CloudFolderModelMapper, //
@ -128,6 +122,28 @@ class SharedFilesPresenter @Inject constructor( //
vaultModel?.let { onCloudOfVaultAuthenticated(it.toVault()) } 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) { private fun decryptedCloudFor(vault: Vault) {
getDecryptedCloudForVaultUseCase // getDecryptedCloudForVaultUseCase //
.withVault(vault) // .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) { private fun setLocation(location: CloudFolderModel) {
this.location = location 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() { fun onChooseLocationPressed() {
authenticate(selectedVault) authenticate(selectedVault)
} }
@ -410,25 +390,6 @@ class SharedFilesPresenter @Inject constructor( //
view?.closeDialog() 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 { private enum class AuthenticationState {
CHOOSE_LOCATION, INIT_ROOT CHOOSE_LOCATION, INIT_ROOT
} }
@ -444,11 +405,9 @@ class SharedFilesPresenter @Inject constructor( //
init { init {
unsubscribeOnDestroy( // unsubscribeOnDestroy( //
getRootFolderUseCase, // getRootFolderUseCase, //
unlockVaultUseCase, //
getVaultListUseCase, // getVaultListUseCase, //
getDecryptedCloudForVaultUseCase, // getDecryptedCloudForVaultUseCase, //
uploadFilesUseCase, // uploadFilesUseCase, //
getCloudListUseCase, // getCloudListUseCase)
removeStoredVaultPasswordsUseCase)
} }
} }

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.Context
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Handler
import androidx.biometric.BiometricManager
import org.cryptomator.data.cloud.crypto.CryptoCloud import org.cryptomator.data.cloud.crypto.CryptoCloud
import org.cryptomator.data.util.NetworkConnectionCheck import org.cryptomator.data.util.NetworkConnectionCheck
import org.cryptomator.domain.Cloud import org.cryptomator.domain.Cloud
import org.cryptomator.domain.CloudFolder import org.cryptomator.domain.CloudFolder
import org.cryptomator.domain.Vault import org.cryptomator.domain.Vault
import org.cryptomator.domain.di.PerView 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.license.LicenseNotValidException
import org.cryptomator.domain.exception.update.SSLHandshakePreAndroid5UpdateCheckException import org.cryptomator.domain.exception.update.SSLHandshakePreAndroid5UpdateCheckException
import org.cryptomator.domain.usecases.DoLicenseCheckUseCase 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.NoOpResultHandler
import org.cryptomator.domain.usecases.UpdateCheck import org.cryptomator.domain.usecases.UpdateCheck
import org.cryptomator.domain.usecases.cloud.GetRootFolderUseCase 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.DeleteVaultUseCase
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase import org.cryptomator.domain.usecases.vault.GetVaultListUseCase
import org.cryptomator.domain.usecases.vault.LockVaultUseCase import org.cryptomator.domain.usecases.vault.LockVaultUseCase
import org.cryptomator.domain.usecases.vault.MoveVaultPositionUseCase 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.RenameVaultUseCase
import org.cryptomator.domain.usecases.vault.SaveVaultUseCase 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.Callback
import org.cryptomator.presentation.BuildConfig import org.cryptomator.presentation.BuildConfig
import org.cryptomator.presentation.R import org.cryptomator.presentation.R
import org.cryptomator.presentation.exception.ExceptionHandlers import org.cryptomator.presentation.exception.ExceptionHandlers
import org.cryptomator.presentation.intent.Intents import org.cryptomator.presentation.intent.Intents
import org.cryptomator.presentation.intent.UnlockVaultIntent
import org.cryptomator.presentation.model.CloudModel import org.cryptomator.presentation.model.CloudModel
import org.cryptomator.presentation.model.CloudTypeModel import org.cryptomator.presentation.model.CloudTypeModel
import org.cryptomator.presentation.model.ProgressModel import org.cryptomator.presentation.model.ProgressModel
import org.cryptomator.presentation.model.ProgressStateModel
import org.cryptomator.presentation.model.VaultModel import org.cryptomator.presentation.model.VaultModel
import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper
import org.cryptomator.presentation.service.AutoUploadService 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.presentation.workflow.Workflow
import org.cryptomator.util.Optional import org.cryptomator.util.Optional
import org.cryptomator.util.SharedPreferencesHandler import org.cryptomator.util.SharedPreferencesHandler
import java.io.Serializable
import javax.inject.Inject import javax.inject.Inject
import timber.log.Timber import timber.log.Timber
@ -76,15 +65,11 @@ class VaultListPresenter @Inject constructor( //
private val renameVaultUseCase: RenameVaultUseCase, // private val renameVaultUseCase: RenameVaultUseCase, //
private val lockVaultUseCase: LockVaultUseCase, // private val lockVaultUseCase: LockVaultUseCase, //
private val getDecryptedCloudForVaultUseCase: GetDecryptedCloudForVaultUseCase, // private val getDecryptedCloudForVaultUseCase: GetDecryptedCloudForVaultUseCase, //
private val prepareUnlockUseCase: PrepareUnlockUseCase, //
private val unlockVaultUseCase: UnlockVaultUseCase, //
private val getRootFolderUseCase: GetRootFolderUseCase, // private val getRootFolderUseCase: GetRootFolderUseCase, //
private val addExistingVaultWorkflow: AddExistingVaultWorkflow, // private val addExistingVaultWorkflow: AddExistingVaultWorkflow, //
private val createNewVaultWorkflow: CreateNewVaultWorkflow, // private val createNewVaultWorkflow: CreateNewVaultWorkflow, //
private val saveVaultUseCase: SaveVaultUseCase, // private val saveVaultUseCase: SaveVaultUseCase, //
private val moveVaultPositionUseCase: MoveVaultPositionUseCase, // private val moveVaultPositionUseCase: MoveVaultPositionUseCase, //
private val changePasswordUseCase: ChangePasswordUseCase, //
private val removeStoredVaultPasswordsUseCase: RemoveStoredVaultPasswordsUseCase, //
private val licenseCheckUseCase: DoLicenseCheckUseCase, // private val licenseCheckUseCase: DoLicenseCheckUseCase, //
private val updateCheckUseCase: DoUpdateCheckUseCase, // private val updateCheckUseCase: DoUpdateCheckUseCase, //
private val updateUseCase: DoUpdateUseCase, // private val updateUseCase: DoUpdateUseCase, //
@ -96,31 +81,14 @@ class VaultListPresenter @Inject constructor( //
exceptionMappings: ExceptionHandlers) : Presenter<VaultListView>(exceptionMappings) { exceptionMappings: ExceptionHandlers) : Presenter<VaultListView>(exceptionMappings) {
private var vaultAction: VaultAction? = null 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<*>> { override fun workflows(): Iterable<Workflow<*>> {
return listOf(addExistingVaultWorkflow, createNewVaultWorkflow) return listOf(addExistingVaultWorkflow, createNewVaultWorkflow)
} }
override fun destroyed() {
super.destroyed()
if (retryUnlockHandler != null) {
running = false
retryUnlockHandler?.removeCallbacks(null)
}
}
fun onWindowFocusChanged(hasFocus: Boolean) { fun onWindowFocusChanged(hasFocus: Boolean) {
if (hasFocus) { if (hasFocus) {
loadVaultList() loadVaultList()
if (retryUnlockHandler != null) {
running = false
retryUnlockHandler?.removeCallbacks(null)
}
} }
} }
@ -257,11 +225,6 @@ class VaultListPresenter @Inject constructor( //
renameVault(VaultModel(vaultWithUpdatedCloud), newVaultName) renameVault(VaultModel(vaultWithUpdatedCloud), newVaultName)
} }
fun onUnlockCanceled() {
prepareUnlockUseCase.unsubscribe()
unlockVaultUseCase.cancel()
}
private fun browseFilesOf(vault: VaultModel) { private fun browseFilesOf(vault: VaultModel) {
getDecryptedCloudForVaultUseCase // getDecryptedCloudForVaultUseCase //
.withVault(vault.toVault()) // .withVault(vault.toVault()) //
@ -318,7 +281,6 @@ class VaultListPresenter @Inject constructor( //
} }
fun onVaultClicked(vault: VaultModel) { fun onVaultClicked(vault: VaultModel) {
startedUsingPrepareUnlock = sharedPreferencesHandler.backgroundUnlockPreparation()
startVaultAction(vault, VaultAction.UNLOCK) startVaultAction(vault, VaultAction.UNLOCK)
} }
@ -378,7 +340,6 @@ class VaultListPresenter @Inject constructor( //
when (vaultAction) { when (vaultAction) {
VaultAction.UNLOCK -> requireUserAuthentication(authenticatedVaultModel) VaultAction.UNLOCK -> requireUserAuthentication(authenticatedVaultModel)
VaultAction.RENAME -> view?.showRenameDialog(authenticatedVaultModel) VaultAction.RENAME -> view?.showRenameDialog(authenticatedVaultModel)
VaultAction.CHANGE_PASSWORD -> view?.showChangePasswordDialog(authenticatedVaultModel)
} }
vaultAction = null vaultAction = null
} }
@ -387,83 +348,22 @@ class VaultListPresenter @Inject constructor( //
view?.addOrUpdateVault(authenticatedVault) view?.addOrUpdateVault(authenticatedVault)
if (authenticatedVault.isLocked) { if (authenticatedVault.isLocked) {
if (!isPaused) { if (!isPaused) {
if (canUseBiometricOn(authenticatedVault)) { requestActivityResult( //
if (startedUsingPrepareUnlock) { ActivityResultCallbacks.vaultUnlockedVaultList(), //
startPrepareUnlockUseCase(authenticatedVault.toVault()) Intents.unlockVaultIntent().withVaultModel(authenticatedVault).withVaultAction(UnlockVaultIntent.VaultAction.UNLOCK))
}
view?.showBiometricDialog(authenticatedVault)
} else {
startPrepareUnlockUseCase(authenticatedVault.toVault())
view?.showEnterPasswordDialog(authenticatedVault)
}
} }
} else { } else {
browseFilesOf(authenticatedVault) browseFilesOf(authenticatedVault)
} }
} }
fun startPrepareUnlockUseCase(vault: Vault) { @Callback
pendingUnlock = null fun vaultUnlockedVaultList(result: ActivityResult) {
prepareUnlockUseCase // val cloud = result.intent().getSerializableExtra(SINGLE_RESULT) as Cloud
.withVault(vault) // when {
.run(object : DefaultResultHandler<UnlockToken>() { result.isResultOk -> navigateToVaultContent(cloud)
override fun onSuccess(unlockToken: UnlockToken) { else -> TODO("Not yet implemented")
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)
}
})
} }
private fun navigateToVaultContent(cloud: Cloud) { private fun navigateToVaultContent(cloud: Cloud) {
@ -488,51 +388,6 @@ class VaultListPresenter @Inject constructor( //
} else false } 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() { fun onAddExistingVault() {
addExistingVaultWorkflow.start() addExistingVaultWorkflow.start()
} }
@ -548,60 +403,11 @@ class VaultListPresenter @Inject constructor( //
} }
fun onChangePasswordClicked(vaultModel: VaultModel) { fun onChangePasswordClicked(vaultModel: VaultModel) {
startVaultAction(vaultModel, VaultAction.CHANGE_PASSWORD) Intents
} .unlockVaultIntent()
.withVaultModel(vaultModel)
fun onChangePasswordClicked(vaultModel: VaultModel, oldPassword: String?, newPassword: String?) { .withVaultAction(UnlockVaultIntent.VaultAction.CHANGE_PASSWORD)
view?.showProgress(ProgressModel(ProgressStateModel.CHANGING_PASSWORD)) .startActivity(this)
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")
}
})
} }
fun onVaultSettingsClicked(vaultModel: VaultModel) { 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 { private enum class VaultAction {
UNLOCK, RENAME, CHANGE_PASSWORD UNLOCK, RENAME
} }
fun installUpdate() { 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 { init {
unsubscribeOnDestroy( // unsubscribeOnDestroy( //
deleteVaultUseCase, // deleteVaultUseCase, //
@ -743,9 +494,6 @@ class VaultListPresenter @Inject constructor( //
getVaultListUseCase, // getVaultListUseCase, //
saveVaultUseCase, // saveVaultUseCase, //
moveVaultPositionUseCase, // moveVaultPositionUseCase, //
removeStoredVaultPasswordsUseCase, //
unlockVaultUseCase, //
prepareUnlockUseCase, //
licenseCheckUseCase, // licenseCheckUseCase, //
updateCheckUseCase, // updateCheckUseCase, //
updateUseCase) updateUseCase)

View File

@ -1,7 +1,5 @@
package org.cryptomator.presentation.ui.activity package org.cryptomator.presentation.ui.activity
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import org.cryptomator.generator.Activity import org.cryptomator.generator.Activity
import org.cryptomator.presentation.R 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.model.VaultModel
import org.cryptomator.presentation.presenter.AutoUploadChooseVaultPresenter import org.cryptomator.presentation.presenter.AutoUploadChooseVaultPresenter
import org.cryptomator.presentation.ui.activity.view.AutoUploadChooseVaultView 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.dialog.NotEnoughVaultsDialog
import org.cryptomator.presentation.ui.fragment.AutoUploadChooseVaultFragment import org.cryptomator.presentation.ui.fragment.AutoUploadChooseVaultFragment
import org.cryptomator.presentation.util.BiometricAuthentication
import javax.inject.Inject import javax.inject.Inject
import kotlinx.android.synthetic.main.toolbar_layout.toolbar import kotlinx.android.synthetic.main.toolbar_layout.toolbar
@Activity @Activity
class AutoUploadChooseVaultActivity : BaseActivity(), // class AutoUploadChooseVaultActivity : BaseActivity(), //
AutoUploadChooseVaultView, // AutoUploadChooseVaultView, //
NotEnoughVaultsDialog.Callback, // NotEnoughVaultsDialog.Callback {
EnterPasswordDialog.Callback,
BiometricAuthentication.Callback {
@Inject @Inject
lateinit var presenter: AutoUploadChooseVaultPresenter 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>) { override fun displayVaults(vaults: List<VaultModel>) {
@ -69,41 +62,5 @@ class AutoUploadChooseVaultActivity : BaseActivity(), //
autoUploadChooseVaultFragment().showChosenLocation(location) 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 private fun autoUploadChooseVaultFragment(): AutoUploadChooseVaultFragment = getCurrentFragment(R.id.fragmentContainer) as AutoUploadChooseVaultFragment
} }

View File

@ -1,28 +1,20 @@
package org.cryptomator.presentation.ui.activity package org.cryptomator.presentation.ui.activity
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.biometric.BiometricManager import androidx.biometric.BiometricManager
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import org.cryptomator.domain.Vault
import org.cryptomator.generator.Activity import org.cryptomator.generator.Activity
import org.cryptomator.presentation.R import org.cryptomator.presentation.R
import org.cryptomator.presentation.model.VaultModel import org.cryptomator.presentation.model.VaultModel
import org.cryptomator.presentation.presenter.BiometricAuthSettingsPresenter import org.cryptomator.presentation.presenter.BiometricAuthSettingsPresenter
import org.cryptomator.presentation.ui.activity.view.BiometricAuthSettingsView 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.EnrollSystemBiometricDialog
import org.cryptomator.presentation.ui.dialog.EnterPasswordDialog
import org.cryptomator.presentation.ui.fragment.BiometricAuthSettingsFragment import org.cryptomator.presentation.ui.fragment.BiometricAuthSettingsFragment
import org.cryptomator.presentation.util.BiometricAuthentication
import javax.inject.Inject import javax.inject.Inject
import kotlinx.android.synthetic.main.toolbar_layout.toolbar import kotlinx.android.synthetic.main.toolbar_layout.toolbar
@Activity @Activity
class BiometricAuthSettingsActivity : BaseActivity(), // class BiometricAuthSettingsActivity : BaseActivity(), //
EnterPasswordDialog.Callback, //
BiometricAuthSettingsView, // BiometricAuthSettingsView, //
BiometricAuthentication.Callback, //
EnrollSystemBiometricDialog.Callback { EnrollSystemBiometricDialog.Callback {
@Inject @Inject
@ -42,10 +34,6 @@ class BiometricAuthSettingsActivity : BaseActivity(), //
} }
} }
override fun showBiometricAuthKeyInvalidatedDialog() {
showDialog(BiometricAuthKeyInvalidatedDialog.newInstance())
}
override fun createFragment(): Fragment? = BiometricAuthSettingsFragment() override fun createFragment(): Fragment? = BiometricAuthSettingsFragment()
override fun renderVaultList(vaultModelCollection: List<VaultModel>) { override fun renderVaultList(vaultModelCollection: List<VaultModel>) {
@ -56,30 +44,6 @@ class BiometricAuthSettingsActivity : BaseActivity(), //
biometricAuthSettingsFragment().clearVaultList() 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 private fun biometricAuthSettingsFragment(): BiometricAuthSettingsFragment = getCurrentFragment(R.id.fragmentContainer) as BiometricAuthSettingsFragment
override fun onSetupBiometricAuthInSystemClicked() { override fun onSetupBiometricAuthInSystemClicked() {
@ -89,17 +53,4 @@ class BiometricAuthSettingsActivity : BaseActivity(), //
override fun onCancelSetupBiometricAuthInSystemClicked() { override fun onCancelSetupBiometricAuthInSystemClicked() {
finish() 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
import android.content.Intent.ACTION_SEND_MULTIPLE import android.content.Intent.ACTION_SEND_MULTIPLE
import android.net.Uri import android.net.Uri
import android.os.Build
import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import org.cryptomator.generator.Activity import org.cryptomator.generator.Activity
import org.cryptomator.presentation.R 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.model.VaultModel
import org.cryptomator.presentation.presenter.SharedFilesPresenter import org.cryptomator.presentation.presenter.SharedFilesPresenter
import org.cryptomator.presentation.ui.activity.view.SharedFilesView 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.NotEnoughVaultsDialog
import org.cryptomator.presentation.ui.dialog.ReplaceDialog import org.cryptomator.presentation.ui.dialog.ReplaceDialog
import org.cryptomator.presentation.ui.dialog.UploadCloudFileDialog import org.cryptomator.presentation.ui.dialog.UploadCloudFileDialog
import org.cryptomator.presentation.ui.fragment.SharedFilesFragment import org.cryptomator.presentation.ui.fragment.SharedFilesFragment
import org.cryptomator.presentation.util.BiometricAuthentication
import java.lang.String.format import java.lang.String.format
import java.util.ArrayList import java.util.ArrayList
import javax.inject.Inject import javax.inject.Inject
@ -32,8 +27,6 @@ import timber.log.Timber
@Activity @Activity
class SharedFilesActivity : BaseActivity(), // class SharedFilesActivity : BaseActivity(), //
SharedFilesView, // SharedFilesView, //
EnterPasswordDialog.Callback, //
BiometricAuthentication.Callback, //
ReplaceDialog.Callback, // ReplaceDialog.Callback, //
NotEnoughVaultsDialog.Callback, // NotEnoughVaultsDialog.Callback, //
UploadCloudFileDialog.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) { public override fun onMenuItemSelected(itemId: Int): Boolean = when (itemId) {
android.R.id.home -> { android.R.id.home -> {
@ -150,16 +143,6 @@ class SharedFilesActivity : BaseActivity(), //
private fun sharedFilesFragment(): SharedFilesFragment = getCurrentFragment(R.id.fragmentContainer) as SharedFilesFragment 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) { override fun showReplaceDialog(existingFiles: List<String>, size: Int) {
ReplaceDialog.withContext(this).show(existingFiles, size) ReplaceDialog.withContext(this).show(existingFiles, size)
} }
@ -168,22 +151,10 @@ class SharedFilesActivity : BaseActivity(), //
sharedFilesFragment().showChosenLocation(folder) sharedFilesFragment().showChosenLocation(folder)
} }
override fun showBiometricAuthKeyInvalidatedDialog() {
showDialog(BiometricAuthKeyInvalidatedDialog.newInstance())
}
override fun showUploadDialog(uploadingFiles: Int) { override fun showUploadDialog(uploadingFiles: Int) {
showDialog(UploadCloudFileDialog.newInstance(uploadingFiles)) showDialog(UploadCloudFileDialog.newInstance(uploadingFiles))
} }
override fun onUnlockClick(vaultModel: VaultModel, password: String) {
presenter.onUnlockPressed(vaultModel, password)
}
override fun onUnlockCanceled() {
presenter.onUnlockCanceled()
}
override fun onReplacePositiveClicked() { override fun onReplacePositiveClicked() {
presenter.onReplaceExistingFilesPressed() presenter.onReplaceExistingFilesPressed()
} }
@ -209,20 +180,6 @@ class SharedFilesActivity : BaseActivity(), //
finish() 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() { override fun onUploadCanceled() {
presenter.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.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Build
import android.view.View import android.view.View
import androidx.annotation.RequiresApi
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import org.cryptomator.domain.Vault import org.cryptomator.domain.Vault
import org.cryptomator.generator.Activity 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.callback.VaultListCallback
import org.cryptomator.presentation.ui.dialog.AskForLockScreenDialog import org.cryptomator.presentation.ui.dialog.AskForLockScreenDialog
import org.cryptomator.presentation.ui.dialog.BetaConfirmationDialog 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.UpdateAppAvailableDialog
import org.cryptomator.presentation.ui.dialog.UpdateAppDialog import org.cryptomator.presentation.ui.dialog.UpdateAppDialog
import org.cryptomator.presentation.ui.dialog.VaultDeleteConfirmationDialog 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.dialog.VaultRenameDialog
import org.cryptomator.presentation.ui.fragment.VaultListFragment import org.cryptomator.presentation.ui.fragment.VaultListFragment
import org.cryptomator.presentation.ui.layout.ObscuredAwareCoordinatorLayout.Listener import org.cryptomator.presentation.ui.layout.ObscuredAwareCoordinatorLayout.Listener
import org.cryptomator.presentation.util.BiometricAuthentication
import java.util.Locale import java.util.Locale
import javax.inject.Inject import javax.inject.Inject
import kotlinx.android.synthetic.main.activity_layout_obscure_aware.activityRootView 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(), // class VaultListActivity : BaseActivity(), //
VaultListView, // VaultListView, //
VaultListCallback, // VaultListCallback, //
BiometricAuthentication.Callback, //
AskForLockScreenDialog.Callback, // AskForLockScreenDialog.Callback, //
ChangePasswordDialog.Callback, // VaultNotFoundDialog.Callback, //
VaultNotFoundDialog.Callback,
UpdateAppAvailableDialog.Callback, // UpdateAppAvailableDialog.Callback, //
UpdateAppDialog.Callback, // UpdateAppDialog.Callback, //
BetaConfirmationDialog.Callback { BetaConfirmationDialog.Callback {
@ -59,8 +51,6 @@ class VaultListActivity : BaseActivity(), //
@InjectIntent @InjectIntent
lateinit var vaultListIntent: VaultListIntent lateinit var vaultListIntent: VaultListIntent
private var biometricAuthentication: BiometricAuthentication? = null
override fun onWindowFocusChanged(hasFocus: Boolean) { override fun onWindowFocusChanged(hasFocus: Boolean) {
super.onWindowFocusChanged(hasFocus) super.onWindowFocusChanged(hasFocus)
vaultListPresenter.onWindowFocusChanged(hasFocus) vaultListPresenter.onWindowFocusChanged(hasFocus)
@ -125,40 +115,6 @@ class VaultListActivity : BaseActivity(), //
showDialog(VaultRenameDialog.newInstance(vaultModel)) 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) { override fun rowMoved(fromPosition: Int, toPosition: Int) {
vaultListFragment().rowMoved(fromPosition, toPosition) vaultListFragment().rowMoved(fromPosition, toPosition)
} }
@ -209,14 +165,6 @@ class VaultListActivity : BaseActivity(), //
vaultListPresenter.onCreateVault() vaultListPresenter.onCreateVault()
} }
override fun onUnlockClick(vaultModel: VaultModel, password: String) {
vaultListPresenter.onUnlockClick(vaultModel, password)
}
override fun onUnlockCanceled() {
vaultListPresenter.onUnlockCanceled()
}
override fun onDeleteVaultClick(vaultModel: VaultModel) { override fun onDeleteVaultClick(vaultModel: VaultModel) {
VaultDeleteConfirmationDialog.newInstance(vaultModel) // VaultDeleteConfirmationDialog.newInstance(vaultModel) //
.show(supportFragmentManager, "VaultDeleteConfirmationDialog") .show(supportFragmentManager, "VaultDeleteConfirmationDialog")
@ -249,10 +197,6 @@ class VaultListActivity : BaseActivity(), //
private fun vaultListFragment(): VaultListFragment = // private fun vaultListFragment(): VaultListFragment = //
getCurrentFragment(R.id.fragmentContainer) as 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) { override fun onDeleteMissingVaultClicked(vault: Vault) {
vaultListPresenter.onDeleteMissingVaultClicked(vault) vaultListPresenter.onDeleteMissingVaultClicked(vault)
} }
@ -279,21 +223,4 @@ class VaultListActivity : BaseActivity(), //
override fun onAskForBetaConfirmationFinished() { override fun onAskForBetaConfirmationFinished() {
sharedPreferencesHandler.setBetaScreenDialogAlreadyShown() 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 displayDialogUnableToUploadFiles()
fun displayVaults(vaults: List<VaultModel>) fun displayVaults(vaults: List<VaultModel>)
fun showChosenLocation(location: CloudFolderModel) 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 renderVaultList(vaultModelCollection: List<VaultModel>)
fun clearVaultList() fun clearVaultList()
fun showBiometricAuthenticationDialog(vaultModel: VaultModel)
fun showEnterPasswordDialog(vaultModel: VaultModel)
fun showSetupBiometricAuthDialog() fun showSetupBiometricAuthDialog()
fun showBiometricAuthKeyInvalidatedDialog()
} }

View File

@ -11,10 +11,8 @@ interface SharedFilesView : View {
fun displayVaults(vaults: List<VaultModel>) fun displayVaults(vaults: List<VaultModel>)
fun displayFilesToUpload(sharedFiles: List<SharedFileModel>) fun displayFilesToUpload(sharedFiles: List<SharedFileModel>)
fun displayDialogUnableToUploadFiles() fun displayDialogUnableToUploadFiles()
fun showEnterPasswordDialog(vault: VaultModel)
fun showReplaceDialog(existingFiles: List<String>, size: Int) fun showReplaceDialog(existingFiles: List<String>, size: Int)
fun showChosenLocation(folder: CloudFolderModel) fun showChosenLocation(folder: CloudFolderModel)
fun showBiometricAuthKeyInvalidatedDialog()
fun showUploadDialog(uploadingFiles: Int) 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 addOrUpdateVault(vault: VaultModel)
fun renameVault(vaultModel: VaultModel) fun renameVault(vaultModel: VaultModel)
fun navigateToVaultContent(vault: VaultModel, decryptedRoot: CloudFolderModel) 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 showVaultSettingsDialog(vaultModel: VaultModel)
fun showAddVaultBottomSheet() fun showAddVaultBottomSheet()
fun showRenameDialog(vaultModel: VaultModel) fun showRenameDialog(vaultModel: VaultModel)
fun showBiometricAuthKeyInvalidatedDialog()
fun isVaultLocked(vaultModel: VaultModel): Boolean fun isVaultLocked(vaultModel: VaultModel): Boolean
fun cancelBasicAuthIfRunning()
fun stoppedBiometricAuthDuringCloudAuthentication(): Boolean
fun rowMoved(fromPosition: Int, toPosition: Int) fun rowMoved(fromPosition: Int, toPosition: Int)
fun vaultMoved(vaults: List<VaultModel>) 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.AddVaultBottomSheet
import org.cryptomator.presentation.ui.bottomsheet.SettingsVaultBottomSheet 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.VaultDeleteConfirmationDialog
import org.cryptomator.presentation.ui.dialog.VaultRenameDialog 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> <item name="colorAccent">@color/colorPrimary</item>
</style> </style>
<style name="TransparentAlertDialogCustom" parent="AlertDialogCustom">
<item name="android:windowBackground">@android:color/transparent</item>
</style>
<style name="Toolbar.Theme" parent="ThemeOverlay.AppCompat.Dark.ActionBar"> <style name="Toolbar.Theme" parent="ThemeOverlay.AppCompat.Dark.ActionBar">
<item name="colorAccent">@color/textColorWhite</item> <item name="colorAccent">@color/textColorWhite</item>
</style> </style>

View File

@ -13,17 +13,13 @@ import org.cryptomator.domain.usecases.DoUpdateUseCase;
import org.cryptomator.domain.usecases.GetDecryptedCloudForVaultUseCase; import org.cryptomator.domain.usecases.GetDecryptedCloudForVaultUseCase;
import org.cryptomator.domain.usecases.ResultHandler; import org.cryptomator.domain.usecases.ResultHandler;
import org.cryptomator.domain.usecases.cloud.GetRootFolderUseCase; 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.DeleteVaultUseCase;
import org.cryptomator.domain.usecases.vault.GetVaultListUseCase; import org.cryptomator.domain.usecases.vault.GetVaultListUseCase;
import org.cryptomator.domain.usecases.vault.LockVaultUseCase; import org.cryptomator.domain.usecases.vault.LockVaultUseCase;
import org.cryptomator.domain.usecases.vault.MoveVaultPositionUseCase; 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.RenameVaultUseCase;
import org.cryptomator.domain.usecases.vault.SaveVaultUseCase; import org.cryptomator.domain.usecases.vault.SaveVaultUseCase;
import org.cryptomator.domain.usecases.vault.UnlockToken; import org.cryptomator.domain.usecases.vault.UnlockToken;
import org.cryptomator.domain.usecases.vault.UnlockVaultUseCase;
import org.cryptomator.presentation.exception.ExceptionHandlers; import org.cryptomator.presentation.exception.ExceptionHandlers;
import org.cryptomator.presentation.model.VaultModel; import org.cryptomator.presentation.model.VaultModel;
import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper; import org.cryptomator.presentation.model.mappers.CloudFolderModelMapper;
@ -98,17 +94,12 @@ public class VaultListPresenterTest {
private LockVaultUseCase lockVaultUseCase = Mockito.mock(LockVaultUseCase.class); private LockVaultUseCase lockVaultUseCase = Mockito.mock(LockVaultUseCase.class);
private LockVaultUseCase.Launcher lockVaultUseCaseLauncher = Mockito.mock(LockVaultUseCase.Launcher.class); private LockVaultUseCase.Launcher lockVaultUseCaseLauncher = Mockito.mock(LockVaultUseCase.Launcher.class);
private GetDecryptedCloudForVaultUseCase getDecryptedCloudForVaultUseCase = Mockito.mock(GetDecryptedCloudForVaultUseCase.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 UnlockToken unlockToken = Mockito.mock(UnlockToken.class);
private UnlockVaultUseCase unlockVaultUseCase = Mockito.mock(UnlockVaultUseCase.class);
private GetRootFolderUseCase getRootFolderUseCase = Mockito.mock(GetRootFolderUseCase.class); private GetRootFolderUseCase getRootFolderUseCase = Mockito.mock(GetRootFolderUseCase.class);
private AddExistingVaultWorkflow addExistingVaultWorkflow = Mockito.mock(AddExistingVaultWorkflow.class); private AddExistingVaultWorkflow addExistingVaultWorkflow = Mockito.mock(AddExistingVaultWorkflow.class);
private CreateNewVaultWorkflow createNewVaultWorkflow = Mockito.mock(CreateNewVaultWorkflow.class); private CreateNewVaultWorkflow createNewVaultWorkflow = Mockito.mock(CreateNewVaultWorkflow.class);
private SaveVaultUseCase saveVaultUseCase = Mockito.mock(SaveVaultUseCase.class); private SaveVaultUseCase saveVaultUseCase = Mockito.mock(SaveVaultUseCase.class);
private MoveVaultPositionUseCase moveVaultPositionUseCase = Mockito.mock(MoveVaultPositionUseCase.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 DoLicenseCheckUseCase doLicenceCheckUsecase = Mockito.mock(DoLicenseCheckUseCase.class);
private DoUpdateCheckUseCase updateCheckUseCase = Mockito.mock(DoUpdateCheckUseCase.class); private DoUpdateCheckUseCase updateCheckUseCase = Mockito.mock(DoUpdateCheckUseCase.class);
private DoUpdateUseCase updateUseCase = Mockito.mock(DoUpdateUseCase.class); private DoUpdateUseCase updateUseCase = Mockito.mock(DoUpdateUseCase.class);
@ -126,15 +117,11 @@ public class VaultListPresenterTest {
renameVaultUseCase, // renameVaultUseCase, //
lockVaultUseCase, // lockVaultUseCase, //
getDecryptedCloudForVaultUseCase, // getDecryptedCloudForVaultUseCase, //
prepareUnlockUseCase, //
unlockVaultUseCase, //
getRootFolderUseCase, // getRootFolderUseCase, //
addExistingVaultWorkflow, // addExistingVaultWorkflow, //
createNewVaultWorkflow, // createNewVaultWorkflow, //
saveVaultUseCase, // saveVaultUseCase, //
moveVaultPositionUseCase, // moveVaultPositionUseCase, //
changePasswordUseCase, //
removeStoredVaultPasswordsUseCase, //
doLicenceCheckUsecase, // doLicenceCheckUsecase, //
updateCheckUseCase, // updateCheckUseCase, //
updateUseCase, // updateUseCase, //
@ -241,43 +228,4 @@ public class VaultListPresenterTest {
Mockito.any()); 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);
}
} }