Show a dialog if the DB key can't be decrypted due to a keystore error.

This commit is contained in:
akwizgran
2020-02-25 14:56:32 +00:00
parent 1546a05568
commit ed50582e27
25 changed files with 452 additions and 293 deletions

View File

@@ -36,6 +36,7 @@ import org.briarproject.bramble.util.AndroidUtils;
import org.briarproject.bramble.util.StringUtils;
import org.briarproject.briar.android.account.LockManagerImpl;
import org.briarproject.briar.android.keyagreement.ContactExchangeModule;
import org.briarproject.briar.android.login.LoginModule;
import org.briarproject.briar.android.viewmodel.ViewModelModule;
import org.briarproject.briar.api.android.AndroidNotificationManager;
import org.briarproject.briar.api.android.DozeWatchdog;
@@ -64,7 +65,11 @@ import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_ONIO
import static org.briarproject.bramble.api.reporting.ReportingConstants.DEV_PUBLIC_KEY_HEX;
import static org.briarproject.briar.android.TestingConstants.IS_DEBUG_BUILD;
@Module(includes = {ContactExchangeModule.class, ViewModelModule.class})
@Module(includes = {
ContactExchangeModule.class,
LoginModule.class,
ViewModelModule.class
})
public class AppModule {
static class EagerSingletons {

View File

@@ -8,8 +8,6 @@ import org.briarproject.briar.android.controller.BriarController;
import org.briarproject.briar.android.controller.BriarControllerImpl;
import org.briarproject.briar.android.controller.DbController;
import org.briarproject.briar.android.controller.DbControllerImpl;
import org.briarproject.briar.android.login.ChangePasswordController;
import org.briarproject.briar.android.login.ChangePasswordControllerImpl;
import org.briarproject.briar.android.navdrawer.NavDrawerController;
import org.briarproject.briar.android.navdrawer.NavDrawerControllerImpl;
@@ -46,13 +44,6 @@ public class ActivityModule {
return setupController;
}
@ActivityScope
@Provides
ChangePasswordController providePasswordController(
ChangePasswordControllerImpl passwordController) {
return passwordController;
}
@ActivityScope
@Provides
protected BriarController provideBriarController(
@@ -80,5 +71,4 @@ public class ActivityModule {
BriarServiceConnection provideBriarServiceConnection() {
return new BriarServiceConnection();
}
}

View File

@@ -15,27 +15,32 @@ import android.widget.Toast;
import com.google.android.material.textfield.TextInputLayout;
import org.briarproject.bramble.api.crypto.DecryptionResult;
import org.briarproject.briar.R;
import org.briarproject.briar.android.activity.ActivityComponent;
import org.briarproject.briar.android.activity.BriarActivity;
import org.briarproject.briar.android.controller.handler.UiResultHandler;
import org.briarproject.briar.android.util.UiUtils;
import javax.inject.Inject;
import androidx.annotation.NonNull;
import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelProviders;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.widget.Toast.LENGTH_LONG;
import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERROR;
import static org.briarproject.bramble.api.crypto.DecryptionResult.SUCCESS;
import static org.briarproject.bramble.api.crypto.PasswordStrengthEstimator.QUITE_WEAK;
import static org.briarproject.briar.android.login.LoginUtils.createKeyStrengthenerErrorDialog;
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
import static org.briarproject.briar.android.util.UiUtils.setError;
import static org.briarproject.briar.android.util.UiUtils.showSoftKeyboard;
public class ChangePasswordActivity extends BriarActivity
implements OnClickListener, OnEditorActionListener {
@Inject
protected ChangePasswordController passwordController;
ViewModelProvider.Factory viewModelFactory;
private TextInputLayout currentPasswordEntryWrapper;
private TextInputLayout newPasswordEntryWrapper;
@@ -47,11 +52,17 @@ public class ChangePasswordActivity extends BriarActivity
private Button changePasswordButton;
private ProgressBar progress;
// Package access for testing
ChangePasswordViewModel viewModel;
@Override
public void onCreate(Bundle state) {
super.onCreate(state);
setContentView(R.layout.activity_change_password);
viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(ChangePasswordViewModel.class);
currentPasswordEntryWrapper =
findViewById(R.id.current_password_entry_wrapper);
newPasswordEntryWrapper = findViewById(R.id.new_password_entry_wrapper);
@@ -102,13 +113,12 @@ public class ChangePasswordActivity extends BriarActivity
String firstPassword = newPassword.getText().toString();
String secondPassword = newPasswordConfirmation.getText().toString();
boolean passwordsMatch = firstPassword.equals(secondPassword);
float strength =
passwordController.estimatePasswordStrength(firstPassword);
float strength = viewModel.estimatePasswordStrength(firstPassword);
strengthMeter.setStrength(strength);
UiUtils.setError(newPasswordEntryWrapper,
setError(newPasswordEntryWrapper,
getString(R.string.password_too_weak),
firstPassword.length() > 0 && strength < QUITE_WEAK);
UiUtils.setError(newPasswordConfirmationWrapper,
setError(newPasswordConfirmationWrapper,
getString(R.string.passwords_do_not_match),
secondPassword.length() > 0 && !passwordsMatch);
changePasswordButton.setEnabled(
@@ -127,32 +137,34 @@ public class ChangePasswordActivity extends BriarActivity
// Replace the button with a progress bar
changePasswordButton.setVisibility(INVISIBLE);
progress.setVisibility(VISIBLE);
passwordController.changePassword(currentPassword.getText().toString(),
newPassword.getText().toString(),
new UiResultHandler<Boolean>(this) {
@Override
public void onResultUi(@NonNull Boolean result) {
if (result) {
Toast.makeText(ChangePasswordActivity.this,
R.string.password_changed,
Toast.LENGTH_LONG).show();
setResult(RESULT_OK);
supportFinishAfterTransition();
} else {
tryAgain();
}
String curPwd = currentPassword.getText().toString();
String newPwd = newPassword.getText().toString();
viewModel.changePassword(curPwd, newPwd).observeEvent(this, result -> {
if (result == SUCCESS) {
Toast.makeText(ChangePasswordActivity.this,
R.string.password_changed,
LENGTH_LONG).show();
setResult(RESULT_OK);
supportFinishAfterTransition();
} else {
tryAgain(result);
}
});
}
);
}
private void tryAgain() {
UiUtils.setError(currentPasswordEntryWrapper,
getString(R.string.try_again), true);
private void tryAgain(DecryptionResult result) {
changePasswordButton.setVisibility(VISIBLE);
progress.setVisibility(INVISIBLE);
currentPassword.setText("");
// show the keyboard again
showSoftKeyboard(currentPassword);
if (result == KEY_STRENGTHENER_ERROR) {
createKeyStrengthenerErrorDialog(this).show();
} else {
setError(currentPasswordEntryWrapper,
getString(R.string.try_again), true);
currentPassword.setText("");
// show the keyboard again
showSoftKeyboard(currentPassword);
}
}
}

View File

@@ -1,43 +0,0 @@
package org.briarproject.briar.android.login;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.controller.handler.ResultHandler;
import java.util.concurrent.Executor;
import javax.inject.Inject;
@NotNullByDefault
public class ChangePasswordControllerImpl implements ChangePasswordController {
protected final AccountManager accountManager;
protected final Executor ioExecutor;
private final PasswordStrengthEstimator strengthEstimator;
@Inject
ChangePasswordControllerImpl(AccountManager accountManager,
@IoExecutor Executor ioExecutor,
PasswordStrengthEstimator strengthEstimator) {
this.accountManager = accountManager;
this.ioExecutor = ioExecutor;
this.strengthEstimator = strengthEstimator;
}
@Override
public float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
}
@Override
public void changePassword(String oldPassword, String newPassword,
ResultHandler<Boolean> resultHandler) {
ioExecutor.execute(() -> {
boolean changed =
accountManager.changePassword(oldPassword, newPassword);
resultHandler.onResult(changed);
});
}
}

View File

@@ -0,0 +1,53 @@
package org.briarproject.briar.android.login;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.DecryptionResult;
import org.briarproject.bramble.api.crypto.PasswordStrengthEstimator;
import org.briarproject.bramble.api.lifecycle.IoExecutor;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.android.viewmodel.LiveEvent;
import org.briarproject.briar.android.viewmodel.MutableLiveEvent;
import java.util.concurrent.Executor;
import javax.inject.Inject;
import androidx.lifecycle.ViewModel;
import static org.briarproject.bramble.api.crypto.DecryptionResult.SUCCESS;
@NotNullByDefault
public class ChangePasswordViewModel extends ViewModel {
private final AccountManager accountManager;
private final Executor ioExecutor;
private final PasswordStrengthEstimator strengthEstimator;
@Inject
ChangePasswordViewModel(AccountManager accountManager,
@IoExecutor Executor ioExecutor,
PasswordStrengthEstimator strengthEstimator) {
this.accountManager = accountManager;
this.ioExecutor = ioExecutor;
this.strengthEstimator = strengthEstimator;
}
float estimatePasswordStrength(String password) {
return strengthEstimator.estimateStrength(password);
}
LiveEvent<DecryptionResult> changePassword(String oldPassword,
String newPassword) {
MutableLiveEvent<DecryptionResult> result = new MutableLiveEvent<>();
ioExecutor.execute(() -> {
try {
accountManager.changePassword(oldPassword, newPassword);
result.postEvent(SUCCESS);
} catch (DecryptionException e) {
result.postEvent(e.getDecryptionResult());
}
});
return result;
}
}

View File

@@ -0,0 +1,23 @@
package org.briarproject.briar.android.login;
import org.briarproject.briar.android.viewmodel.ViewModelKey;
import androidx.lifecycle.ViewModel;
import dagger.Binds;
import dagger.Module;
import dagger.multibindings.IntoMap;
@Module
public abstract class LoginModule {
@Binds
@IntoMap
@ViewModelKey(StartupViewModel.class)
abstract ViewModel bindStartupViewModel(StartupViewModel viewModel);
@Binds
@IntoMap
@ViewModelKey(ChangePasswordViewModel.class)
abstract ViewModel bindChangePasswordViewModel(
ChangePasswordViewModel viewModel);
}

View File

@@ -0,0 +1,30 @@
package org.briarproject.briar.android.login;
import android.content.Context;
import android.graphics.drawable.Drawable;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import org.briarproject.briar.R;
import androidx.appcompat.app.AlertDialog;
import static androidx.core.content.ContextCompat.getColor;
import static androidx.core.content.ContextCompat.getDrawable;
import static androidx.core.graphics.drawable.DrawableCompat.setTint;
import static java.util.Objects.requireNonNull;
@NotNullByDefault
class LoginUtils {
static AlertDialog createKeyStrengthenerErrorDialog(Context ctx) {
AlertDialog.Builder builder =
new AlertDialog.Builder(ctx, R.style.BriarDialogTheme);
Drawable icon = getDrawable(ctx, R.drawable.alerts_and_states_error);
setTint(requireNonNull(icon), getColor(ctx, R.color.color_primary));
builder.setIcon(icon);
builder.setTitle(R.string.dialog_title_cannot_check_password);
builder.setMessage(R.string.dialog_message_cannot_check_password);
builder.setPositiveButton(R.string.ok, null);
return builder.create();
}
}

View File

@@ -12,6 +12,7 @@ import android.widget.ProgressBar;
import com.google.android.material.textfield.TextInputEditText;
import com.google.android.material.textfield.TextInputLayout;
import org.briarproject.bramble.api.crypto.DecryptionResult;
import org.briarproject.bramble.api.nullsafety.MethodsNotNullByDefault;
import org.briarproject.bramble.api.nullsafety.ParametersNotNullByDefault;
import org.briarproject.briar.R;
@@ -28,6 +29,9 @@ import androidx.lifecycle.ViewModelProviders;
import static android.view.View.INVISIBLE;
import static android.view.View.VISIBLE;
import static android.view.inputmethod.EditorInfo.IME_ACTION_DONE;
import static org.briarproject.bramble.api.crypto.DecryptionResult.KEY_STRENGTHENER_ERROR;
import static org.briarproject.bramble.api.crypto.DecryptionResult.SUCCESS;
import static org.briarproject.briar.android.login.LoginUtils.createKeyStrengthenerErrorDialog;
import static org.briarproject.briar.android.util.UiUtils.enterPressed;
import static org.briarproject.briar.android.util.UiUtils.hideSoftKeyboard;
import static org.briarproject.briar.android.util.UiUtils.setError;
@@ -58,12 +62,13 @@ public class PasswordFragment extends BaseFragment implements TextWatcher {
@Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment_password, container,
false);
false);
viewModel = ViewModelProviders.of(requireActivity(), viewModelFactory)
.get(StartupViewModel.class);
viewModel.getPasswordValidated().observeEvent(this, valid -> {
if (!valid) onPasswordInvalid();
viewModel.getPasswordValidated().observeEvent(this, result -> {
if (result != SUCCESS) onPasswordInvalid(result);
});
signInButton = v.findViewById(R.id.btn_sign_in);
@@ -107,18 +112,20 @@ public class PasswordFragment extends BaseFragment implements TextWatcher {
viewModel.validatePassword(password.getText().toString());
}
private void onPasswordInvalid() {
setError(input, getString(R.string.try_again), true);
private void onPasswordInvalid(DecryptionResult result) {
signInButton.setVisibility(VISIBLE);
progress.setVisibility(INVISIBLE);
password.setText(null);
// show the keyboard again
showSoftKeyboard(password);
if (result == KEY_STRENGTHENER_ERROR) {
createKeyStrengthenerErrorDialog(requireContext()).show();
} else {
setError(input, getString(R.string.try_again), true);
password.setText(null);
// show the keyboard again
showSoftKeyboard(password);
}
}
public void onForgottenPasswordClick() {
// TODO Encapsulate the dialog in a re-usable fragment
private void onForgottenPasswordClick() {
AlertDialog.Builder builder = new AlertDialog.Builder(requireContext(),
R.style.BriarDialogTheme);
builder.setTitle(R.string.dialog_title_lost_password);

View File

@@ -3,6 +3,8 @@ package org.briarproject.briar.android.login;
import android.app.Application;
import org.briarproject.bramble.api.account.AccountManager;
import org.briarproject.bramble.api.crypto.DecryptionException;
import org.briarproject.bramble.api.crypto.DecryptionResult;
import org.briarproject.bramble.api.event.Event;
import org.briarproject.bramble.api.event.EventBus;
import org.briarproject.bramble.api.event.EventListener;
@@ -24,6 +26,7 @@ import androidx.lifecycle.AndroidViewModel;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData;
import static org.briarproject.bramble.api.crypto.DecryptionResult.SUCCESS;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.COMPACTING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.MIGRATING_DATABASE;
import static org.briarproject.bramble.api.lifecycle.LifecycleManager.LifecycleState.STARTING_SERVICES;
@@ -46,7 +49,7 @@ public class StartupViewModel extends AndroidViewModel
@IoExecutor
private final Executor ioExecutor;
private final MutableLiveEvent<Boolean> passwordValidated =
private final MutableLiveEvent<DecryptionResult> passwordValidated =
new MutableLiveEvent<>();
private final MutableLiveEvent<Boolean> accountDeleted =
new MutableLiveEvent<>();
@@ -105,13 +108,17 @@ public class StartupViewModel extends AndroidViewModel
void validatePassword(String password) {
ioExecutor.execute(() -> {
boolean signedIn = accountManager.signIn(password);
passwordValidated.postEvent(signedIn);
if (signedIn) state.postValue(SIGNED_IN);
try {
accountManager.signIn(password);
passwordValidated.postEvent(SUCCESS);
state.postValue(SIGNED_IN);
} catch (DecryptionException e) {
passwordValidated.postEvent(e.getDecryptionResult());
}
});
}
LiveEvent<Boolean> getPasswordValidated() {
LiveEvent<DecryptionResult> getPasswordValidated() {
return passwordValidated;
}

View File

@@ -381,7 +381,7 @@ public class UiUtils {
/**
* Same as {@link #observeOnce(LiveData, LifecycleOwner, Observer)},
* but without a {@link LifecycleOwner}.
*
* <p>
* Warning: Do NOT call from objects that have a lifecycle.
*/
@UiThread
@@ -401,5 +401,4 @@ public class UiUtils {
return ctx.getResources().getConfiguration().getLayoutDirection() ==
LAYOUT_DIRECTION_RTL;
}
}

View File

@@ -4,7 +4,6 @@ import org.briarproject.briar.android.contact.add.remote.AddContactViewModel;
import org.briarproject.briar.android.contact.add.remote.PendingContactListViewModel;
import org.briarproject.briar.android.conversation.ConversationViewModel;
import org.briarproject.briar.android.conversation.ImageViewModel;
import org.briarproject.briar.android.login.StartupViewModel;
import javax.inject.Singleton;
@@ -17,11 +16,6 @@ import dagger.multibindings.IntoMap;
@Module
public abstract class ViewModelModule {
@Binds
@IntoMap
@ViewModelKey(StartupViewModel.class)
abstract ViewModel bindStartupViewModel(StartupViewModel startupViewModel);
@Binds
@IntoMap
@ViewModelKey(ConversationViewModel.class)