From 7e1130635a72f960f46cf0a443419fe9a6d1cf6b Mon Sep 17 00:00:00 2001 From: Marten Rebane <54431068+martenrebane@users.noreply.github.com> Date: Wed, 20 Dec 2023 15:18:04 +0200 Subject: [PATCH] Added proxy functionality --- .../main/diagnostics/DiagnosticsView.java | 61 ++- .../main/settings/SettingsDataStore.java | 102 +++- .../android/main/settings/SettingsView.java | 31 +- .../settings/access/SettingsAccessView.java | 472 ------------------ .../settings/access/siva/SivaSetting.java | 6 - .../settings/proxy/SettingsProxyDialog.java | 275 ++++++++++ .../SettingsRightsScreen.java} | 16 +- .../settings/rights/SettingsRightsView.java | 102 ++++ .../role/SettingsRoleAndAddressView.java | 109 ---- .../{access => signing}/SettingsFragment.java | 4 +- .../SettingsSigningScreen.java} | 15 +- .../settings/signing/SettingsSigningView.java | 246 +++++++++ .../{access => signing}/TsaUrlPreference.java | 15 +- .../TsaUrlPreferenceDialogFragment.java | 34 +- .../{access => signing}/UUIDPreference.java | 16 +- .../UUIDPreferenceDialogFragment.java | 20 +- .../signing/siva/SettingsSivaDialog.java | 365 ++++++++++++++ .../settings/signing/siva/SivaSetting.java | 6 + .../signature/update/SignatureAddSource.java | 9 +- .../update/mobileid/MobileIdOnSubscribe.java | 25 +- .../update/smartid/SmartIdOnSubscribe.java | 28 +- .../main/res/drawable/ic_icon_permissions.xml | 10 + app/src/main/res/drawable/ic_icon_signing.xml | 26 + .../res/layout/certificate_details_screen.xml | 8 +- app/src/main/res/layout/crypto_home.xml | 4 + app/src/main/res/layout/main_home_menu.xml | 3 + app/src/main/res/layout/main_settings.xml | 28 +- .../main/res/layout/main_settings_access.xml | 346 ------------- .../main/res/layout/main_settings_proxy.xml | 208 ++++++++ .../main_settings_proxy_dialog_layout.xml | 237 +++++++++ ...d_address.xml => main_settings_rights.xml} | 67 ++- .../main/res/layout/main_settings_signing.xml | 213 ++++++++ .../main_settings_siva_dialog_layout.xml | 177 +++++++ app/src/main/res/layout/signature_home.xml | 4 + app/src/main/res/values-et/strings.xml | 11 +- app/src/main/res/values-ru/strings.xml | 11 +- app/src/main/res/values/ids.xml | 23 +- app/src/main/res/values/strings.xml | 11 +- ...s_access.xml => main_settings_signing.xml} | 4 +- build.gradle | 1 + common-lib/build.gradle | 1 + .../DigiDoc/common/EncryptedPreferences.java | 27 + .../ee/ria/DigiDoc/common/ManualProxy.java | 47 ++ .../ee/ria/DigiDoc/common/ProxyConfig.java | 27 + .../ee/ria/DigiDoc/common/ProxySetting.java | 7 + .../java/ee/ria/DigiDoc/common/ProxyUtil.java | 71 +++ .../src/main/res/values/donottranslate.xml | 8 + configuration-lib/build.gradle | 1 + .../configuration/ConfigurationConstants.java | 6 + .../configuration/ConfigurationManager.java | 2 +- .../loader/CentralConfigurationClient.java | 107 +++- .../loader/CentralConfigurationLoader.java | 6 +- ...tchAndPackageDefaultConfigurationTask.java | 2 +- .../mobileid/rest/ServiceGenerator.java | 29 +- .../mobileid/service/MobileSignConstants.java | 6 + .../mobileid/service/MobileSignService.java | 34 +- .../java/ee/ria/DigiDoc/sign/SignLib.java | 47 ++ .../smartid/rest/ServiceGenerator.java | 27 +- .../smartid/service/SmartSignConstants.java | 6 + .../smartid/service/SmartSignService.java | 33 +- 60 files changed, 2765 insertions(+), 1078 deletions(-) delete mode 100644 app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsAccessView.java delete mode 100644 app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/siva/SivaSetting.java create mode 100644 app/src/main/java/ee/ria/DigiDoc/android/main/settings/proxy/SettingsProxyDialog.java rename app/src/main/java/ee/ria/DigiDoc/android/main/settings/{access/SettingsAccessScreen.java => rights/SettingsRightsScreen.java} (75%) create mode 100644 app/src/main/java/ee/ria/DigiDoc/android/main/settings/rights/SettingsRightsView.java delete mode 100644 app/src/main/java/ee/ria/DigiDoc/android/main/settings/role/SettingsRoleAndAddressView.java rename app/src/main/java/ee/ria/DigiDoc/android/main/settings/{access => signing}/SettingsFragment.java (97%) rename app/src/main/java/ee/ria/DigiDoc/android/main/settings/{role/SettingsRoleAndAddressScreen.java => signing/SettingsSigningScreen.java} (69%) create mode 100644 app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/SettingsSigningView.java rename app/src/main/java/ee/ria/DigiDoc/android/main/settings/{access => signing}/TsaUrlPreference.java (79%) rename app/src/main/java/ee/ria/DigiDoc/android/main/settings/{access => signing}/TsaUrlPreferenceDialogFragment.java (71%) rename app/src/main/java/ee/ria/DigiDoc/android/main/settings/{access => signing}/UUIDPreference.java (83%) rename app/src/main/java/ee/ria/DigiDoc/android/main/settings/{access => signing}/UUIDPreferenceDialogFragment.java (83%) create mode 100644 app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/siva/SettingsSivaDialog.java create mode 100644 app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/siva/SivaSetting.java create mode 100644 app/src/main/res/drawable/ic_icon_permissions.xml create mode 100644 app/src/main/res/drawable/ic_icon_signing.xml delete mode 100644 app/src/main/res/layout/main_settings_access.xml create mode 100644 app/src/main/res/layout/main_settings_proxy.xml create mode 100644 app/src/main/res/layout/main_settings_proxy_dialog_layout.xml rename app/src/main/res/layout/{main_settings_role_and_address.xml => main_settings_rights.xml} (58%) create mode 100644 app/src/main/res/layout/main_settings_signing.xml create mode 100644 app/src/main/res/layout/main_settings_siva_dialog_layout.xml rename app/src/main/res/xml/{main_settings_access.xml => main_settings_signing.xml} (95%) create mode 100644 common-lib/src/main/java/ee/ria/DigiDoc/common/EncryptedPreferences.java create mode 100644 common-lib/src/main/java/ee/ria/DigiDoc/common/ManualProxy.java create mode 100644 common-lib/src/main/java/ee/ria/DigiDoc/common/ProxyConfig.java create mode 100644 common-lib/src/main/java/ee/ria/DigiDoc/common/ProxySetting.java create mode 100644 common-lib/src/main/java/ee/ria/DigiDoc/common/ProxyUtil.java diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/diagnostics/DiagnosticsView.java b/app/src/main/java/ee/ria/DigiDoc/android/main/diagnostics/DiagnosticsView.java index e90c38182..d349a5236 100644 --- a/app/src/main/java/ee/ria/DigiDoc/android/main/diagnostics/DiagnosticsView.java +++ b/app/src/main/java/ee/ria/DigiDoc/android/main/diagnostics/DiagnosticsView.java @@ -41,6 +41,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.OutputStreamWriter; +import java.net.Proxy; import java.nio.charset.StandardCharsets; import java.nio.file.NoSuchFileException; import java.text.Normalizer; @@ -49,12 +50,14 @@ import java.util.Arrays; import java.util.Date; import java.util.List; +import java.util.concurrent.TimeUnit; import ee.ria.DigiDoc.BuildConfig; import ee.ria.DigiDoc.R; import ee.ria.DigiDoc.android.Activity; import ee.ria.DigiDoc.android.ApplicationApp; import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils; +import ee.ria.DigiDoc.android.main.settings.SettingsDataStore; import ee.ria.DigiDoc.android.utils.ClickableDialogUtil; import ee.ria.DigiDoc.android.utils.TSLException; import ee.ria.DigiDoc.android.utils.TSLUtil; @@ -64,6 +67,10 @@ import ee.ria.DigiDoc.android.utils.navigator.Transaction; import ee.ria.DigiDoc.android.utils.widget.ConfirmationDialog; import ee.ria.DigiDoc.common.FileUtil; +import ee.ria.DigiDoc.common.ManualProxy; +import ee.ria.DigiDoc.common.ProxyConfig; +import ee.ria.DigiDoc.common.ProxySetting; +import ee.ria.DigiDoc.common.ProxyUtil; import ee.ria.DigiDoc.configuration.ConfigurationDateUtil; import ee.ria.DigiDoc.configuration.ConfigurationManagerService; import ee.ria.DigiDoc.configuration.ConfigurationProvider; @@ -72,9 +79,11 @@ import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.disposables.Disposable; import io.reactivex.rxjava3.schedulers.Schedulers; +import okhttp3.Authenticator; import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; +import okhttp3.internal.tls.OkHostnameVerifier; import timber.log.Timber; public final class DiagnosticsView extends CoordinatorLayout implements ContentView { @@ -82,9 +91,12 @@ public final class DiagnosticsView extends CoordinatorLayout implements ContentV private final AppBarLayout appBarLayout; private final NestedScrollView scrollView; private final Toolbar toolbarView; + private static final int DEFAULT_TIMEOUT = 5; + private final Navigator navigator; private final SimpleDateFormat dateFormat; private final ConfirmationDialog diagnosticsRestartConfirmationDialog; + private final SettingsDataStore settingsDataStore; private final ViewDisposables disposables; @@ -112,6 +124,7 @@ public DiagnosticsView(Context context, AttributeSet attrs, int defStyleAttr) { scrollView = findViewById(R.id.scrollView); View saveDiagnosticsButton = findViewById(R.id.configurationSaveButton); navigator = ApplicationApp.component(context).navigator(); + settingsDataStore = ApplicationApp.component(context).settingsDataStore(); ContentView.addInvisibleElement(getContext(), this); @@ -119,7 +132,7 @@ public DiagnosticsView(Context context, AttributeSet attrs, int defStyleAttr) { R.string.main_diagnostics_restart_message, R.id.mainDiagnosticsRestartConfirmationDialog); SwitchCompat activateLogFileGenerating = findViewById(R.id.mainDiagnosticsLogging); - activateLogFileGenerating.setChecked(((Activity) this.getContext()).getSettingsDataStore().getIsLogFileGenerationEnabled()); + activateLogFileGenerating.setChecked(settingsDataStore.getIsLogFileGenerationEnabled()); Button saveLogFileButton = findViewById(R.id.mainDiagnosticsSaveLoggingButton); saveLogFileButton.setVisibility( (activateLogFileGenerating.isChecked() && @@ -150,14 +163,14 @@ public DiagnosticsView(Context context, AttributeSet attrs, int defStyleAttr) { private void fileLogToggleListener(SwitchCompat activateLogFileGenerating) { activateLogFileGenerating.setOnCheckedChangeListener((buttonView, isChecked) -> { Activity activityContext = ((Activity) this.getContext()); - boolean isLogFileGenerationEnabled = activityContext.getSettingsDataStore().getIsLogFileGenerationEnabled(); + boolean isLogFileGenerationEnabled = settingsDataStore.getIsLogFileGenerationEnabled(); if (isChecked) { diagnosticsRestartConfirmationDialog.show(); ClickableDialogUtil.makeLinksInDialogClickable(diagnosticsRestartConfirmationDialog); diagnosticsRestartConfirmationDialog.positiveButtonClicks() .doOnNext(next -> { diagnosticsRestartConfirmationDialog.dismiss(); - activityContext.getSettingsDataStore().setIsLogFileGenerationEnabled(true); + settingsDataStore.setIsLogFileGenerationEnabled(true); activityContext.restartAppWithIntent(activityContext.getIntent(), true); }) .subscribe(); @@ -165,12 +178,12 @@ private void fileLogToggleListener(SwitchCompat activateLogFileGenerating) { .doOnNext(next -> { diagnosticsRestartConfirmationDialog.dismiss(); activateLogFileGenerating.setChecked(false); - activityContext.getSettingsDataStore().setIsLogFileGenerationEnabled(false); + settingsDataStore.setIsLogFileGenerationEnabled(false); }) .subscribe(); } else { - activityContext.getSettingsDataStore().setIsLogFileGenerationEnabled(false); - activityContext.getSettingsDataStore().setIsLogFileGenerationRunning(false); + settingsDataStore.setIsLogFileGenerationEnabled(false); + settingsDataStore.setIsLogFileGenerationRunning(false); if (isLogFileGenerationEnabled) { activityContext.restartAppWithIntent(activityContext.getIntent(), true); } @@ -387,7 +400,7 @@ private void setData(ConfigurationProvider configurationProvider) { } private void appendTslVersion(TextView tslUrlTextView, String tslUrl) { - tslVersionDisposable = getObservableTslVersion(tslUrl ) + tslVersionDisposable = getObservableTslVersion(tslUrl) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( @@ -396,9 +409,35 @@ private void appendTslVersion(TextView tslUrlTextView, String tslUrl) { ); } + private ProxySetting getProxySetting() { + return settingsDataStore.getProxySetting(); + } + + private ManualProxy getManualProxySettings() { + return new ManualProxy( + settingsDataStore.getProxyHost(), + settingsDataStore.getProxyPort(), + settingsDataStore.getProxyUsername(), + settingsDataStore.getProxyPassword(navigator.activity()) + ); + } + private Observable getObservableTslVersion(String tslUrl) { + ProxySetting proxySetting = getProxySetting(); + boolean isProxySSLEnabled = settingsDataStore.getIsProxyForSSLEnabled(); + boolean useHTTPSProxy = ProxyUtil.useHTTPSProxy(isProxySSLEnabled, getManualProxySettings()); + ProxyConfig proxyConfig = ProxyUtil.getProxy(proxySetting, getManualProxySettings()); + return Observable.fromCallable(() -> { - OkHttpClient client = new OkHttpClient(); + OkHttpClient.Builder builder = new OkHttpClient.Builder() + .proxy(proxySetting == ProxySetting.NO_PROXY || !useHTTPSProxy ? Proxy.NO_PROXY : proxyConfig.proxy()) + .proxyAuthenticator(proxySetting == ProxySetting.NO_PROXY || !useHTTPSProxy ? Authenticator.NONE : proxyConfig.authenticator()) + .hostnameVerifier(OkHostnameVerifier.INSTANCE) + .connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) + .readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) + .callTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS) + .writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS); + OkHttpClient client = builder.build(); Request request = new Request.Builder().url(tslUrl).build(); Response response = client.newCall(request).execute(); if (response.isSuccessful() && response.body() != null) { @@ -414,8 +453,10 @@ private Observable getObservableTslVersion(String tslUrl) { } private String getRpUuidText() { - String rpUuid = ((Activity) this.getContext()).getSettingsDataStore().getUuid(); - int uuid = rpUuid.isEmpty() ? R.string.main_diagnostics_rpuuid_default : R.string.main_diagnostics_rpuuid_custom; + String rpUuid = settingsDataStore.getUuid(); + int uuid = rpUuid == null || rpUuid.isEmpty() + ? R.string.main_diagnostics_rpuuid_default + : R.string.main_diagnostics_rpuuid_custom; return getResources().getString(uuid); } diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsDataStore.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsDataStore.java index cda7c5f07..f7e04edee 100644 --- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsDataStore.java +++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsDataStore.java @@ -1,6 +1,9 @@ package ee.ria.DigiDoc.android.main.settings; +import static ee.ria.DigiDoc.common.ProxySetting.NO_PROXY; + import android.app.Application; +import android.content.Context; import android.content.SharedPreferences; import android.content.res.Resources; import android.util.Log; @@ -8,6 +11,8 @@ import androidx.annotation.Nullable; import androidx.preference.PreferenceManager; +import java.io.IOException; +import java.security.GeneralSecurityException; import java.util.Arrays; import java.util.List; import java.util.Locale; @@ -15,7 +20,11 @@ import javax.inject.Inject; import ee.ria.DigiDoc.R; -import ee.ria.DigiDoc.android.main.settings.access.siva.SivaSetting; +import ee.ria.DigiDoc.android.main.settings.signing.siva.SivaSetting; +import ee.ria.DigiDoc.android.utils.ToastUtil; +import ee.ria.DigiDoc.common.EncryptedPreferences; +import ee.ria.DigiDoc.common.ManualProxy; +import ee.ria.DigiDoc.common.ProxySetting; import timber.log.Timber; public final class SettingsDataStore { @@ -322,4 +331,95 @@ public void setSivaCertName(String cert) { public String getSivaCertName() { return preferences.getString(resources.getString(R.string.main_settings_siva_cert_key), ""); } + + public void setProxySetting(ProxySetting proxySetting) { + SharedPreferences.Editor editor = preferences.edit(); + editor.putString(resources.getString(R.string.main_settings_proxy_setting_key), proxySetting.name()); + editor.commit(); + } + + public ProxySetting getProxySetting() { + String settingKey = preferences.getString(resources.getString(R.string.main_settings_proxy_setting_key), NO_PROXY.name()); + try { + return ProxySetting.valueOf(settingKey); + } catch (IllegalArgumentException iae) { + Timber.log(Log.ERROR, iae, "Unable to get proxy setting"); + return NO_PROXY; + } + } + + public void setIsProxyForSSLEnabled(boolean isEnabled) { + SharedPreferences.Editor editor = preferences.edit(); + editor.putBoolean(resources.getString(R.string.main_settings_proxy_ssl_enabled_key), isEnabled); + editor.commit(); + } + + public boolean getIsProxyForSSLEnabled() { + return preferences.getBoolean(resources.getString(R.string.main_settings_proxy_ssl_enabled_key), true); + } + + public void setProxyHost(String host) { + SharedPreferences.Editor editor = preferences.edit(); + editor.putString(resources.getString(R.string.main_settings_proxy_host_key), host); + editor.commit(); + } + + public String getProxyHost() { + return preferences.getString(resources.getString(R.string.main_settings_proxy_host_key), ""); + } + + public void setProxyPort(int port) { + SharedPreferences.Editor editor = preferences.edit(); + editor.putInt(resources.getString(R.string.main_settings_proxy_port_key), port); + editor.commit(); + } + + public int getProxyPort() { + return preferences.getInt(resources.getString(R.string.main_settings_proxy_port_key), 80); + } + + public void setProxyUsername(String username) { + SharedPreferences.Editor editor = preferences.edit(); + editor.putString(resources.getString(R.string.main_settings_proxy_username_key), username); + editor.commit(); + } + + public String getProxyUsername() { + return preferences.getString(resources.getString(R.string.main_settings_proxy_username_key), ""); + } + + public void setProxyPassword(Context context, String password) { + SharedPreferences encryptedPreferences = getEncryptedPreferences(context); + if (encryptedPreferences != null) { + SharedPreferences.Editor editor = encryptedPreferences.edit(); + editor.putString(resources.getString(R.string.main_settings_proxy_password_key), password); + editor.commit(); + } + Timber.log(Log.ERROR, "Unable to set proxy password"); + } + + public String getProxyPassword(Context context) { + SharedPreferences encryptedPreferences = getEncryptedPreferences(context); + if (encryptedPreferences != null) { + return encryptedPreferences.getString(resources.getString(R.string.main_settings_proxy_password_key), ""); + } + Timber.log(Log.ERROR, "Unable to get proxy password"); + return ""; + } + + public ManualProxy getManualProxySettings(Context context) { + return new ManualProxy(getProxyHost(), getProxyPort(), + getProxyUsername(), getProxyPassword(context)); + } + + @Nullable + private static SharedPreferences getEncryptedPreferences(Context context) { + try { + return EncryptedPreferences.getEncryptedPreferences(context); + } catch (GeneralSecurityException | IOException e) { + Timber.log(Log.ERROR, e, "Unable to get encrypted preferences"); + ToastUtil.showError(context, R.string.signature_update_mobile_id_error_general_client); + return null; + } + } } diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsView.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsView.java index 0beeffb58..9df16c919 100644 --- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsView.java +++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/SettingsView.java @@ -20,10 +20,11 @@ import ee.ria.DigiDoc.R; import ee.ria.DigiDoc.android.ApplicationApp; -import ee.ria.DigiDoc.android.main.settings.access.SettingsAccessScreen; -import ee.ria.DigiDoc.android.main.settings.access.SettingsAccessView; -import ee.ria.DigiDoc.android.main.settings.role.SettingsRoleAndAddressScreen; -import ee.ria.DigiDoc.android.main.settings.role.SettingsRoleAndAddressView; +import ee.ria.DigiDoc.android.main.settings.proxy.SettingsProxyDialog; +import ee.ria.DigiDoc.android.main.settings.rights.SettingsRightsScreen; +import ee.ria.DigiDoc.android.main.settings.rights.SettingsRightsView; +import ee.ria.DigiDoc.android.main.settings.signing.SettingsSigningScreen; +import ee.ria.DigiDoc.android.main.settings.signing.SettingsSigningView; import ee.ria.DigiDoc.android.utils.ViewDisposables; import ee.ria.DigiDoc.android.utils.navigator.ContentView; import ee.ria.DigiDoc.android.utils.navigator.Navigator; @@ -40,10 +41,11 @@ public final class SettingsView extends CoordinatorLayout implements ContentView private final ViewDisposables disposables; - private final Button accessCategory; - private final Button roleAndAddressCategory; private final Button defaultSettingsButton; + private final Button accessCategory; + private final Button rightsCategory; + public SettingsView(Context context) { this(context, null); } @@ -62,12 +64,12 @@ public SettingsView(Context context, AttributeSet attrs, int defStyleAttr) { settingsDataStore = ApplicationApp.component(context).settingsDataStore(); disposables = new ViewDisposables(); - accessCategory = findViewById(R.id.mainSettingsAccessCategory); - roleAndAddressCategory = findViewById(R.id.mainSettingsRoleAndAddressCategory); defaultSettingsButton = findViewById(R.id.mainSettingsUseDefaultSettings); defaultSettingsButton.setContentDescription( defaultSettingsButton.getText().toString().toLowerCase()); + accessCategory = findViewById(R.id.mainSettingsSigningCategory); + rightsCategory = findViewById(R.id.mainSettingsRightsCategory); toolbarView.setTitle(R.string.main_settings_title); toolbarView.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material); @@ -75,8 +77,9 @@ public SettingsView(Context context, AttributeSet attrs, int defStyleAttr) { } private void resetToDefaultSettings(SettingsDataStore settingsDataStore) { - SettingsAccessView.resetSettings(getContext(), settingsDataStore); - SettingsRoleAndAddressView.resetSettings(settingsDataStore); + SettingsSigningView.resetSettings(getContext(), settingsDataStore); + SettingsRightsView.resetSettings(settingsDataStore); + SettingsProxyDialog.resetSettings(getContext(), settingsDataStore); } @Override @@ -102,10 +105,10 @@ public void onAttachedToWindow() { navigator.execute(Transaction.pop()))); disposables.add(clicks(accessCategory).subscribe(o -> navigator.execute( - Transaction.push(SettingsAccessScreen.create())))); - disposables.add(clicks(roleAndAddressCategory).subscribe(o -> + Transaction.push(SettingsSigningScreen.create())))); + disposables.add(clicks(rightsCategory).subscribe(o -> navigator.execute( - Transaction.push(SettingsRoleAndAddressScreen.create())))); + Transaction.push(SettingsRightsScreen.create())))); disposables.add(clicks(defaultSettingsButton).subscribe(o -> resetToDefaultSettings(settingsDataStore) )); @@ -116,4 +119,4 @@ public void onDetachedFromWindow() { disposables.detach(); super.onDetachedFromWindow(); } -} +} \ No newline at end of file diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsAccessView.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsAccessView.java deleted file mode 100644 index 4d7d670a9..000000000 --- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsAccessView.java +++ /dev/null @@ -1,472 +0,0 @@ -package ee.ria.DigiDoc.android.main.settings.access; - -import static com.jakewharton.rxbinding4.view.RxView.clicks; -import static com.jakewharton.rxbinding4.widget.RxRadioGroup.checkedChanges; -import static com.jakewharton.rxbinding4.widget.RxTextView.textChanges; -import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks; -import static ee.ria.DigiDoc.android.main.settings.access.siva.SivaSetting.DEFAULT; -import static ee.ria.DigiDoc.android.main.settings.access.siva.SivaSetting.MANUAL; -import static ee.ria.DigiDoc.android.main.settings.util.SettingsUtil.getToolbarImageButton; -import static ee.ria.DigiDoc.android.main.settings.util.SettingsUtil.getToolbarTextView; -import static ee.ria.DigiDoc.common.CommonConstants.DIR_SIVA_CERT; -import static ee.ria.DigiDoc.common.CommonConstants.DIR_TSA_CERT; - -import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.pm.PackageManager; -import android.text.Editable; -import android.util.AttributeSet; -import android.util.Log; -import android.view.View; -import android.widget.Button; -import android.widget.ImageButton; -import android.widget.LinearLayout; -import android.widget.RadioButton; -import android.widget.RadioGroup; -import android.widget.ScrollView; -import android.widget.TextView; -import android.widget.Toolbar; - -import androidx.appcompat.widget.SwitchCompat; -import androidx.coordinatorlayout.widget.CoordinatorLayout; - -import com.google.android.material.appbar.AppBarLayout; -import com.google.android.material.textfield.TextInputEditText; -import com.google.android.material.textfield.TextInputLayout; - -import org.bouncycastle.asn1.x500.RDN; -import org.bouncycastle.asn1.x500.style.BCStyle; -import org.bouncycastle.cert.X509CertificateHolder; -import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; - -import java.io.File; -import java.security.cert.CertificateException; -import java.security.cert.X509Certificate; -import java.text.SimpleDateFormat; -import java.util.Date; -import java.util.Locale; -import java.util.Optional; - -import ee.ria.DigiDoc.R; -import ee.ria.DigiDoc.android.Activity; -import ee.ria.DigiDoc.android.ApplicationApp; -import ee.ria.DigiDoc.android.main.settings.SettingsDataStore; -import ee.ria.DigiDoc.android.main.settings.access.siva.SivaSetting; -import ee.ria.DigiDoc.android.main.settings.create.CertificateAddViewModel; -import ee.ria.DigiDoc.android.main.settings.create.ChooseFileScreen; -import ee.ria.DigiDoc.android.main.settings.create.ViewState; -import ee.ria.DigiDoc.android.signature.detail.CertificateDetailScreen; -import ee.ria.DigiDoc.android.utils.ViewDisposables; -import ee.ria.DigiDoc.android.utils.navigator.Navigator; -import ee.ria.DigiDoc.android.utils.navigator.Transaction; -import ee.ria.DigiDoc.common.CertificateUtil; -import ee.ria.DigiDoc.common.FileUtil; -import ee.ria.DigiDoc.configuration.ConfigurationProvider; -import ee.ria.DigiDoc.configuration.util.FileUtils; -import io.reactivex.rxjava3.core.Observable; -import io.reactivex.rxjava3.subjects.PublishSubject; -import io.reactivex.rxjava3.subjects.Subject; -import timber.log.Timber; - -public final class SettingsAccessView extends CoordinatorLayout { - - private final AppBarLayout appBarLayout; - private final ScrollView scrollView; - private final Toolbar toolbarView; - private final LinearLayout tsaCertContainer; - private final TextView tsaCertIssuedTo; - private final TextView tsaCertValidTo; - private final Button addCertificateButton; - private final Button showCertificateButton; - private X509Certificate tsaCertificate; - - private final RadioGroup sivaServiceChoiceGroup; - private final RadioButton sivaServiceDefaultChoice; - private final RadioButton sivaServiceManualChoice; - private final TextInputLayout sivaServiceUrlLayout; - private final TextInputEditText sivaServiceUrl; - private final TextView sivaCertificateIssuedTo; - private final TextView sivaCertificateValidTo; - - private final LinearLayout sivaServiceCertificateContainer; - private final Button sivaCertificateAddCertificateButton; - private final Button sivaCertificateShowCertificateButton; - private X509Certificate sivaCertificate; - - private final Navigator navigator; - private final SettingsDataStore settingsDataStore; - private final ConfigurationProvider configurationProvider; - - private final ViewDisposables disposables; - private final CertificateAddViewModel viewModel; - - private static boolean isTsaCertificateViewVisible; - private static final Subject isTsaCertificateViewVisibleSubject = PublishSubject.create(); - - private String previousSivaUrl = ""; - - String viewId = String.valueOf(View.generateViewId()); - - public SettingsAccessView(Context context) { - this(context, null); - } - - public SettingsAccessView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SettingsAccessView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - viewModel = ApplicationApp.component(context).navigator() - .viewModel(viewId, CertificateAddViewModel.class); - inflate(context, R.layout.main_settings_access, this); - toolbarView = findViewById(R.id.toolbar); - appBarLayout = findViewById(R.id.appBar); - scrollView = findViewById(R.id.scrollView); - navigator = ApplicationApp.component(context).navigator(); - settingsDataStore = ApplicationApp.component(context).settingsDataStore(); - configurationProvider = ((ApplicationApp) context.getApplicationContext()).getConfigurationProvider(); - disposables = new ViewDisposables(); - - toolbarView.setTitle(R.string.main_settings_access_button); - toolbarView.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material); - toolbarView.setNavigationContentDescription(R.string.back); - - tsaCertContainer = findViewById(R.id.mainSettingsTsaCertificateContainer); - tsaCertIssuedTo = findViewById(R.id.mainSettingsTsaCertificateIssuedTo); - tsaCertValidTo = findViewById(R.id.mainSettingsTsaCertificateValidTo); - addCertificateButton = findViewById(R.id.mainSettingsTsaCertificateAddCertificateButton); - showCertificateButton = findViewById(R.id.mainSettingsTsaCertificateShowCertificateButton); - - addCertificateButton.setContentDescription(addCertificateButton.getText().toString().toLowerCase()); - showCertificateButton.setContentDescription(showCertificateButton.getText().toString().toLowerCase()); - - sivaServiceChoiceGroup = findViewById(R.id.mainSettingsSivaServiceChoiceGroup); - sivaServiceDefaultChoice = findViewById(R.id.mainSettingsSivaServiceDefaultChoice); - sivaServiceManualChoice = findViewById(R.id.mainSettingsSivaServiceManualChoice); - sivaServiceUrlLayout = findViewById(R.id.mainSettingsSivaServiceUrlLayout); - sivaServiceUrl = findViewById(R.id.mainSettingsSivaServiceUrl); - sivaCertificateIssuedTo = findViewById(R.id.mainSettingsSivaCertificateIssuedTo); - sivaCertificateValidTo = findViewById(R.id.mainSettingsSivaCertificateValidTo); - - sivaServiceCertificateContainer = findViewById(R.id.mainSettingsSivaServiceCertificateContainer); - sivaCertificateAddCertificateButton = findViewById(R.id.mainSettingsSivaCertificateAddCertificateButton); - sivaCertificateShowCertificateButton = findViewById(R.id.mainSettingsSivaCertificateShowCertificateButton); - - if (settingsDataStore != null) { - isTsaCertificateViewVisible = settingsDataStore.getIsTsaCertificateViewVisible(); - setTSAContainerViewVisibility(isTsaCertificateViewVisible); - } - - Activity activityContext = (Activity) this.getContext(); - SwitchCompat openAllFileTypesSwitch = findViewById(R.id.mainSettingsOpenAllFileTypes); - if (openAllFileTypesSwitch != null && activityContext != null) { - openAllFileTypesSwitch.setChecked(activityContext.getSettingsDataStore().getIsOpenAllFileTypesEnabled()); - - openAllFileTypesSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { - activityContext.getSettingsDataStore().setIsOpenAllFileTypesEnabled(isChecked); - restartIntent(); - }); - } - - SwitchCompat allowScreenshotsSwitch = findViewById(R.id.mainSettingsAllowScreenshots); - if (allowScreenshotsSwitch != null && activityContext != null) { - allowScreenshotsSwitch.setChecked(activityContext.getSettingsDataStore().getIsScreenshotAllowed()); - - allowScreenshotsSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> { - activityContext.getSettingsDataStore().setIsScreenshotAllowed(isChecked); - restartIntent(); - }); - } - - checkSivaServiceSetting(settingsDataStore, configurationProvider.getSivaUrl()); - } - - private void restartIntent() { - PackageManager packageManager = getContext().getPackageManager(); - Intent intent = packageManager.getLaunchIntentForPackage(getContext().getPackageName()); - assert intent != null; - ComponentName componentName = intent.getComponent(); - Intent restartIntent = Intent.makeRestartActivityTask(componentName); - restartIntent.setAction(Intent.ACTION_CONFIGURATION_CHANGED); - getContext().startActivity(restartIntent); - } - - public void render(ViewState state) { - if (settingsDataStore != null) { - String tsaCertName = settingsDataStore.getTSACertName(); - File tsaFile = FileUtil.getCertFile(getContext(), tsaCertName, DIR_TSA_CERT); - - if (tsaFile != null) { - String fileContents = FileUtils.readFileContent(tsaFile.getPath()); - try { - tsaCertificate = CertificateUtil.x509Certificate(fileContents); - X509CertificateHolder certificateHolder = new JcaX509CertificateHolder(tsaCertificate); - String issuer = getIssuer(certificateHolder); - tsaCertIssuedTo.setText(String.format("%s %s", - getResources().getText(R.string.main_settings_timestamp_cert_issued_to_title), - issuer)); - tsaCertIssuedTo.setContentDescription(String.format("%s %s", - getResources().getText(R.string.main_settings_timestamp_cert_issued_to_title), - issuer).toLowerCase()); - tsaCertValidTo.setText(String.format("%s %s", - getResources().getText(R.string.main_settings_timestamp_cert_valid_to_title), - getFormattedDateTime(certificateHolder.getNotAfter()))); - tsaCertValidTo.setContentDescription(String.format("%s %s", - getResources().getText(R.string.main_settings_timestamp_cert_valid_to_title), - getFormattedDateTime(certificateHolder.getNotAfter())).toLowerCase()); - } catch (CertificateException e) { - Timber.log(Log.ERROR, e, "Unable to get TSA certificate"); - - // Remove invalid files - removeCertificate(tsaFile, settingsDataStore); - - tsaCertIssuedTo.setText(getResources().getText(R.string.main_settings_timestamp_cert_issued_to_title)); - tsaCertValidTo.setText(getResources().getText(R.string.main_settings_timestamp_cert_valid_to_title)); - } - } - - if (sivaServiceUrl.getText() != null) { - previousSivaUrl = sivaServiceUrl.getText().toString(); - } - String sivaCertName = settingsDataStore.getSivaCertName(); - File sivaFile = FileUtil.getCertFile(getContext(), sivaCertName, DIR_SIVA_CERT); - - if (sivaFile != null) { - String fileContents = FileUtils.readFileContent(sivaFile.getPath()); - try { - sivaCertificate = CertificateUtil.x509Certificate(fileContents); - X509CertificateHolder certificateHolder = new JcaX509CertificateHolder(sivaCertificate); - String issuer = getIssuer(certificateHolder); - sivaCertificateIssuedTo.setText(String.format("%s %s", - getResources().getText(R.string.main_settings_timestamp_cert_issued_to_title), - issuer)); - sivaCertificateValidTo.setText(String.format("%s %s", - getResources().getText(R.string.main_settings_timestamp_cert_valid_to_title), - getFormattedDateTime(certificateHolder.getNotAfter()))); - } catch (CertificateException e) { - Timber.log(Log.ERROR, e, "Unable to get SiVa certificate"); - - // Remove invalid files - removeSivaCert(settingsDataStore, sivaCertificateIssuedTo, sivaCertificateValidTo); - } - } - } - } - - private String getIssuer(X509CertificateHolder certificateHolder) { - RDN[] organizationRDNs = certificateHolder.getIssuer().getRDNs(BCStyle.O); - if (organizationRDNs.length > 0) { - return organizationRDNs[0].getFirst().getValue().toString(); - } - RDN[] organizationUnitRDNs = certificateHolder.getIssuer().getRDNs(BCStyle.OU); - if (organizationUnitRDNs.length > 0) { - return organizationUnitRDNs[0].getFirst().getValue().toString(); - } - RDN[] commonNameRDNs = certificateHolder.getIssuer().getRDNs(BCStyle.CN); - if (commonNameRDNs.length > 0) { - return commonNameRDNs[0].getFirst().getValue().toString(); - } - - return "-"; - } - - private static String getFormattedDateTime(Date date) { - try { - SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()); - if (date != null) { - return dateFormat.format(date); - } - } catch (IllegalStateException e) { - Timber.log(Log.ERROR, e, "Unable to format date"); - } - return "-"; - } - - private void saveSivaUrl(String sivaServiceUrl) { - Optional sivaUrl = Optional.ofNullable(sivaServiceUrl) - .filter(text -> !text.isEmpty()); - if (sivaUrl.isPresent()) { - setSivaUrl(settingsDataStore, sivaUrl.get().trim()); - } else { - String defaultSivaUrl = configurationProvider.getSivaUrl(); - setSivaUrl(settingsDataStore, defaultSivaUrl); - setSivaPlaceholderText(defaultSivaUrl); - } - } - - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - - scrollView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - TextView toolbarTextView = getToolbarTextView(toolbarView); - if (toolbarTextView != null) { - toolbarTextView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - } - ImageButton toolbarImageButton = getToolbarImageButton(toolbarView); - if (toolbarImageButton != null) { - toolbarImageButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - } - appBarLayout.postDelayed(() -> { - scrollView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); - if (toolbarImageButton != null) { - toolbarImageButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - }, 1000); - - disposables.attach(); - disposables.add(navigationClicks(toolbarView).subscribe(o -> navigator.execute(Transaction.pop()))); - disposables.add(clicks(showCertificateButton).subscribe(o -> { - if (tsaCertificate != null) { - navigator.execute(Transaction.push(CertificateDetailScreen.create(tsaCertificate))); - } - })); - disposables.add(clicks(addCertificateButton).subscribe(o -> - navigator.execute(Transaction.push(ChooseFileScreen.create(true, false))))); - disposables.add(observeTsaCertificateViewVisibleChanges().subscribe(isVisible -> { - settingsDataStore.setIsTsaCertificateViewVisible(isVisible); - setTSAContainerViewVisibility(isVisible); - })); - disposables.add(checkedChanges(sivaServiceChoiceGroup).subscribe(setting -> - setSivaServiceSetting(settingsDataStore, configurationProvider.getSivaUrl(), setting))); - disposables.add(clicks(sivaCertificateAddCertificateButton).subscribe(o -> - navigator.execute(Transaction.push(ChooseFileScreen.create(false, true))))); - disposables.add(clicks(sivaCertificateShowCertificateButton).subscribe(o -> { - if (sivaCertificate != null) { - navigator.execute(Transaction.push(CertificateDetailScreen.create(sivaCertificate))); - } - })); - disposables.add(textChanges(sivaServiceUrl).subscribe(text -> { - SivaSetting currentSivaSetting = getSivaSetting(settingsDataStore); - if (currentSivaSetting == MANUAL && text.toString().isEmpty() && !previousSivaUrl.isEmpty()) { - removeSivaCert(settingsDataStore, sivaCertificateIssuedTo, sivaCertificateValidTo); - } - previousSivaUrl = text.toString(); - })); - disposables.add(viewModel.viewStates().subscribe(this::render)); - } - - @Override - public void onDetachedFromWindow() { - disposables.detach(); - SivaSetting currentSivaSetting = getSivaSetting(settingsDataStore); - Editable sivaUrl = sivaServiceUrl.getText(); - if (sivaUrl != null) { - saveSivaUrl(currentSivaSetting == DEFAULT ? - configurationProvider.getSivaUrl() : sivaUrl.toString()); - } else { - saveSivaUrl(configurationProvider.getSivaUrl()); - } - super.onDetachedFromWindow(); - } - - public static void setTsaCertificateViewVisibleValue(boolean value) { - isTsaCertificateViewVisible = value; - isTsaCertificateViewVisibleSubject.onNext(value); - } - - public static Observable observeTsaCertificateViewVisibleChanges() { - return isTsaCertificateViewVisibleSubject; - } - - public static void resetSettings(Context context, SettingsDataStore settingsDataStore) { - settingsDataStore.setUuid(""); - settingsDataStore.setTsaUrl(""); - settingsDataStore.setIsOpenAllFileTypesEnabled(true); - settingsDataStore.setIsScreenshotAllowed(false); - File certFile = FileUtil.getCertFile(context, settingsDataStore.getTSACertName(), DIR_SIVA_CERT); - removeCertificate(certFile, settingsDataStore); - setTsaCertificateViewVisibleValue(false); - } - - private static void removeCertificate(File tsaFile, SettingsDataStore settingsDataStore) { - if (tsaFile != null) { - FileUtils.removeFile(tsaFile.getPath()); - } - settingsDataStore.setTSACertName(null); - } - - private void setTSAContainerViewVisibility(boolean isVisible) { - tsaCertContainer.setVisibility(!isVisible ? GONE : VISIBLE); - } - - private SivaSetting getSivaSetting(SettingsDataStore settingsDataStore) { - if (settingsDataStore != null) { - return settingsDataStore.getSivaSetting(); - } - return DEFAULT; - } - - private void checkSivaServiceSetting(SettingsDataStore settingsDataStore, - String defaultSivaUrl) { - SivaSetting currentSivaSetting = getSivaSetting(settingsDataStore); - switch (currentSivaSetting) { - case DEFAULT -> { - String trimmedDefaultSivaUrl = defaultSivaUrl.trim(); - setSivaPlaceholderText(trimmedDefaultSivaUrl); - setSivaUrl(settingsDataStore, trimmedDefaultSivaUrl); - sivaServiceDefaultChoice.setChecked(true); - isSivaCertViewShown(GONE); - setSivaUrlEnabled(false); - } - case MANUAL -> { - sivaServiceManualChoice.setChecked(true); - isSivaCertViewShown(VISIBLE); - setSivaUrlEnabled(true); - String manualSivaUrl = settingsDataStore.getSivaUrl(); - if (!manualSivaUrl.isEmpty() && !defaultSivaUrl.equals(manualSivaUrl)) { - sivaServiceUrl.setText(manualSivaUrl.trim()); - } else { - setSivaUrl(settingsDataStore, defaultSivaUrl); - setSivaPlaceholderText(defaultSivaUrl); - } - } - } - } - - private void isSivaCertViewShown(int visibility) { - sivaServiceCertificateContainer.setVisibility(visibility); - } - - private void setSivaServiceSetting(SettingsDataStore settingsDataStore, - String defaultSivaUrl, int buttonId) { - if (settingsDataStore != null) { - if (sivaServiceDefaultChoice.getId() == buttonId) { - settingsDataStore.setSivaSetting(DEFAULT); - } else if (sivaServiceManualChoice.getId() == buttonId) { - settingsDataStore.setSivaSetting(MANUAL); - } - - checkSivaServiceSetting(settingsDataStore, defaultSivaUrl); - } - } - - private void setSivaUrlEnabled(boolean isEnabled) { - sivaServiceUrl.setEnabled(isEnabled); - } - - private void setSivaUrl(SettingsDataStore settingsDataStore, String sivaUrl) { - if (settingsDataStore != null) { - settingsDataStore.setSivaUrl(sivaUrl); - } - } - - private void setSivaPlaceholderText(String text) { - sivaServiceUrlLayout.setPlaceholderText(text); - } - - private void removeSivaCert(SettingsDataStore settingsDataStore, TextView issuedTo, TextView validTo) { - String sivaCertName = settingsDataStore.getSivaCertName(); - File sivaFile = FileUtil.getCertFile(getContext(), sivaCertName, DIR_SIVA_CERT); - - if (sivaFile != null) { - FileUtils.removeFile(sivaFile.getPath()); - } - settingsDataStore.setSivaCertName(null); - - issuedTo.setText(getResources().getText(R.string.main_settings_timestamp_cert_issued_to_title)); - validTo.setText(getResources().getText(R.string.main_settings_timestamp_cert_valid_to_title)); - } -} diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/siva/SivaSetting.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/siva/SivaSetting.java deleted file mode 100644 index e34ea523d..000000000 --- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/siva/SivaSetting.java +++ /dev/null @@ -1,6 +0,0 @@ -package ee.ria.DigiDoc.android.main.settings.access.siva; - -public enum SivaSetting { - DEFAULT, - MANUAL -} diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/proxy/SettingsProxyDialog.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/proxy/SettingsProxyDialog.java new file mode 100644 index 000000000..01b10dedf --- /dev/null +++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/proxy/SettingsProxyDialog.java @@ -0,0 +1,275 @@ +package ee.ria.DigiDoc.android.main.settings.proxy; + +import static com.jakewharton.rxbinding4.view.RxView.clicks; +import static com.jakewharton.rxbinding4.widget.RxRadioGroup.checkedChanges; +import static com.jakewharton.rxbinding4.widget.RxTextView.textChanges; +import static ee.ria.DigiDoc.common.ProxySetting.MANUAL_PROXY; +import static ee.ria.DigiDoc.common.ProxySetting.NO_PROXY; +import static ee.ria.DigiDoc.common.ProxySetting.SYSTEM_PROXY; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.view.Window; +import android.widget.CheckBox; +import android.widget.ImageButton; +import android.widget.RadioButton; +import android.widget.RadioGroup; + +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; +import com.jakewharton.rxbinding4.widget.RxCompoundButton; + +import ee.ria.DigiDoc.R; +import ee.ria.DigiDoc.android.ApplicationApp; +import ee.ria.DigiDoc.android.main.settings.SettingsDataStore; +import ee.ria.DigiDoc.android.utils.SecureUtil; +import ee.ria.DigiDoc.android.utils.ViewDisposables; +import ee.ria.DigiDoc.android.utils.navigator.Navigator; +import ee.ria.DigiDoc.common.ManualProxy; +import ee.ria.DigiDoc.common.ProxySetting; +import ee.ria.DigiDoc.sign.SignLib; +import timber.log.Timber; + +public class SettingsProxyDialog extends Dialog { + + private final Navigator navigator; + private final SettingsDataStore settingsDataStore; + + private final ViewDisposables disposables; + + private final RadioGroup proxyGroup; + private final RadioButton noProxy; + private final RadioButton systemProxy; + private final RadioButton manualProxy; + private final CheckBox allowSSLConnections; + + private final TextInputEditText host; + private final TextInputEditText port; + private final TextInputEditText username; + private final TextInputEditText password; + + private final TextInputLayout portLayout; + + private final ManualProxy manualProxySettings; + + private final ImageButton backButton; + + public SettingsProxyDialog(Context context) { + super(context); + + Window window = getWindow(); + if (window != null) { + SecureUtil.markAsSecure(context, window); + } + + setContentView(R.layout.main_settings_proxy_dialog_layout); + + navigator = ApplicationApp.component(context).navigator(); + settingsDataStore = ApplicationApp.component(context).settingsDataStore(); + disposables = new ViewDisposables(); + + proxyGroup = findViewById(R.id.mainSettingsProxyGroup); + noProxy = findViewById(R.id.mainSettingsProxyNoProxy); + systemProxy = findViewById(R.id.mainSettingsProxyUseSystem); + manualProxy = findViewById(R.id.mainSettingsProxyManual); + allowSSLConnections = findViewById(R.id.mainSettingsProxyAllowSSL); + + host = findViewById(R.id.mainSettingsProxyHost); + port = findViewById(R.id.mainSettingsProxyPort); + username = findViewById(R.id.mainSettingsProxyUsername); + password = findViewById(R.id.mainSettingsProxyPassword); + + portLayout = findViewById(R.id.mainSettingsProxyPortLayout); + + manualProxySettings = getManualProxySettings(); + + backButton = findViewById(R.id.mainSettingsProxyBackButton); + backButton.requestFocus(); + + checkActiveProxySetting(settingsDataStore); + checkManualProxySettings(settingsDataStore, manualProxySettings); + checkIsSSLForProxyEnabled(settingsDataStore); + + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + disposables.attach(); + disposables.add(clicks(backButton).subscribe(o -> dismiss())); + disposables.add(textChanges(port) + .map(CharSequence::toString) + .subscribe(this::validatePortNumber)); + disposables.add(checkedChanges(proxyGroup).subscribe(setting -> + setProxySetting(settingsDataStore, setting))); + disposables.add(RxCompoundButton.checkedChanges(allowSSLConnections).subscribe(isEnabled -> + setIsSSLForProxyEnabled(settingsDataStore, isEnabled))); + } + + @Override + public void onDetachedFromWindow() { + if (settingsDataStore != null) { + ProxySetting currentProxySetting = settingsDataStore.getProxySetting(); + if (currentProxySetting.equals(MANUAL_PROXY)) { + manualProxySettings.setHost(host.getEditableText().toString()); + String portNumber = port.getEditableText().toString(); + try { + manualProxySettings.setPort( + portNumber.isEmpty() || !isValidPortNumber(portNumber) ? 80 : + Integer.parseInt(port.getEditableText().toString())); + } catch (NumberFormatException nfe) { + Timber.log(Log.ERROR, nfe, "Unable to get the port number"); + manualProxySettings.setPort(80); + } + manualProxySettings.setUsername(username.getEditableText().toString()); + manualProxySettings.setPassword(password.getEditableText().toString()); + setManualProxySettings(settingsDataStore, manualProxySettings); + } else { + clearProxySettings(settingsDataStore); + } + } + + disposables.detach(); + super.onDetachedFromWindow(); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + } + + public static void resetSettings(Context context, SettingsDataStore settingsDataStore) { + if (settingsDataStore != null) { + settingsDataStore.setProxySetting(NO_PROXY); + settingsDataStore.setProxyHost(""); + settingsDataStore.setProxyPort(80); + settingsDataStore.setProxyUsername(""); + settingsDataStore.setProxyPassword(context, ""); + settingsDataStore.setIsProxyForSSLEnabled(true); + } + } + + private void checkActiveProxySetting(SettingsDataStore settingsDataStore) { + if (settingsDataStore != null) { + ProxySetting currentProxySetting = settingsDataStore.getProxySetting(); + switch (currentProxySetting) { + case NO_PROXY -> { + noProxy.setChecked(true); + isManualProxyEnabled(false); + } + case SYSTEM_PROXY -> { + systemProxy.setChecked(true); + isManualProxyEnabled(false); + } + case MANUAL_PROXY -> { + manualProxy.setChecked(true); + isManualProxyEnabled(true); + } + } + } + } + + private void setProxySetting(SettingsDataStore settingsDataStore, int buttonId) { + if (settingsDataStore != null) { + if (noProxy.getId() == buttonId) { + settingsDataStore.setProxySetting(NO_PROXY); + } else if (systemProxy.getId() == buttonId) { + settingsDataStore.setProxySetting(SYSTEM_PROXY); + } else if (manualProxy.getId() == buttonId) { + settingsDataStore.setProxySetting(MANUAL_PROXY); + } + + checkActiveProxySetting(settingsDataStore); + } + } + + private ManualProxy getManualProxySettings() { + return new ManualProxy( + settingsDataStore.getProxyHost(), + settingsDataStore.getProxyPort(), + settingsDataStore.getProxyUsername(), + settingsDataStore.getProxyPassword(navigator.activity()) + ); + } + + private void checkManualProxySettings(SettingsDataStore settingsDataStore, ManualProxy manualProxy) { + if (settingsDataStore != null) { + host.setText(manualProxy.getHost()); + port.setText(String.valueOf(manualProxy.getPort())); + username.setText(manualProxy.getUsername()); + password.setText(manualProxy.getPassword()); + } + } + + private void setManualProxySettings(SettingsDataStore settingsDataStore, ManualProxy manualProxy) { + if (settingsDataStore != null) { + settingsDataStore.setProxyHost(manualProxy.getHost()); + settingsDataStore.setProxyPort(manualProxy.getPort()); + settingsDataStore.setProxyUsername(manualProxy.getUsername()); + settingsDataStore.setProxyPassword(navigator.activity(), manualProxy.getPassword()); + SignLib.overrideProxy(manualProxy.getHost(), manualProxy.getPort(), + manualProxy.getUsername(), manualProxy.getPassword(), + settingsDataStore.getIsProxyForSSLEnabled()); + } + } + + private void checkIsSSLForProxyEnabled(SettingsDataStore settingsDataStore) { + if (settingsDataStore != null) { + boolean isSSLForProxyEnabled = settingsDataStore.getIsProxyForSSLEnabled(); + allowSSLConnections.setChecked(isSSLForProxyEnabled); + } + } + + private void setIsSSLForProxyEnabled(SettingsDataStore settingsDataStore, boolean isEnabled) { + if (settingsDataStore != null) { + settingsDataStore.setIsProxyForSSLEnabled(isEnabled); + SignLib.overrideProxy(settingsDataStore.getProxyHost(), settingsDataStore.getProxyPort(), + settingsDataStore.getProxyUsername(), + settingsDataStore.getProxyPassword(navigator.activity()), + settingsDataStore.getIsProxyForSSLEnabled()); + } + } + + private void clearProxySettings(SettingsDataStore settingsDataStore) { + manualProxySettings.setHost(""); + manualProxySettings.setPort(80); + manualProxySettings.setUsername(""); + manualProxySettings.setPassword(""); + setManualProxySettings(settingsDataStore, manualProxySettings); + } + + private void isManualProxyEnabled(boolean isEnabled) { + host.setEnabled(isEnabled); + port.setEnabled(isEnabled); + username.setEnabled(isEnabled); + password.setEnabled(isEnabled); + allowSSLConnections.setEnabled(isEnabled); + } + + private void validatePortNumber(String portNumber) { + if (!portNumber.isEmpty()) { + if (!isValidPortNumber(portNumber)) { + portLayout.setError("Min 1, max 65535"); + } else { + portLayout.setError(null); + } + } + } + + private static boolean isValidPortNumber(String portNumber) { + try { + int number = Integer.parseInt(portNumber); + return number >= 1 && number <= 65535; + } catch (NumberFormatException e) { + Timber.log(Log.ERROR, e, String.format("Invalid number: %s", portNumber)); + return false; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsAccessScreen.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/rights/SettingsRightsScreen.java similarity index 75% rename from app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsAccessScreen.java rename to app/src/main/java/ee/ria/DigiDoc/android/main/settings/rights/SettingsRightsScreen.java index 6577a7b28..265fdb9a3 100644 --- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsAccessScreen.java +++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/rights/SettingsRightsScreen.java @@ -1,4 +1,4 @@ -package ee.ria.DigiDoc.android.main.settings.access; +package ee.ria.DigiDoc.android.main.settings.rights; import android.content.Context; import android.view.View; @@ -11,20 +11,20 @@ import ee.ria.DigiDoc.R; import ee.ria.DigiDoc.android.utils.navigator.conductor.ConductorScreen; -public final class SettingsAccessScreen extends ConductorScreen { +public final class SettingsRightsScreen extends ConductorScreen { - public static SettingsAccessScreen create() { - return new SettingsAccessScreen(); + public static SettingsRightsScreen create() { + return new SettingsRightsScreen(); } @SuppressWarnings("WeakerAccess") - public SettingsAccessScreen() { - super(R.id.mainSettingsAccessScreen); + public SettingsRightsScreen() { + super(R.id.mainSettingsRightsScreen); } @Override protected View view(Context context) { - return new SettingsAccessView(context); + return new SettingsRightsView(context); } @Override @@ -41,4 +41,4 @@ protected void onDestroyView(@NonNull View view) { } super.onDestroyView(view); } -} +} \ No newline at end of file diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/rights/SettingsRightsView.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/rights/SettingsRightsView.java new file mode 100644 index 000000000..1107eac70 --- /dev/null +++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/rights/SettingsRightsView.java @@ -0,0 +1,102 @@ +package ee.ria.DigiDoc.android.main.settings.rights; + +import static com.jakewharton.rxbinding4.view.RxView.clicks; +import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.util.AttributeSet; +import android.widget.Toolbar; + +import androidx.appcompat.widget.SwitchCompat; +import androidx.coordinatorlayout.widget.CoordinatorLayout; + +import ee.ria.DigiDoc.R; +import ee.ria.DigiDoc.android.ApplicationApp; +import ee.ria.DigiDoc.android.main.settings.SettingsDataStore; +import ee.ria.DigiDoc.android.utils.ViewDisposables; +import ee.ria.DigiDoc.android.utils.navigator.Navigator; +import ee.ria.DigiDoc.android.utils.navigator.Transaction; + +public final class SettingsRightsView extends CoordinatorLayout { + + private final Toolbar toolbarView; + + private final Navigator navigator; + private final SettingsDataStore settingsDataStore; + + private final ViewDisposables disposables; + + private final SwitchCompat openAllFileTypesSwitch; + private final SwitchCompat allowScreenshotsSwitch; + + public SettingsRightsView(Context context) { + this(context, null); + } + + public SettingsRightsView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SettingsRightsView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + inflate(context, R.layout.main_settings_rights, this); + toolbarView = findViewById(R.id.toolbar); + navigator = ApplicationApp.component(context).navigator(); + settingsDataStore = ApplicationApp.component(context).settingsDataStore(); + disposables = new ViewDisposables(); + + toolbarView.setTitle(R.string.main_settings_rights); + toolbarView.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material); + toolbarView.setNavigationContentDescription(R.string.back); + + openAllFileTypesSwitch = findViewById(R.id.mainSettingsOpenAllFileTypes); + allowScreenshotsSwitch = findViewById(R.id.mainSettingsAllowScreenshots); + + if (settingsDataStore != null) { + openAllFileTypesSwitch.setChecked(settingsDataStore.getIsOpenAllFileTypesEnabled()); + allowScreenshotsSwitch.setChecked(settingsDataStore.getIsScreenshotAllowed()); + } + } + + private void restartIntent() { + PackageManager packageManager = getContext().getPackageManager(); + Intent intent = packageManager.getLaunchIntentForPackage(getContext().getPackageName()); + ComponentName componentName = intent.getComponent(); + Intent restartIntent = Intent.makeRestartActivityTask(componentName); + restartIntent.setAction(Intent.ACTION_CONFIGURATION_CHANGED); + getContext().startActivity(restartIntent); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + disposables.attach(); + disposables.add(navigationClicks(toolbarView).subscribe(o -> + navigator.execute(Transaction.pop()))); + disposables.add(clicks(allowScreenshotsSwitch) + .subscribe(o -> { + boolean isChecked = allowScreenshotsSwitch.isChecked(); + settingsDataStore.setIsScreenshotAllowed(isChecked); + restartIntent(); + })); + disposables.add(clicks(openAllFileTypesSwitch) + .subscribe(o -> { + boolean isChecked = openAllFileTypesSwitch.isChecked(); + settingsDataStore.setIsOpenAllFileTypesEnabled(isChecked); + restartIntent(); + })); + } + + @Override + public void onDetachedFromWindow() { + disposables.detach(); + super.onDetachedFromWindow(); + } + + public static void resetSettings(SettingsDataStore settingsDataStore) { + settingsDataStore.setIsRoleAskingEnabled(false); + } +} diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/role/SettingsRoleAndAddressView.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/role/SettingsRoleAndAddressView.java deleted file mode 100644 index 0bd11878c..000000000 --- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/role/SettingsRoleAndAddressView.java +++ /dev/null @@ -1,109 +0,0 @@ -package ee.ria.DigiDoc.android.main.settings.role; - -import static com.jakewharton.rxbinding4.widget.RxCompoundButton.checkedChanges; -import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks; -import static ee.ria.DigiDoc.android.main.settings.util.SettingsUtil.getToolbarImageButton; -import static ee.ria.DigiDoc.android.main.settings.util.SettingsUtil.getToolbarTextView; - -import android.content.Context; -import android.util.AttributeSet; -import android.view.View; -import android.widget.ImageButton; -import android.widget.ScrollView; -import android.widget.TextView; -import android.widget.Toolbar; - -import androidx.appcompat.widget.SwitchCompat; -import androidx.coordinatorlayout.widget.CoordinatorLayout; - -import com.google.android.material.appbar.AppBarLayout; - -import ee.ria.DigiDoc.R; -import ee.ria.DigiDoc.android.Activity; -import ee.ria.DigiDoc.android.ApplicationApp; -import ee.ria.DigiDoc.android.main.settings.SettingsDataStore; -import ee.ria.DigiDoc.android.utils.ViewDisposables; -import ee.ria.DigiDoc.android.utils.navigator.Navigator; -import ee.ria.DigiDoc.android.utils.navigator.Transaction; - -public final class SettingsRoleAndAddressView extends CoordinatorLayout { - - private final AppBarLayout appBarLayout; - private final ScrollView scrollView; - private final Toolbar toolbarView; - private final SwitchCompat askRoleAndAddressSwitch; - - private final Navigator navigator; - private final SettingsDataStore settingsDataStore; - private final ViewDisposables disposables; - - public SettingsRoleAndAddressView(Context context) { - this(context, null); - } - - public SettingsRoleAndAddressView(Context context, AttributeSet attrs) { - this(context, attrs, 0); - } - - public SettingsRoleAndAddressView(Context context, AttributeSet attrs, int defStyleAttr) { - super(context, attrs, defStyleAttr); - inflate(context, R.layout.main_settings_role_and_address, this); - toolbarView = findViewById(R.id.toolbar); - appBarLayout = findViewById(R.id.appBar); - scrollView = findViewById(R.id.scrollView); - navigator = ApplicationApp.component(context).navigator(); - settingsDataStore = ApplicationApp.component(context).settingsDataStore(); - disposables = new ViewDisposables(); - - toolbarView.setTitle(R.string.main_settings_role_and_address_button); - toolbarView.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material); - toolbarView.setNavigationContentDescription(R.string.back); - - Activity activityContext = (Activity) this.getContext(); - - askRoleAndAddressSwitch = findViewById(R.id.mainSettingsAskRoleAndAddress); - if (askRoleAndAddressSwitch != null && activityContext != null) { - askRoleAndAddressSwitch.setChecked(activityContext.getSettingsDataStore().getIsRoleAskingEnabled()); - - askRoleAndAddressSwitch.setOnCheckedChangeListener((buttonView, isChecked) -> - activityContext.getSettingsDataStore().setIsRoleAskingEnabled(isChecked)); - } - } - - public static void resetSettings(SettingsDataStore settingsDataStore) { - settingsDataStore.setIsRoleAskingEnabled(false); - } - - @Override - public void onAttachedToWindow() { - super.onAttachedToWindow(); - - scrollView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS); - TextView toolbarTextView = getToolbarTextView(toolbarView); - if (toolbarTextView != null) { - toolbarTextView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - } - ImageButton toolbarImageButton = getToolbarImageButton(toolbarView); - if (toolbarImageButton != null) { - toolbarImageButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_NO); - } - appBarLayout.postDelayed(() -> { - scrollView.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO); - if (toolbarImageButton != null) { - toolbarImageButton.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES); - } - }, 1000); - - disposables.attach(); - disposables.add(navigationClicks(toolbarView).subscribe(o -> - navigator.execute(Transaction.pop()))); - disposables.add(checkedChanges(askRoleAndAddressSwitch) - .subscribe(settingsDataStore::setIsRoleAskingEnabled)); - } - - @Override - public void onDetachedFromWindow() { - disposables.detach(); - super.onDetachedFromWindow(); - } -} diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsFragment.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/SettingsFragment.java similarity index 97% rename from app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsFragment.java rename to app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/SettingsFragment.java index 83c039858..30731963f 100644 --- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/SettingsFragment.java +++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/SettingsFragment.java @@ -1,4 +1,4 @@ -package ee.ria.DigiDoc.android.main.settings.access; +package ee.ria.DigiDoc.android.main.settings.signing; import android.os.Bundle; import android.view.View; @@ -24,7 +24,7 @@ public final class SettingsFragment extends PreferenceFragmentCompat { @Override public void onCreatePreferencesFix(@Nullable Bundle savedInstanceState, String rootKey) { - setPreferencesFromResource(R.xml.main_settings_access, null); + setPreferencesFromResource(R.xml.main_settings_signing, null); } @Override diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/role/SettingsRoleAndAddressScreen.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/SettingsSigningScreen.java similarity index 69% rename from app/src/main/java/ee/ria/DigiDoc/android/main/settings/role/SettingsRoleAndAddressScreen.java rename to app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/SettingsSigningScreen.java index c82dbca3f..5be6fa5e0 100644 --- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/role/SettingsRoleAndAddressScreen.java +++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/SettingsSigningScreen.java @@ -1,4 +1,4 @@ -package ee.ria.DigiDoc.android.main.settings.role; +package ee.ria.DigiDoc.android.main.settings.signing; import android.content.Context; import android.view.View; @@ -9,23 +9,22 @@ import androidx.fragment.app.FragmentManager; import ee.ria.DigiDoc.R; -import ee.ria.DigiDoc.android.main.settings.access.SettingsAccessView; import ee.ria.DigiDoc.android.utils.navigator.conductor.ConductorScreen; -public final class SettingsRoleAndAddressScreen extends ConductorScreen { +public final class SettingsSigningScreen extends ConductorScreen { - public static SettingsRoleAndAddressScreen create() { - return new SettingsRoleAndAddressScreen(); + public static SettingsSigningScreen create() { + return new SettingsSigningScreen(); } @SuppressWarnings("WeakerAccess") - public SettingsRoleAndAddressScreen() { - super(R.id.mainSettingsRoleAndAddressScreen); + public SettingsSigningScreen() { + super(R.id.mainSettingsSigningScreen); } @Override protected View view(Context context) { - return new SettingsRoleAndAddressView(context); + return new SettingsSigningView(context); } @Override diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/SettingsSigningView.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/SettingsSigningView.java new file mode 100644 index 000000000..ce163fb42 --- /dev/null +++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/SettingsSigningView.java @@ -0,0 +1,246 @@ +package ee.ria.DigiDoc.android.main.settings.signing; + +import static com.jakewharton.rxbinding4.view.RxView.clicks; +import static com.jakewharton.rxbinding4.widget.RxToolbar.navigationClicks; +import static ee.ria.DigiDoc.common.CommonConstants.DIR_SIVA_CERT; +import static ee.ria.DigiDoc.common.CommonConstants.DIR_TSA_CERT; + +import android.content.Context; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.TextView; +import android.widget.Toolbar; + +import androidx.appcompat.widget.SwitchCompat; +import androidx.coordinatorlayout.widget.CoordinatorLayout; + +import com.jakewharton.rxbinding4.widget.RxCompoundButton; + +import org.bouncycastle.asn1.x500.RDN; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; + +import java.io.File; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; + +import ee.ria.DigiDoc.R; +import ee.ria.DigiDoc.android.ApplicationApp; +import ee.ria.DigiDoc.android.main.settings.SettingsDataStore; +import ee.ria.DigiDoc.android.main.settings.create.CertificateAddViewModel; +import ee.ria.DigiDoc.android.main.settings.create.ChooseFileScreen; +import ee.ria.DigiDoc.android.main.settings.create.ViewState; +import ee.ria.DigiDoc.android.main.settings.proxy.SettingsProxyDialog; +import ee.ria.DigiDoc.android.main.settings.signing.siva.SettingsSivaDialog; +import ee.ria.DigiDoc.android.signature.detail.CertificateDetailScreen; +import ee.ria.DigiDoc.android.utils.ViewDisposables; +import ee.ria.DigiDoc.android.utils.navigator.Navigator; +import ee.ria.DigiDoc.android.utils.navigator.Transaction; +import ee.ria.DigiDoc.common.CertificateUtil; +import ee.ria.DigiDoc.common.FileUtil; +import ee.ria.DigiDoc.configuration.util.FileUtils; +import io.reactivex.rxjava3.core.Observable; +import io.reactivex.rxjava3.subjects.PublishSubject; +import io.reactivex.rxjava3.subjects.Subject; +import timber.log.Timber; + +public final class SettingsSigningView extends CoordinatorLayout { + + private final Toolbar toolbarView; + private final SwitchCompat askRoleAndAddressSwitch; + private final LinearLayout tsaCertContainer; + private final TextView tsaCertIssuedTo; + private final TextView tsaCertValidTo; + private final Button addCertificateButton; + private final Button showCertificateButton; + private X509Certificate tsaCertificate; + + private final Button sivaCategory; + private final SettingsSivaDialog sivaDialog; + private final Button proxyCategory; + private final SettingsProxyDialog proxyDialog; + + private final Navigator navigator; + private final SettingsDataStore settingsDataStore; + + private final ViewDisposables disposables; + private final CertificateAddViewModel viewModel; + + private static boolean isTsaCertificateViewVisible; + private static final Subject isTsaCertificateViewVisibleSubject = PublishSubject.create(); + + private final String viewId = String.valueOf(View.generateViewId()); + + public SettingsSigningView(Context context) { + this(context, null); + } + + public SettingsSigningView(Context context, AttributeSet attrs) { + this(context, attrs, 0); + } + + public SettingsSigningView(Context context, AttributeSet attrs, int defStyleAttr) { + super(context, attrs, defStyleAttr); + viewModel = ApplicationApp.component(context).navigator() + .viewModel(viewId, CertificateAddViewModel.class); + inflate(context, R.layout.main_settings_signing, this); + toolbarView = findViewById(R.id.toolbar); + navigator = ApplicationApp.component(context).navigator(); + settingsDataStore = ApplicationApp.component(context).settingsDataStore(); + disposables = new ViewDisposables(); + + toolbarView.setTitle(R.string.signature_update_title_created); + toolbarView.setNavigationIcon(androidx.appcompat.R.drawable.abc_ic_ab_back_material); + toolbarView.setNavigationContentDescription(R.string.back); + + tsaCertContainer = findViewById(R.id.mainSettingsTsaCertificateContainer); + tsaCertIssuedTo = findViewById(R.id.mainSettingsTsaCertificateIssuedTo); + tsaCertValidTo = findViewById(R.id.mainSettingsTsaCertificateValidTo); + addCertificateButton = findViewById(R.id.mainSettingsTsaCertificateAddCertificateButton); + showCertificateButton = findViewById(R.id.mainSettingsTsaCertificateShowCertificateButton); + + if (settingsDataStore != null) { + isTsaCertificateViewVisible = settingsDataStore.getIsTsaCertificateViewVisible(); + setTSAContainerViewVisibility(isTsaCertificateViewVisible); + } + + sivaCategory = findViewById(R.id.signingSettingsSivaCategory); + proxyCategory = findViewById(R.id.signingSettingsProxyCategory); + sivaDialog = new SettingsSivaDialog(navigator.activity()); + proxyDialog = new SettingsProxyDialog(navigator.activity()); + + askRoleAndAddressSwitch = findViewById(R.id.mainSettingsAskRoleAndAddress); + } + + public void render(ViewState state) { + if (settingsDataStore != null) { + String tsaCertName = settingsDataStore.getTSACertName(); + File tsaFile = FileUtil.getCertFile(getContext(), tsaCertName, DIR_TSA_CERT); + + if (tsaFile != null) { + String fileContents = FileUtils.readFileContent(tsaFile.getPath()); + try { + tsaCertificate = CertificateUtil.x509Certificate(fileContents); + X509CertificateHolder certificateHolder = new JcaX509CertificateHolder(tsaCertificate); + String issuer = getIssuer(certificateHolder); + tsaCertIssuedTo.setText(String.format("%s %s", + getResources().getText(R.string.main_settings_timestamp_cert_issued_to_title), + issuer)); + tsaCertValidTo.setText(String.format("%s %s", + getResources().getText(R.string.main_settings_timestamp_cert_valid_to_title), + getFormattedDateTime(certificateHolder.getNotAfter()))); + } catch (CertificateException e) { + Timber.log(Log.ERROR, e, "Unable to get TSA certificate"); + + // Remove invalid files + FileUtils.removeFile(tsaFile.getPath()); + settingsDataStore.setTSACertName(null); + + tsaCertIssuedTo.setText(getResources().getText(R.string.main_settings_timestamp_cert_issued_to_title)); + tsaCertValidTo.setText(getResources().getText(R.string.main_settings_timestamp_cert_valid_to_title)); + } + } + } + } + + private String getIssuer(X509CertificateHolder certificateHolder) { + RDN[] organizationRDNs = certificateHolder.getIssuer().getRDNs(BCStyle.O); + if (organizationRDNs.length > 0) { + return organizationRDNs[0].getFirst().getValue().toString(); + } + RDN[] organizationUnitRDNs = certificateHolder.getIssuer().getRDNs(BCStyle.OU); + if (organizationUnitRDNs.length > 0) { + return organizationUnitRDNs[0].getFirst().getValue().toString(); + } + RDN[] commonNameRDNs = certificateHolder.getIssuer().getRDNs(BCStyle.CN); + if (commonNameRDNs.length > 0) { + return commonNameRDNs[0].getFirst().getValue().toString(); + } + + return "-"; + } + + private static String getFormattedDateTime(Date date) { + try { + SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()); + if (date != null) { + return dateFormat.format(date); + } + } catch (IllegalStateException e) { + Timber.log(Log.ERROR, e, "Unable to format date"); + } + return "-"; + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + disposables.attach(); + disposables.add(navigationClicks(toolbarView).subscribe(o -> + navigator.execute(Transaction.pop()))); + disposables.add(RxCompoundButton.checkedChanges(askRoleAndAddressSwitch) + .subscribe(settingsDataStore::setIsRoleAskingEnabled)); + disposables.add(clicks(showCertificateButton).subscribe(o -> { + if (tsaCertificate != null) { + navigator.execute(Transaction.push(CertificateDetailScreen.create(tsaCertificate))); + } + })); + disposables.add(clicks(addCertificateButton).subscribe(o -> + navigator.execute(Transaction.push(ChooseFileScreen.create(true, false))))); + disposables.add(observeTsaCertificateViewVisibleChanges().subscribe(isVisible -> { + settingsDataStore.setIsTsaCertificateViewVisible(isVisible); + setTSAContainerViewVisibility(isVisible); + })); + disposables.add(clicks(askRoleAndAddressSwitch) + .subscribe(o -> { + boolean isChecked = askRoleAndAddressSwitch.isChecked(); + settingsDataStore.setIsRoleAskingEnabled(isChecked); + })); + disposables.add(clicks(sivaCategory).subscribe(o -> sivaDialog.show())); + disposables.add(clicks(proxyCategory).subscribe(o -> proxyDialog.show())); + disposables.add(viewModel.viewStates().subscribe(this::render)); + } + + @Override + public void onDetachedFromWindow() { + disposables.detach(); + super.onDetachedFromWindow(); + } + + public static void setTsaCertificateViewVisibleValue(boolean value) { + isTsaCertificateViewVisible = value; + isTsaCertificateViewVisibleSubject.onNext(value); + } + + public static Observable observeTsaCertificateViewVisibleChanges() { + return isTsaCertificateViewVisibleSubject; + } + + public static void resetSettings(Context context, SettingsDataStore settingsDataStore) { + settingsDataStore.setUuid(""); + settingsDataStore.setTsaUrl(""); + settingsDataStore.setIsOpenAllFileTypesEnabled(true); + settingsDataStore.setIsScreenshotAllowed(false); + File certFile = FileUtil.getCertFile(context, settingsDataStore.getTSACertName(), DIR_SIVA_CERT); + removeCertificate(certFile, settingsDataStore); + setTsaCertificateViewVisibleValue(false); + } + + private static void removeCertificate(File tsaFile, SettingsDataStore settingsDataStore) { + if (tsaFile != null) { + FileUtils.removeFile(tsaFile.getPath()); + } + settingsDataStore.setTSACertName(null); + } + + private void setTSAContainerViewVisibility(boolean isVisible) { + tsaCertContainer.setVisibility(!isVisible ? GONE : VISIBLE); + } +} diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/TsaUrlPreference.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/TsaUrlPreference.java similarity index 79% rename from app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/TsaUrlPreference.java rename to app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/TsaUrlPreference.java index b8b90b442..b3b2ee859 100644 --- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/TsaUrlPreference.java +++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/TsaUrlPreference.java @@ -1,4 +1,4 @@ -package ee.ria.DigiDoc.android.main.settings.access; +package ee.ria.DigiDoc.android.main.settings.signing; import static android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT; @@ -16,7 +16,6 @@ import ee.ria.DigiDoc.R; import ee.ria.DigiDoc.android.ApplicationApp; import ee.ria.DigiDoc.android.accessibility.AccessibilityUtils; -import ee.ria.DigiDoc.android.main.settings.access.SettingsAccessView; import ee.ria.DigiDoc.configuration.ConfigurationProvider; public class TsaUrlPreference extends EditTextPreference { @@ -40,17 +39,21 @@ public TsaUrlPreference(Context context, @Nullable AttributeSet attrs, int defSt int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); configurationProvider = ((ApplicationApp) context.getApplicationContext()).getConfigurationProvider(); + + float sizePreference = 48f; + int sizeDisplayMetrics = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, sizePreference, context.getResources().getDisplayMetrics()); + checkBox = new AppCompatCheckBox(context); checkBox.setId(android.R.id.checkbox); checkBox.setText(R.string.main_settings_tsa_url_use_default); - checkBox.setMinHeight((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 48f, context.getResources().getDisplayMetrics())); - checkBox.setMinWidth((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, 120f, context.getResources().getDisplayMetrics())); - checkBox.setX(48f); + checkBox.setMinHeight(sizeDisplayMetrics); + checkBox.setX(sizePreference); + checkBox.setPadding(checkBox.getPaddingLeft(), checkBox.getPaddingTop(), sizeDisplayMetrics, checkBox.getPaddingBottom()); setViewId(R.id.mainSettingsAccessToTimeStampingService); setOnPreferenceChangeListener((preference, newValue) -> { - SettingsAccessView.setTsaCertificateViewVisibleValue(!checkBox.isChecked()); + SettingsSigningView.setTsaCertificateViewVisibleValue(!checkBox.isChecked()); AccessibilityUtils.sendAccessibilityEvent(context, TYPE_ANNOUNCEMENT, R.string.setting_value_changed); return true; }); diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/TsaUrlPreferenceDialogFragment.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/TsaUrlPreferenceDialogFragment.java similarity index 71% rename from app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/TsaUrlPreferenceDialogFragment.java rename to app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/TsaUrlPreferenceDialogFragment.java index baa9fa5b8..406c7add0 100644 --- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/TsaUrlPreferenceDialogFragment.java +++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/TsaUrlPreferenceDialogFragment.java @@ -1,4 +1,4 @@ -package ee.ria.DigiDoc.android.main.settings.access; +package ee.ria.DigiDoc.android.main.settings.signing; import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; @@ -6,7 +6,9 @@ import android.app.Dialog; import android.os.Bundle; +import android.text.Editable; import android.text.TextUtils; +import android.text.TextWatcher; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; @@ -25,6 +27,9 @@ public class TsaUrlPreferenceDialogFragment extends EditTextPreferenceDialogFragmentCompat { + private AppCompatEditText appCompatEditText; + private TextWatcher tsaUrlTextWatcher; + @Override protected void onBindDialogView(View view) { super.onBindDialogView(view); @@ -33,7 +38,7 @@ protected void onBindDialogView(View view) { ConfigurationProvider configurationProvider = ((ApplicationApp) getContext().getApplicationContext()).getConfigurationProvider(); CheckBox checkBox = tsaUrlPreference.getCheckBox(); - AppCompatEditText appCompatEditText = TextUtil.getTextView(view); + appCompatEditText = TextUtil.getTextView(view); tsaUrlPreference.setOnBindEditTextListener(editText -> { checkBox.setChecked(false); @@ -50,7 +55,7 @@ protected void onBindDialogView(View view) { checkBox.setChecked(TextUtils.isEmpty(tsaUrlPreference.getText())); - SettingsAccessView.setTsaCertificateViewVisibleValue(!checkBox.isChecked()); + SettingsSigningView.setTsaCertificateViewVisibleValue(!checkBox.isChecked()); ViewGroup parent = ((ViewGroup) appCompatEditText.getParent()); View oldCheckBox = appCompatEditText.findViewById(checkBox.getId()); @@ -65,6 +70,28 @@ protected void onBindDialogView(View view) { parent.addView(checkBox, ViewGroup.LayoutParams.MATCH_PARENT, WRAP_CONTENT); } + + appCompatEditText.setSelection(appCompatEditText.getText() != null ? appCompatEditText.getText().length() : 0); + + tsaUrlTextWatcher = new TextWatcher() { + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + appCompatEditText.setSingleLine(appCompatEditText.getText() != null && appCompatEditText.getText().length() != 0); + } + + @Override + public void afterTextChanged(Editable s) { + if (s.length() == 1 && appCompatEditText.getText() != null) { + appCompatEditText.setSelection(appCompatEditText.getText().length()); + } + } + }; + + appCompatEditText.addTextChangedListener(tsaUrlTextWatcher); } } } @@ -93,5 +120,6 @@ public void onDialogClosed(boolean positiveResult) { if (!positiveResult && getContext() != null) { AccessibilityUtils.sendAccessibilityEvent(getContext(), TYPE_ANNOUNCEMENT, R.string.setting_value_change_cancelled); } + appCompatEditText.removeTextChangedListener(tsaUrlTextWatcher); } } diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/UUIDPreference.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/UUIDPreference.java similarity index 83% rename from app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/UUIDPreference.java rename to app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/UUIDPreference.java index 7fffc7c1a..e84c233d8 100644 --- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/UUIDPreference.java +++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/UUIDPreference.java @@ -18,7 +18,7 @@ * */ -package ee.ria.DigiDoc.android.main.settings.access; +package ee.ria.DigiDoc.android.main.settings.signing; import static android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT; @@ -28,8 +28,10 @@ import android.util.TypedValue; import android.widget.CheckBox; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatCheckBox; +import androidx.preference.PreferenceViewHolder; import com.takisoft.preferencex.EditTextPreference; @@ -57,14 +59,16 @@ public UUIDPreference(Context context, @Nullable AttributeSet attrs, int defStyl public UUIDPreference(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); + + float sizePreference = 48f; + int sizeDisplayMetrics = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, sizePreference, context.getResources().getDisplayMetrics()); + checkBox = new AppCompatCheckBox(context); checkBox.setId(android.R.id.checkbox); checkBox.setText(R.string.main_settings_tsa_url_use_default); - checkBox.setMinHeight((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - 48f, context.getResources().getDisplayMetrics())); - checkBox.setMinWidth((int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, - 120f, context.getResources().getDisplayMetrics())); - checkBox.setX(48f); + checkBox.setMinHeight(sizeDisplayMetrics); + checkBox.setX(sizePreference); + checkBox.setPadding(checkBox.getPaddingLeft(), checkBox.getPaddingTop(), sizeDisplayMetrics, checkBox.getPaddingBottom()); setViewId(R.id.mainSettingsAccessToSigningService); diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/UUIDPreferenceDialogFragment.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/UUIDPreferenceDialogFragment.java similarity index 83% rename from app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/UUIDPreferenceDialogFragment.java rename to app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/UUIDPreferenceDialogFragment.java index 8293dee6d..b29612191 100644 --- a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/access/UUIDPreferenceDialogFragment.java +++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/UUIDPreferenceDialogFragment.java @@ -18,17 +18,23 @@ * */ -package ee.ria.DigiDoc.android.main.settings.access; +package ee.ria.DigiDoc.android.main.settings.signing; import static android.view.accessibility.AccessibilityEvent.TYPE_ANNOUNCEMENT; import android.app.Dialog; +import android.os.Build; import android.os.Bundle; +import android.text.Editable; +import android.text.InputType; import android.text.TextUtils; +import android.text.TextWatcher; import android.view.View; import android.view.ViewGroup; import android.view.ViewParent; import android.widget.CheckBox; +import android.widget.LinearLayout; +import android.widget.TextView; import androidx.appcompat.widget.AppCompatEditText; import androidx.preference.EditTextPreferenceDialogFragmentCompat; @@ -41,6 +47,9 @@ public class UUIDPreferenceDialogFragment extends EditTextPreferenceDialogFragmentCompat { + private AppCompatEditText appCompatEditText; + private TextWatcher uuidTextWatcher; + @Override protected void onBindDialogView(View view) { super.onBindDialogView(view); @@ -48,7 +57,7 @@ protected void onBindDialogView(View view) { if (uuidPreference != null) { CheckBox checkBox = uuidPreference.getCheckBox(); - AppCompatEditText appCompatEditText = TextUtil.getTextView(view); + appCompatEditText = TextUtil.getTextView(view); uuidPreference.setOnBindEditTextListener(editText -> { checkBox.setChecked(false); @@ -78,12 +87,18 @@ protected void onBindDialogView(View view) { parent.addView(checkBox, ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT); } + + appCompatEditText.setSingleLine(true); + appCompatEditText.setInputType(InputType.TYPE_CLASS_TEXT | InputType.TYPE_TEXT_VARIATION_PASSWORD); + appCompatEditText.setSelection(appCompatEditText.getText() != null ? + appCompatEditText.getText().length() : 0); } } } private void disableTextViewOnChecked(AppCompatEditText appCompatEditText) { appCompatEditText.setText(null); + appCompatEditText.setSingleLine(false); appCompatEditText.setHint("00000000-0000-0000-0000-000000000000"); appCompatEditText.clearFocus(); } @@ -105,5 +120,6 @@ public void onDialogClosed(boolean positiveResult) { if (!positiveResult && getContext() != null) { AccessibilityUtils.sendAccessibilityEvent(getContext(), TYPE_ANNOUNCEMENT, R.string.setting_value_change_cancelled); } + appCompatEditText.removeTextChangedListener(uuidTextWatcher); } } diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/siva/SettingsSivaDialog.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/siva/SettingsSivaDialog.java new file mode 100644 index 000000000..40be1cd96 --- /dev/null +++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/siva/SettingsSivaDialog.java @@ -0,0 +1,365 @@ +package ee.ria.DigiDoc.android.main.settings.signing.siva; + +import static android.view.View.GONE; +import static android.view.View.VISIBLE; +import static android.view.View.inflate; +import static com.jakewharton.rxbinding4.view.RxView.clicks; +import static com.jakewharton.rxbinding4.widget.RxRadioGroup.checkedChanges; +import static com.jakewharton.rxbinding4.widget.RxTextView.textChanges; +import static ee.ria.DigiDoc.android.main.settings.signing.siva.SivaSetting.DEFAULT; +import static ee.ria.DigiDoc.android.main.settings.signing.siva.SivaSetting.MANUAL; +import static ee.ria.DigiDoc.common.CommonConstants.DIR_SIVA_CERT; + +import android.app.Dialog; +import android.content.Context; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.util.Log; +import android.view.View; +import android.view.Window; +import android.widget.Button; +import android.widget.ImageButton; +import android.widget.LinearLayout; +import android.widget.RadioButton; +import android.widget.RadioGroup; +import android.widget.TextView; + +import com.google.android.material.textfield.TextInputEditText; +import com.google.android.material.textfield.TextInputLayout; + +import org.bouncycastle.asn1.x500.RDN; +import org.bouncycastle.asn1.x500.style.BCStyle; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder; + +import java.io.File; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.Locale; +import java.util.Optional; + +import ee.ria.DigiDoc.R; +import ee.ria.DigiDoc.android.ApplicationApp; +import ee.ria.DigiDoc.android.main.settings.SettingsDataStore; +import ee.ria.DigiDoc.android.main.settings.create.CertificateAddViewModel; +import ee.ria.DigiDoc.android.main.settings.create.ChooseFileScreen; +import ee.ria.DigiDoc.android.main.settings.create.ViewState; +import ee.ria.DigiDoc.android.signature.detail.CertificateDetailScreen; +import ee.ria.DigiDoc.android.utils.SecureUtil; +import ee.ria.DigiDoc.android.utils.ViewDisposables; +import ee.ria.DigiDoc.android.utils.navigator.Navigator; +import ee.ria.DigiDoc.android.utils.navigator.Transaction; +import ee.ria.DigiDoc.common.CertificateUtil; +import ee.ria.DigiDoc.common.FileUtil; +import ee.ria.DigiDoc.configuration.ConfigurationProvider; +import ee.ria.DigiDoc.configuration.util.FileUtils; +import timber.log.Timber; + +public class SettingsSivaDialog extends Dialog { + + private final Navigator navigator; + private final SettingsDataStore settingsDataStore; + private final ConfigurationProvider configurationProvider; + + private final ViewDisposables disposables; + private final CertificateAddViewModel viewModel; + + private final String viewId = String.valueOf(View.generateViewId()); + + private TextWatcher sivaUrlTextWatcher; + + private final ImageButton backButton; + private final RadioGroup sivaServiceChoiceGroup; + private final RadioButton sivaServiceDefaultChoice; + private final RadioButton sivaServiceManualChoice; + private final TextInputLayout sivaServiceUrlLayout; + private final TextInputEditText sivaServiceUrl; + private final TextView sivaCertificateIssuedTo; + private final TextView sivaCertificateValidTo; + + private final LinearLayout sivaServiceCertificateContainer; + private final Button sivaCertificateAddCertificateButton; + private final Button sivaCertificateShowCertificateButton; + private X509Certificate sivaCertificate; + + private String previousSivaUrl = ""; + + public SettingsSivaDialog(Context context) { + super(context); + + Window window = getWindow(); + if (window != null) { + SecureUtil.markAsSecure(context, window); + } + + setContentView(R.layout.main_settings_siva_dialog_layout); + + viewModel = ApplicationApp.component(context).navigator() + .viewModel(viewId, CertificateAddViewModel.class); + inflate(context, R.layout.main_settings_siva_dialog_layout, null); + + navigator = ApplicationApp.component(context).navigator(); + settingsDataStore = ApplicationApp.component(context).settingsDataStore(); + configurationProvider = ((ApplicationApp) context.getApplicationContext()).getConfigurationProvider(); + + disposables = new ViewDisposables(); + + backButton = findViewById(R.id.mainSettingsSivaBackButton); + backButton.requestFocus(); + sivaServiceChoiceGroup = findViewById(R.id.mainSettingsSivaServiceChoiceGroup); + sivaServiceDefaultChoice = findViewById(R.id.mainSettingsSivaServiceDefaultChoice); + sivaServiceManualChoice = findViewById(R.id.mainSettingsSivaServiceManualChoice); + sivaServiceUrlLayout = findViewById(R.id.mainSettingsSivaServiceUrlLayout); + sivaServiceUrl = findViewById(R.id.mainSettingsSivaServiceUrl); + sivaCertificateIssuedTo = findViewById(R.id.mainSettingsSivaCertificateIssuedTo); + sivaCertificateValidTo = findViewById(R.id.mainSettingsSivaCertificateValidTo); + + sivaServiceCertificateContainer = findViewById(R.id.mainSettingsSivaServiceCertificateContainer); + sivaCertificateAddCertificateButton = findViewById(R.id.mainSettingsSivaCertificateAddCertificateButton); + sivaCertificateShowCertificateButton = findViewById(R.id.mainSettingsSivaCertificateShowCertificateButton); + + if (sivaServiceUrl.getText() != null) { + previousSivaUrl = sivaServiceUrl.getText().toString(); + } + + checkSivaServiceSetting(settingsDataStore, configurationProvider.getSivaUrl()); + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + sivaServiceUrl.setSelection(sivaServiceUrl.getText() != null ? sivaServiceUrl.getText().length() : 0); + + sivaUrlTextWatcher = new TextWatcher() { + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) {} + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + sivaServiceUrl.setSingleLine(sivaServiceUrl.getText() != null && sivaServiceUrl.getText().length() != 0); + } + + @Override + public void afterTextChanged(Editable s) { + if (s.length() == 1 && sivaServiceUrl.getText() != null) { + sivaServiceUrl.setSelection(sivaServiceUrl.getText().length()); + } + } + }; + + sivaServiceUrl.addTextChangedListener(sivaUrlTextWatcher); + + updateData(settingsDataStore); + } + + private void updateData(SettingsDataStore settingsDataStore) { + if (sivaServiceUrl.getText() != null) { + previousSivaUrl = sivaServiceUrl.getText().toString(); + } + + String sivaCertName = settingsDataStore.getSivaCertName(); + File sivaFile = FileUtil.getCertFile(getContext(), sivaCertName, DIR_SIVA_CERT); + + if (sivaFile != null) { + String fileContents = FileUtils.readFileContent(sivaFile.getPath()); + try { + sivaCertificate = CertificateUtil.x509Certificate(fileContents); + X509CertificateHolder certificateHolder = new JcaX509CertificateHolder(sivaCertificate); + String issuer = getIssuer(certificateHolder); + sivaCertificateIssuedTo.setText(String.format("%s %s", + navigator.activity().getResources().getText(R.string.main_settings_timestamp_cert_issued_to_title), + issuer)); + sivaCertificateValidTo.setText(String.format("%s %s", + navigator.activity().getResources().getText(R.string.main_settings_timestamp_cert_valid_to_title), + getFormattedDateTime(certificateHolder.getNotAfter()))); + } catch (CertificateException e) { + Timber.log(Log.ERROR, e, "Unable to get SiVa certificate"); + + // Remove invalid files + removeSivaCert(settingsDataStore, sivaCertificateIssuedTo, sivaCertificateValidTo); + } + } + } + + public void render(ViewState state) { + updateData(settingsDataStore); + } + + private String getIssuer(X509CertificateHolder certificateHolder) { + RDN[] organizationRDNs = certificateHolder.getIssuer().getRDNs(BCStyle.O); + if (organizationRDNs.length > 0) { + return organizationRDNs[0].getFirst().getValue().toString(); + } + RDN[] organizationUnitRDNs = certificateHolder.getIssuer().getRDNs(BCStyle.OU); + if (organizationUnitRDNs.length > 0) { + return organizationUnitRDNs[0].getFirst().getValue().toString(); + } + RDN[] commonNameRDNs = certificateHolder.getIssuer().getRDNs(BCStyle.CN); + if (commonNameRDNs.length > 0) { + return commonNameRDNs[0].getFirst().getValue().toString(); + } + + return "-"; + } + + private static String getFormattedDateTime(Date date) { + try { + SimpleDateFormat dateFormat = new SimpleDateFormat("dd.MM.yyyy", Locale.getDefault()); + if (date != null) { + return dateFormat.format(date); + } + } catch (IllegalStateException e) { + Timber.log(Log.ERROR, e, "Unable to format date"); + } + return "-"; + } + + private SivaSetting getSivaSetting(SettingsDataStore settingsDataStore) { + if (settingsDataStore != null) { + return settingsDataStore.getSivaSetting(); + } + return DEFAULT; + } + + private void saveSivaUrl(String sivaServiceUrl) { + Optional sivaUrl = Optional.ofNullable(sivaServiceUrl) + .filter(text -> !text.isEmpty()); + if (sivaUrl.isPresent()) { + setSivaUrl(settingsDataStore, sivaUrl.get().trim()); + } else { + String defaultSivaUrl = configurationProvider.getSivaUrl(); + setSivaUrl(settingsDataStore, defaultSivaUrl); + setSivaPlaceholderText(defaultSivaUrl); + } + } + + private void checkSivaServiceSetting(SettingsDataStore settingsDataStore, + String defaultSivaUrl) { + SivaSetting currentSivaSetting = getSivaSetting(settingsDataStore); + switch (currentSivaSetting) { + case DEFAULT -> { + String trimmedDefaultSivaUrl = defaultSivaUrl.trim(); + setSivaPlaceholderText(trimmedDefaultSivaUrl); + setSivaUrl(settingsDataStore, trimmedDefaultSivaUrl); + sivaServiceDefaultChoice.setChecked(true); + isSivaCertViewShown(GONE); + setSivaUrlEnabled(false); + } + case MANUAL -> { + sivaServiceManualChoice.setChecked(true); + isSivaCertViewShown(VISIBLE); + setSivaUrlEnabled(true); + String manualSivaUrl = settingsDataStore.getSivaUrl(); + if (!manualSivaUrl.isEmpty() && !defaultSivaUrl.equals(manualSivaUrl)) { + sivaServiceUrl.setText(manualSivaUrl.trim()); + } else { + setSivaUrl(settingsDataStore, defaultSivaUrl); + setSivaPlaceholderText(defaultSivaUrl); + } + } + } + } + + private void isSivaCertViewShown(int visibility) { + sivaServiceCertificateContainer.setVisibility(visibility); + } + + private void setSivaServiceSetting(SettingsDataStore settingsDataStore, + String defaultSivaUrl, int buttonId) { + if (settingsDataStore != null) { + if (sivaServiceDefaultChoice.getId() == buttonId) { + settingsDataStore.setSivaSetting(DEFAULT); + } else if (sivaServiceManualChoice.getId() == buttonId) { + settingsDataStore.setSivaSetting(MANUAL); + } + + checkSivaServiceSetting(settingsDataStore, defaultSivaUrl); + } + } + + private void setSivaUrlEnabled(boolean isEnabled) { + sivaServiceUrl.setEnabled(isEnabled); + } + + private void setSivaUrl(SettingsDataStore settingsDataStore, String sivaUrl) { + if (settingsDataStore != null) { + settingsDataStore.setSivaUrl(sivaUrl); + } + } + + private void setSivaPlaceholderText(String text) { + sivaServiceUrlLayout.setPlaceholderText(text); + } + + private void removeSivaCert(SettingsDataStore settingsDataStore, TextView issuedTo, TextView validTo) { + String sivaCertName = settingsDataStore.getSivaCertName(); + File sivaFile = FileUtil.getCertFile(getContext(), sivaCertName, DIR_SIVA_CERT); + + if (sivaFile != null) { + FileUtils.removeFile(sivaFile.getPath()); + } + settingsDataStore.setSivaCertName(null); + + issuedTo.setText(navigator.activity().getResources().getText(R.string.main_settings_timestamp_cert_issued_to_title)); + validTo.setText(navigator.activity().getResources().getText(R.string.main_settings_timestamp_cert_valid_to_title)); + } + + @Override + public void onAttachedToWindow() { + super.onAttachedToWindow(); + + sivaServiceUrl.clearFocus(); + + disposables.attach(); + disposables.add(clicks(backButton).subscribe(o -> dismiss())); + disposables.add(checkedChanges(sivaServiceChoiceGroup).subscribe(setting -> + setSivaServiceSetting(settingsDataStore, configurationProvider.getSivaUrl(), setting))); + disposables.add(textChanges(sivaServiceUrl).subscribe(text -> { + SivaSetting currentSivaSetting = getSivaSetting(settingsDataStore); + if (currentSivaSetting == MANUAL && text.toString().isEmpty() && !previousSivaUrl.isEmpty()) { + removeSivaCert(settingsDataStore, sivaCertificateIssuedTo, sivaCertificateValidTo); + } + previousSivaUrl = text.toString(); + })); + disposables.add(clicks(sivaCertificateAddCertificateButton).subscribe(o -> + navigator.execute(Transaction.push(ChooseFileScreen.create(false, true))))); + disposables.add(clicks(sivaCertificateShowCertificateButton).subscribe(o -> { + if (sivaCertificate != null) { + dismiss(); + navigator.execute(Transaction.push(CertificateDetailScreen.create(sivaCertificate))); + } + })); + disposables.add(viewModel.viewStates().subscribe(this::render)); + } + + @Override + public void onDetachedFromWindow() { + disposables.detach(); + SivaSetting currentSivaSetting = getSivaSetting(settingsDataStore); + Editable sivaUrl = sivaServiceUrl.getText(); + if (sivaUrl != null) { + saveSivaUrl(currentSivaSetting == DEFAULT ? + configurationProvider.getSivaUrl() : sivaUrl.toString()); + } else { + saveSivaUrl(configurationProvider.getSivaUrl()); + } + if (sivaUrlTextWatcher != null) { + sivaServiceUrl.removeTextChangedListener(sivaUrlTextWatcher); + } + super.onDetachedFromWindow(); + } + + @Override + public void onWindowFocusChanged(boolean hasFocus) { + super.onWindowFocusChanged(hasFocus); + + if (hasFocus) { + updateData(settingsDataStore); + } + } +} diff --git a/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/siva/SivaSetting.java b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/siva/SivaSetting.java new file mode 100644 index 000000000..f0acc550b --- /dev/null +++ b/app/src/main/java/ee/ria/DigiDoc/android/main/settings/signing/siva/SivaSetting.java @@ -0,0 +1,6 @@ +package ee.ria.DigiDoc.android.main.settings.signing.siva; + +public enum SivaSetting { + DEFAULT, + MANUAL +} diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureAddSource.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureAddSource.java index 4f1ecf562..de0a833d9 100644 --- a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureAddSource.java +++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/SignatureAddSource.java @@ -101,7 +101,9 @@ Observable sign(File containerFile, Observable.create(new MobileIdOnSubscribe(navigator, container, localeService.applicationLocale(), settingsDataStore.getUuid(), mobileIdRequest.personalCode(), - mobileIdRequest.phoneNo(), roleData))) + mobileIdRequest.phoneNo(), settingsDataStore.getIsProxyForSSLEnabled(), + settingsDataStore.getProxySetting(), + settingsDataStore.getManualProxySettings(navigator.activity()), roleData))) .switchMap(response -> { String signature = response.signature(); Button mobileIdCancelButton = navigator.activity().findViewById(R.id.signatureUpdateMobileIdCancelButton); @@ -137,7 +139,10 @@ Observable sign(File containerFile, .flatMapObservable(container -> Observable.create(new SmartIdOnSubscribe(navigator, container, localeService.applicationLocale(), settingsDataStore.getUuid(), - smartIdRequest.personalCode(), smartIdRequest.country(), roleData))) + smartIdRequest.personalCode(), smartIdRequest.country(), + settingsDataStore.getIsProxyForSSLEnabled(), + settingsDataStore.getProxySetting(), + settingsDataStore.getManualProxySettings(navigator.activity()), roleData))) .switchMap(response -> { SessionStatusResponse.ProcessStatus processStatus = response.status(); Button smartIdCancelButton = navigator.activity().findViewById(R.id.signatureUpdateSmartIdCancelButton); diff --git a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/mobileid/MobileIdOnSubscribe.java b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/mobileid/MobileIdOnSubscribe.java index b4de58e77..d8387fd6e 100644 --- a/app/src/main/java/ee/ria/DigiDoc/android/signature/update/mobileid/MobileIdOnSubscribe.java +++ b/app/src/main/java/ee/ria/DigiDoc/android/signature/update/mobileid/MobileIdOnSubscribe.java @@ -7,8 +7,14 @@ import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.CREATE_SIGNATURE_CHALLENGE; import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.CREATE_SIGNATURE_REQUEST; import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.CREATE_SIGNATURE_STATUS; +import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.MANUAL_PROXY_HOST; +import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.MANUAL_PROXY_PASSWORD; +import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.MANUAL_PROXY_PORT; +import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.MANUAL_PROXY_USERNAME; import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.MID_BROADCAST_ACTION; import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.MID_BROADCAST_TYPE_KEY; +import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.PROXY_IS_SSL_ENABLED; +import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.PROXY_SETTING; import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.SERVICE_FAULT; import static ee.ria.DigiDoc.mobileid.service.MobileSignConstants.SIGNING_ROLE_DATA; @@ -36,6 +42,8 @@ import ee.ria.DigiDoc.android.ApplicationApp; import ee.ria.DigiDoc.android.model.mobileid.MobileIdMessageException; import ee.ria.DigiDoc.android.utils.navigator.Navigator; +import ee.ria.DigiDoc.common.ManualProxy; +import ee.ria.DigiDoc.common.ProxySetting; import ee.ria.DigiDoc.common.RoleData; import ee.ria.DigiDoc.configuration.ConfigurationProvider; import ee.ria.DigiDoc.mobileid.dto.request.MobileCreateSignatureRequest; @@ -56,12 +64,16 @@ public final class MobileIdOnSubscribe implements ObservableOnSubscribe + + diff --git a/app/src/main/res/drawable/ic_icon_signing.xml b/app/src/main/res/drawable/ic_icon_signing.xml new file mode 100644 index 000000000..cdd34dc89 --- /dev/null +++ b/app/src/main/res/drawable/ic_icon_signing.xml @@ -0,0 +1,26 @@ + + + + + + + + diff --git a/app/src/main/res/layout/certificate_details_screen.xml b/app/src/main/res/layout/certificate_details_screen.xml index 6bee411b4..5c8b529bc 100644 --- a/app/src/main/res/layout/certificate_details_screen.xml +++ b/app/src/main/res/layout/certificate_details_screen.xml @@ -19,13 +19,13 @@ android:id="@id/toolbar" android:nextFocusDown="@+id/certificateDetailSubjectDataTitle" android:nextFocusUp="@+id/certificateDetailFingerprintsSHA1" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:accessibilityHeading="true" android:touchscreenBlocksFocus="false" android:focusable="true" android:focusableInTouchMode="true" - android:layout_width="match_parent" - android:layout_height="wrap_content" - android:theme="@style/ThemeOverlay.Application.ActionBar" - android:accessibilityHeading="true" /> + android:theme="@style/ThemeOverlay.Application.ActionBar" /> diff --git a/app/src/main/res/layout/crypto_home.xml b/app/src/main/res/layout/crypto_home.xml index 0270943ac..4873511d1 100644 --- a/app/src/main/res/layout/crypto_home.xml +++ b/app/src/main/res/layout/crypto_home.xml @@ -8,12 +8,16 @@ diff --git a/app/src/main/res/layout/main_home_menu.xml b/app/src/main/res/layout/main_home_menu.xml index e22d7f2a2..e59a685d1 100644 --- a/app/src/main/res/layout/main_home_menu.xml +++ b/app/src/main/res/layout/main_home_menu.xml @@ -12,6 +12,9 @@ android:layout_height="wrap_content" android:layout_gravity="end" android:layout_margin="?attr/dialogPreferredPadding" + android:touchscreenBlocksFocus="false" + android:focusable="true" + android:focusableInTouchMode="true" android:src="@drawable/ic_clear" android:minWidth="@dimen/material_button_touch_target_height_minimum" app:tint="@color/material_color_white" /> diff --git a/app/src/main/res/layout/main_settings.xml b/app/src/main/res/layout/main_settings.xml index deb090751..d62a0efba 100644 --- a/app/src/main/res/layout/main_settings.xml +++ b/app/src/main/res/layout/main_settings.xml @@ -27,8 +27,10 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:importantForAccessibility="no" - android:accessibilityPaneTitle="@string/main_settings_title" - /> + android:touchscreenBlocksFocus="false" + android:focusable="true" + android:focusableInTouchMode="true" + android:accessibilityPaneTitle="@string/main_settings_title" />