4ea8c4a8f6
This hopefully also prevents some NPEs from google play crash reports.
414 lines
12 KiB
Java
414 lines
12 KiB
Java
/*
|
|
* Copyright (C) 2017 Schürmann & Breitmoser GbR
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it under the terms of the GNU General Public License as published by
|
|
* the Free Software Foundation, either version 3 of the License, or
|
|
* (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
package org.sufficientlysecure.keychain.ui.token;
|
|
|
|
|
|
import android.arch.lifecycle.LifecycleOwner;
|
|
import android.content.Context;
|
|
import android.net.Uri;
|
|
import android.os.Handler;
|
|
|
|
import org.sufficientlysecure.keychain.operations.results.GenericOperationResult;
|
|
import org.sufficientlysecure.keychain.operations.results.OperationResult;
|
|
import org.sufficientlysecure.keychain.operations.results.OperationResult.OperationLog;
|
|
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo;
|
|
import org.sufficientlysecure.keychain.securitytoken.SecurityTokenInfo.TokenType;
|
|
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenContract.ManageSecurityTokenMvpPresenter;
|
|
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenContract.ManageSecurityTokenMvpView;
|
|
import org.sufficientlysecure.keychain.ui.token.ManageSecurityTokenFragment.StatusLine;
|
|
import org.sufficientlysecure.keychain.ui.token.PublicKeyRetriever.KeyRetrievalResult;
|
|
import org.sufficientlysecure.keychain.ui.util.PermissionsUtil;
|
|
|
|
|
|
class ManageSecurityTokenPresenter implements ManageSecurityTokenMvpPresenter {
|
|
private final Context context;
|
|
|
|
private final ManageSecurityTokenViewModel viewModel;
|
|
private final LifecycleOwner lifecycleOwner;
|
|
|
|
|
|
private ManageSecurityTokenMvpView view;
|
|
|
|
private boolean checkedKeyStatus;
|
|
private boolean searchedLocally;
|
|
private boolean searchedAtUri;
|
|
private boolean searchedKeyservers;
|
|
|
|
private byte[] importKeyData;
|
|
private Long masterKeyId;
|
|
|
|
private OperationLog log;
|
|
private Uri selectedContentUri;
|
|
|
|
ManageSecurityTokenPresenter(Context context, LifecycleOwner lifecycleOwner, ManageSecurityTokenViewModel viewModel) {
|
|
this.context = context.getApplicationContext();
|
|
this.lifecycleOwner = lifecycleOwner;
|
|
this.viewModel = viewModel;
|
|
|
|
this.log = new OperationLog();
|
|
viewModel.resetLiveData(lifecycleOwner);
|
|
}
|
|
|
|
@Override
|
|
public void setView(ManageSecurityTokenMvpView view) {
|
|
this.view = view;
|
|
}
|
|
|
|
@Override
|
|
public void detach() {
|
|
this.view = null;
|
|
|
|
viewModel.resetLiveData(lifecycleOwner);
|
|
}
|
|
|
|
@Override
|
|
public void onActivityCreated() {
|
|
if (!checkedKeyStatus || !searchedLocally || !searchedAtUri || !searchedKeyservers) {
|
|
continueSearch();
|
|
}
|
|
}
|
|
|
|
private void continueSearchAfterError() {
|
|
view.statusLineError();
|
|
continueSearch();
|
|
}
|
|
|
|
private void resetAndContinueSearch() {
|
|
checkedKeyStatus = false;
|
|
searchedLocally = false;
|
|
searchedAtUri = false;
|
|
searchedKeyservers = false;
|
|
|
|
viewModel.resetLiveData(lifecycleOwner);
|
|
|
|
view.hideAction();
|
|
view.resetStatusLines();
|
|
continueSearch();
|
|
}
|
|
|
|
private void continueSearch() {
|
|
if (!checkedKeyStatus) {
|
|
boolean keyIsLocked = viewModel.tokenInfo.getVerifyRetries() == 0;
|
|
boolean keyIsEmpty = viewModel.tokenInfo.isEmpty();
|
|
if (keyIsLocked || keyIsEmpty) {
|
|
// the "checking key status" is fake: we only do it if we already know the key is locked
|
|
view.statusLineAdd(StatusLine.CHECK_KEY);
|
|
delayPerformKeyCheck();
|
|
return;
|
|
} else {
|
|
checkedKeyStatus = true;
|
|
}
|
|
}
|
|
|
|
if (!searchedLocally) {
|
|
view.statusLineAdd(StatusLine.SEARCH_LOCAL);
|
|
viewModel.getKeyRetrievalLocal(context).observe(lifecycleOwner, this::processLocalResult);
|
|
return;
|
|
}
|
|
|
|
if (!searchedAtUri) {
|
|
view.statusLineAdd(StatusLine.SEARCH_URI);
|
|
viewModel.getKeyRetrievalUri(context).observe(lifecycleOwner, this::processUriResult);
|
|
return;
|
|
}
|
|
|
|
if (!searchedKeyservers) {
|
|
view.statusLineAdd(StatusLine.SEARCH_KEYSERVER);
|
|
viewModel.getKeyRetrievalKeyserver(context).observe(lifecycleOwner, this::processKeyserverResult);
|
|
return;
|
|
}
|
|
|
|
view.showActionRetryOrFromFile();
|
|
}
|
|
|
|
private void delayPerformKeyCheck() {
|
|
new Handler().postDelayed(() -> {
|
|
if (view == null) {
|
|
return;
|
|
}
|
|
|
|
performKeyCheck();
|
|
}, 1000);
|
|
}
|
|
|
|
private void performKeyCheck() {
|
|
boolean keyIsEmpty = viewModel.tokenInfo.isEmpty();
|
|
boolean putKeyIsSupported = viewModel.tokenInfo.isPutKeySupported();
|
|
|
|
if (keyIsEmpty && !putKeyIsSupported) {
|
|
view.statusLineOk();
|
|
view.showActionUnsupportedToken();
|
|
return;
|
|
}
|
|
|
|
if (keyIsEmpty) {
|
|
boolean tokenIsAdminLocked = viewModel.tokenInfo.getVerifyAdminRetries() == 0;
|
|
if (tokenIsAdminLocked) {
|
|
view.statusLineError();
|
|
view.showActionLocked(0);
|
|
return;
|
|
}
|
|
|
|
view.statusLineOk();
|
|
view.showActionEmptyToken();
|
|
return;
|
|
}
|
|
|
|
boolean keyIsLocked = viewModel.tokenInfo.getVerifyRetries() == 0;
|
|
if (keyIsLocked) {
|
|
view.statusLineError();
|
|
|
|
int unlockAttemptsLeft = viewModel.tokenInfo.getVerifyAdminRetries();
|
|
view.showActionLocked(unlockAttemptsLeft);
|
|
return;
|
|
}
|
|
|
|
view.statusLineOk();
|
|
|
|
checkedKeyStatus = true;
|
|
continueSearch();
|
|
}
|
|
|
|
@Override
|
|
public void onClickUnlockToken() {
|
|
view.showAdminPinDialog();
|
|
}
|
|
|
|
@Override
|
|
public void onMenuClickChangePin() {
|
|
if (!checkedKeyStatus) {
|
|
return;
|
|
}
|
|
|
|
if (viewModel.tokenInfo.getVerifyAdminRetries() == 0) {
|
|
view.showErrorCannotUnlock();
|
|
return;
|
|
}
|
|
|
|
view.showAdminPinDialog();
|
|
}
|
|
|
|
@Override
|
|
public void onInputAdminPin(String adminPin, String newPin) {
|
|
view.operationChangePinSecurityToken(adminPin, newPin);
|
|
}
|
|
|
|
@Override
|
|
public void onClickUnlockTokenImpossible() {
|
|
view.showErrorCannotUnlock();
|
|
}
|
|
|
|
private void processLocalResult(KeyRetrievalResult result) {
|
|
searchedLocally = true;
|
|
processResult(result);
|
|
}
|
|
|
|
|
|
private void processUriResult(KeyRetrievalResult result) {
|
|
searchedAtUri = true;
|
|
processResult(result);
|
|
}
|
|
|
|
private void processKeyserverResult(KeyRetrievalResult result) {
|
|
searchedKeyservers = true;
|
|
processResult(result);
|
|
}
|
|
|
|
private void processContentUriResult(KeyRetrievalResult result) {
|
|
processResult(result);
|
|
}
|
|
|
|
private void processResult(KeyRetrievalResult result) {
|
|
log.add(result.getOperationResult(), 0);
|
|
|
|
if (result.isSuccess()) {
|
|
processResultSuccess(result);
|
|
} else {
|
|
continueSearchAfterError();
|
|
}
|
|
}
|
|
|
|
private void processResultSuccess(KeyRetrievalResult result) {
|
|
view.statusLineOk();
|
|
|
|
byte[] importKeyData = result.getKeyData();
|
|
Long masterKeyId = result.getMasterKeyId();
|
|
if (importKeyData != null && masterKeyId != null) {
|
|
view.showActionImport();
|
|
this.importKeyData = importKeyData;
|
|
this.masterKeyId = masterKeyId;
|
|
return;
|
|
}
|
|
|
|
if (masterKeyId != null) {
|
|
this.masterKeyId = masterKeyId;
|
|
view.statusLineAdd(StatusLine.TOKEN_CHECK);
|
|
|
|
promoteKeyWithTokenInfo(masterKeyId);
|
|
return;
|
|
}
|
|
|
|
throw new IllegalArgumentException("Method can only be called with successful result!");
|
|
}
|
|
|
|
private void promoteKeyWithTokenInfo(Long masterKeyId) {
|
|
view.operationPromote(masterKeyId, viewModel.tokenInfo.getAid(), viewModel.tokenInfo.getFingerprints());
|
|
}
|
|
|
|
@Override
|
|
public void onClickImport() {
|
|
view.statusLineAdd(StatusLine.IMPORT);
|
|
view.hideAction();
|
|
view.operationImportKey(importKeyData);
|
|
}
|
|
|
|
@Override
|
|
public void onImportSuccess(OperationResult result) {
|
|
log.add(result, 0);
|
|
|
|
view.statusLineOk();
|
|
view.statusLineAdd(StatusLine.TOKEN_PROMOTE);
|
|
promoteKeyWithTokenInfo(masterKeyId);
|
|
}
|
|
|
|
@Override
|
|
public void onImportError(OperationResult result) {
|
|
log.add(result, 0);
|
|
|
|
view.statusLineError();
|
|
}
|
|
|
|
@Override
|
|
public void onPromoteSuccess(OperationResult result) {
|
|
log.add(result, 0);
|
|
|
|
view.statusLineOk();
|
|
view.showActionViewKey();
|
|
}
|
|
|
|
@Override
|
|
public void onPromoteError(OperationResult result) {
|
|
log.add(result, 0);
|
|
|
|
view.statusLineError();
|
|
}
|
|
|
|
@Override
|
|
public void onClickRetry() {
|
|
resetAndContinueSearch();
|
|
}
|
|
|
|
@Override
|
|
public void onClickViewKey() {
|
|
view.finishAndShowKey(masterKeyId);
|
|
}
|
|
|
|
@Override
|
|
public void onClickResetToken() {
|
|
if (!viewModel.tokenInfo.isResetSupported()) {
|
|
TokenType tokenType = viewModel.tokenInfo.getTokenType();
|
|
boolean isGnukOrNitrokeyStart = tokenType == TokenType.GNUK_OLD || tokenType == TokenType.NITROKEY_START_OLD;
|
|
|
|
view.showErrorCannotReset(isGnukOrNitrokeyStart);
|
|
return;
|
|
}
|
|
|
|
view.showConfirmResetDialog();
|
|
}
|
|
|
|
@Override
|
|
public void onClickConfirmReset() {
|
|
view.operationResetSecurityToken();
|
|
}
|
|
|
|
@Override
|
|
public void onSecurityTokenResetSuccess(SecurityTokenInfo tokenInfo) {
|
|
viewModel.setTokenInfo(context, tokenInfo);
|
|
resetAndContinueSearch();
|
|
}
|
|
|
|
@Override
|
|
public void onSecurityTokenResetCanceled(SecurityTokenInfo tokenInfo) {
|
|
if (tokenInfo != null) {
|
|
viewModel.setTokenInfo(context, tokenInfo);
|
|
resetAndContinueSearch();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onClickSetupToken() {
|
|
view.startCreateKeyForToken(viewModel.tokenInfo);
|
|
}
|
|
|
|
@Override
|
|
public void onSecurityTokenChangePinSuccess(SecurityTokenInfo tokenInfo) {
|
|
viewModel.setTokenInfo(context, tokenInfo);
|
|
resetAndContinueSearch();
|
|
}
|
|
|
|
@Override
|
|
public void onSecurityTokenChangePinCanceled(SecurityTokenInfo tokenInfo) {
|
|
if (tokenInfo != null) {
|
|
viewModel.setTokenInfo(context, tokenInfo);
|
|
resetAndContinueSearch();
|
|
}
|
|
}
|
|
|
|
@Override
|
|
public void onClickLoadFile() {
|
|
view.showFileSelectDialog();
|
|
}
|
|
|
|
@Override
|
|
public void onFileSelected(Uri contentUri) {
|
|
boolean hasReadPermission = PermissionsUtil.checkReadPermission(context, contentUri);
|
|
if (!hasReadPermission) {
|
|
selectedContentUri = contentUri;
|
|
view.requestStoragePermission();
|
|
return;
|
|
}
|
|
|
|
startLoadingFile(contentUri);
|
|
}
|
|
|
|
private void startLoadingFile(Uri contentUri) {
|
|
view.resetStatusLines();
|
|
view.statusLineAdd(StatusLine.SEARCH_CONTENT_URI);
|
|
|
|
viewModel.getKeyRetrievalContentUri(context, contentUri).observe(lifecycleOwner, this::processContentUriResult);
|
|
}
|
|
|
|
@Override
|
|
public void onStoragePermissionGranted() {
|
|
Uri contentUri = selectedContentUri;
|
|
selectedContentUri = null;
|
|
startLoadingFile(contentUri);
|
|
}
|
|
|
|
@Override
|
|
public void onStoragePermissionDenied() {
|
|
selectedContentUri = null;
|
|
}
|
|
|
|
@Override
|
|
public void onMenuClickViewLog() {
|
|
OperationResult result = new GenericOperationResult(GenericOperationResult.RESULT_OK, log);
|
|
view.showDisplayLogActivity(result);
|
|
}
|
|
}
|