Remove AccountSetupCheckSettings activity

This commit is contained in:
Wolf-Martell Montwé 2023-11-29 16:02:52 +01:00
parent 5f5c17f62e
commit 0f14eb8545
No known key found for this signature in database
GPG key ID: 6D45B21512ACBF72
8 changed files with 8 additions and 577 deletions

View file

@ -364,6 +364,7 @@ public class MessagingController {
put("refreshFolderList", null, () -> refreshFolderListSynchronous(account));
}
@Deprecated
public void refreshFolderListSynchronous(Account account) {
try {
if (isAuthenticationProblem(account, true)) {
@ -1671,10 +1672,12 @@ public class MessagingController {
return getBackend(account).getSupportsFolderSubscriptions();
}
@Deprecated
public void checkIncomingServerSettings(Account account) throws MessagingException {
getBackend(account).checkIncomingServerSettings();
}
@Deprecated
public void checkOutgoingServerSettings(Account account) throws MessagingException {
getBackend(account).checkOutgoingServerSettings();
}
@ -2440,6 +2443,7 @@ public class MessagingController {
notificationController.removeNewMailNotification(account, messageReference);
}
@Deprecated
public void clearCertificateErrorNotifications(Account account, boolean incoming) {
notificationController.clearCertificateErrorNotifications(account, incoming);
}

View file

@ -6,6 +6,7 @@ import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import com.fsck.k9.Account
@Deprecated("Remove once all usages have been removed for the new account edit feature")
internal open class CertificateErrorNotificationController(
private val notificationHelper: NotificationHelper,
private val actionCreator: NotificationActionCreator,

View file

@ -106,13 +106,6 @@
android:taskAffinity=""
android:theme="@style/Theme.K9.Dialog.Translucent.DayNight"/>
<!-- XXX Note: this activity is hacked to ignore config changes,
since it doesn't currently handle them correctly in code. -->
<activity
android:name=".activity.setup.AccountSetupCheckSettings"
android:configChanges="keyboardHidden|orientation|locale"
android:label="@string/account_setup_check_settings_title"/>
<activity
android:name=".ui.endtoend.AutocryptKeyTransferActivity"
android:configChanges="locale"

View file

@ -1,501 +0,0 @@
package com.fsck.k9.activity.setup
import android.app.Activity
import android.app.AlertDialog
import android.content.Intent
import android.os.AsyncTask
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.view.View
import android.widget.ProgressBar
import android.widget.TextView
import androidx.fragment.app.DialogFragment
import androidx.fragment.app.commit
import com.fsck.k9.Account
import com.fsck.k9.LocalKeyStoreManager
import com.fsck.k9.Preferences
import com.fsck.k9.controller.MessagingController
import com.fsck.k9.fragment.ConfirmationDialogFragment
import com.fsck.k9.fragment.ConfirmationDialogFragment.ConfirmationDialogFragmentListener
import com.fsck.k9.mail.AuthType
import com.fsck.k9.mail.AuthenticationFailedException
import com.fsck.k9.mail.CertificateValidationException
import com.fsck.k9.mail.MailServerDirection
import com.fsck.k9.mail.filter.Hex
import com.fsck.k9.ui.R
import com.fsck.k9.ui.base.K9Activity
import com.fsck.k9.ui.observe
import java.security.MessageDigest
import java.security.NoSuchAlgorithmException
import java.security.cert.CertificateEncodingException
import java.security.cert.CertificateException
import java.security.cert.X509Certificate
import java.util.Locale
import java.util.concurrent.Executors
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.viewModel
import timber.log.Timber
/**
* Checks the given settings to make sure that they can be used to send and receive mail.
*
* XXX NOTE: The manifest for this app has it ignore config changes, because it doesn't correctly deal with restarting
* while its thread is running.
*/
@Deprecated("Remove once all usages have been removed for the new account edit feature")
class AccountSetupCheckSettings : K9Activity(), ConfirmationDialogFragmentListener {
private val authViewModel: AuthViewModel by viewModel()
private val messagingController: MessagingController by inject()
private val preferences: Preferences by inject()
private val localKeyStoreManager: LocalKeyStoreManager by inject()
private val handler = Handler(Looper.myLooper()!!)
private lateinit var progressBar: ProgressBar
private lateinit var messageView: TextView
private lateinit var account: Account
private lateinit var direction: CheckDirection
@Volatile
private var canceled = false
@Volatile
private var destroyed = false
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setLayout(R.layout.account_setup_check_settings)
messageView = findViewById(R.id.message)
progressBar = findViewById(R.id.progress)
findViewById<View>(R.id.cancel).setOnClickListener { onCancel() }
setMessage(R.string.account_setup_check_settings_retr_info_msg)
progressBar.isIndeterminate = true
val accountUuid = intent.getStringExtra(EXTRA_ACCOUNT) ?: error("Missing account UUID")
account = preferences.getAccount(accountUuid) ?: error("Could not find account")
direction = intent.getSerializableExtra(EXTRA_CHECK_DIRECTION) as CheckDirection?
?: error("Missing CheckDirection")
authViewModel.init(activityResultRegistry, lifecycle, account)
authViewModel.uiState.observe(this) { state ->
when (state) {
AuthFlowState.Idle -> {
return@observe
}
AuthFlowState.Success -> {
startCheckServerSettings()
}
AuthFlowState.Canceled -> {
showErrorDialog(R.string.account_setup_failed_dlg_oauth_flow_canceled)
}
is AuthFlowState.Failed -> {
showErrorDialog(R.string.account_setup_failed_dlg_oauth_flow_failed, state)
}
AuthFlowState.NotSupported -> {
showErrorDialog(R.string.account_setup_failed_dlg_oauth_not_supported)
}
AuthFlowState.BrowserNotFound -> {
showErrorDialog(R.string.account_setup_failed_dlg_browser_not_found)
}
}
authViewModel.authResultConsumed()
}
if (savedInstanceState == null) {
if (needsAuthorization()) {
setMessage(R.string.account_setup_check_settings_authenticate)
authViewModel.login()
} else {
startCheckServerSettings()
}
}
}
private fun needsAuthorization(): Boolean {
return (
account.incomingServerSettings.authenticationType == AuthType.XOAUTH2 ||
account.outgoingServerSettings.authenticationType == AuthType.XOAUTH2
) &&
!authViewModel.isAuthorized(account)
}
private fun startCheckServerSettings() {
CheckAccountTask(account).executeOnExecutor(Executors.newSingleThreadExecutor(), direction)
}
private fun handleCertificateValidationException(exception: CertificateValidationException) {
Timber.e(exception, "Error while testing settings")
val chain = exception.certChain
// Avoid NullPointerException in acceptKeyDialog()
if (chain != null) {
acceptKeyDialog(
R.string.account_setup_failed_dlg_certificate_message_fmt,
exception,
)
} else {
showErrorDialog(
R.string.account_setup_failed_dlg_server_message_fmt,
errorMessageForCertificateException(exception)!!,
)
}
}
override fun onDestroy() {
super.onDestroy()
destroyed = true
canceled = true
}
private fun setMessage(resId: Int) {
messageView.text = getString(resId)
}
private fun acceptKeyDialog(msgResId: Int, exception: CertificateValidationException) {
handler.post {
if (destroyed) {
return@post
}
val errorMessage = exception.cause?.cause?.message ?: exception.cause?.message ?: exception.message
progressBar.isIndeterminate = false
val chainInfo = StringBuilder()
val chain = exception.certChain
// We already know chain != null (tested before calling this method)
for (i in chain.indices) {
// display certificate chain information
// TODO: localize this strings
chainInfo.append("Certificate chain[").append(i).append("]:\n")
chainInfo.append("Subject: ").append(chain[i].subjectDN.toString()).append("\n")
// display SubjectAltNames too
// (the user may be mislead into mistrusting a certificate
// by a subjectDN not matching the server even though a
// SubjectAltName matches)
try {
val subjectAlternativeNames = chain[i].subjectAlternativeNames
if (subjectAlternativeNames != null) {
// TODO: localize this string
val altNamesText = StringBuilder()
altNamesText.append("Subject has ")
.append(subjectAlternativeNames.size)
.append(" alternative names\n")
// we need these for matching
val incomingServerHost = account.incomingServerSettings.host!!
val outgoingServerHost = account.outgoingServerSettings.host!!
for (subjectAlternativeName in subjectAlternativeNames) {
val type = subjectAlternativeName[0] as Int
val value: Any? = subjectAlternativeName[1]
val name: String = when (type) {
0 -> {
Timber.w("SubjectAltName of type OtherName not supported.")
continue
}
1 -> value as String
2 -> value as String
3 -> {
Timber.w("unsupported SubjectAltName of type x400Address")
continue
}
4 -> {
Timber.w("unsupported SubjectAltName of type directoryName")
continue
}
5 -> {
Timber.w("unsupported SubjectAltName of type ediPartyName")
continue
}
6 -> value as String
7 -> value as String
else -> {
Timber.w("unsupported SubjectAltName of unknown type")
continue
}
}
// if some of the SubjectAltNames match the store or transport -host, display them
if (name.equals(incomingServerHost, ignoreCase = true) ||
name.equals(outgoingServerHost, ignoreCase = true)
) {
// TODO: localize this string
altNamesText.append("Subject(alt): ").append(name).append(",...\n")
} else if (name.startsWith("*.") &&
(
incomingServerHost.endsWith(name.substring(2)) ||
outgoingServerHost.endsWith(name.substring(2))
)
) {
// TODO: localize this string
altNamesText.append("Subject(alt): ").append(name).append(",...\n")
}
}
chainInfo.append(altNamesText)
}
} catch (e: Exception) {
// don't fail just because of subjectAltNames
Timber.w(e, "cannot display SubjectAltNames in dialog")
}
chainInfo.append("Issuer: ").append(chain[i].issuerDN.toString()).append("\n")
for (algorithm in arrayOf("SHA-1", "SHA-256", "SHA-512")) {
val digest = try {
MessageDigest.getInstance(algorithm)
} catch (e: NoSuchAlgorithmException) {
Timber.e(e, "Error while initializing MessageDigest ($algorithm)")
null
}
if (digest != null) {
digest.reset()
try {
val hash = Hex.encodeHex(digest.digest(chain[i].encoded))
chainInfo.append("Fingerprint ($algorithm): ").append("\n").append(hash).append("\n")
} catch (e: CertificateEncodingException) {
Timber.e(e, "Error while encoding certificate")
}
}
}
}
// TODO: refactor with DialogFragment.
// This is difficult because we need to pass through chain[0] for onClick()
AlertDialog.Builder(this@AccountSetupCheckSettings)
.setTitle(getString(R.string.account_setup_failed_dlg_invalid_certificate_title))
.setMessage(getString(msgResId, errorMessage) + " " + chainInfo.toString())
.setCancelable(true)
.setPositiveButton(R.string.account_setup_failed_dlg_invalid_certificate_accept) { _, _ ->
acceptCertificate(chain[0])
}
.setNegativeButton(R.string.account_setup_failed_dlg_invalid_certificate_reject) { _, _ ->
finish()
}
.show()
}
}
/**
* Permanently accepts a certificate for the INCOMING or OUTGOING direction by adding it to the local key store.
*/
private fun acceptCertificate(certificate: X509Certificate) {
try {
localKeyStoreManager.addCertificate(account, direction.toMailServerDirection(), certificate)
} catch (e: CertificateException) {
showErrorDialog(R.string.account_setup_failed_dlg_certificate_message_fmt, e.message.orEmpty())
}
actionCheckSettings(this@AccountSetupCheckSettings, account, direction)
}
override fun onActivityResult(reqCode: Int, resCode: Int, data: Intent?) {
if (reqCode == ACTIVITY_REQUEST_CODE) {
setResult(resCode)
finish()
} else {
super.onActivityResult(reqCode, resCode, data)
}
}
private fun onCancel() {
canceled = true
setMessage(R.string.account_setup_check_settings_canceling_msg)
setResult(RESULT_CANCELED)
finish()
}
private fun showErrorDialog(msgResId: Int, vararg args: Any) {
handler.post {
showDialogFragment(R.id.dialog_account_setup_error, getString(msgResId, *args))
}
}
private fun showDialogFragment(dialogId: Int, customMessage: String) {
if (destroyed) return
progressBar.isIndeterminate = false
val fragment: DialogFragment = if (dialogId == R.id.dialog_account_setup_error) {
ConfirmationDialogFragment.newInstance(
dialogId,
getString(R.string.account_setup_failed_dlg_title),
customMessage,
getString(R.string.account_setup_failed_dlg_edit_details_action),
getString(R.string.account_setup_failed_dlg_continue_action),
)
} else {
throw RuntimeException("Called showDialog(int) with unknown dialog id.")
}
// TODO: commitAllowingStateLoss() is used to prevent https://code.google.com/p/android/issues/detail?id=23761
// but is a bad...
supportFragmentManager.commit(allowStateLoss = true) {
add(fragment, getDialogTag(dialogId))
}
}
private fun getDialogTag(dialogId: Int): String {
return String.format(Locale.US, "dialog-%d", dialogId)
}
override fun doPositiveClick(dialogId: Int) {
if (dialogId == R.id.dialog_account_setup_error) {
finish()
}
}
override fun doNegativeClick(dialogId: Int) {
if (dialogId == R.id.dialog_account_setup_error) {
canceled = false
setResult(RESULT_OK)
finish()
}
}
override fun dialogCancelled(dialogId: Int) = Unit
private fun errorMessageForCertificateException(e: CertificateValidationException): String? {
return when (e.reason) {
CertificateValidationException.Reason.Expired -> {
getString(R.string.client_certificate_expired, e.alias, e.message)
}
CertificateValidationException.Reason.MissingCapability -> {
getString(R.string.auth_external_error)
}
CertificateValidationException.Reason.RetrievalFailure -> {
getString(R.string.client_certificate_retrieval_failure, e.alias)
}
CertificateValidationException.Reason.UseMessage -> {
e.message
}
else -> {
""
}
}
}
/**
* FIXME: Don't use an AsyncTask to perform network operations.
* See also discussion in https://github.com/thunderbird/thunderbird-android/pull/560
*/
private inner class CheckAccountTask(private val account: Account) : AsyncTask<CheckDirection, Int, Unit>() {
override fun doInBackground(vararg params: CheckDirection) {
val direction = params[0]
try {
/*
* This task could be interrupted at any point, but network operations can block,
* so relying on InterruptedException is not enough. Instead, check after
* each potentially long-running operation.
*/
if (isCanceled()) {
return
}
clearCertificateErrorNotifications(direction)
checkServerSettings(direction)
if (isCanceled()) {
return
}
setResult(RESULT_OK)
finish()
} catch (e: AuthenticationFailedException) {
Timber.e(e, "Error while testing settings")
showErrorDialog(R.string.account_setup_failed_dlg_auth_message_fmt, e.messageFromServer.orEmpty())
} catch (e: CertificateValidationException) {
handleCertificateValidationException(e)
} catch (e: Exception) {
Timber.e(e, "Error while testing settings")
showErrorDialog(R.string.account_setup_failed_dlg_server_message_fmt, e.message.orEmpty())
}
}
private fun clearCertificateErrorNotifications(direction: CheckDirection) {
val incoming = direction == CheckDirection.INCOMING
messagingController.clearCertificateErrorNotifications(account, incoming)
}
private fun isCanceled(): Boolean {
if (destroyed) return true
if (canceled) {
finish()
return true
}
return false
}
private fun checkServerSettings(direction: CheckDirection) {
when (direction) {
CheckDirection.INCOMING -> checkIncoming()
CheckDirection.OUTGOING -> checkOutgoing()
}
}
private fun checkOutgoing() {
publishProgress(R.string.account_setup_check_settings_check_outgoing_msg)
messagingController.checkOutgoingServerSettings(account)
}
private fun checkIncoming() {
publishProgress(R.string.account_setup_check_settings_check_incoming_msg)
messagingController.checkIncomingServerSettings(account)
messagingController.refreshFolderListSynchronous(account)
val inboxFolderId = account.inboxFolderId
if (inboxFolderId != null) {
messagingController.synchronizeMailbox(account, inboxFolderId, false, null)
}
}
override fun onProgressUpdate(vararg values: Int?) {
setMessage(values[0]!!)
}
}
enum class CheckDirection {
INCOMING, OUTGOING;
fun toMailServerDirection(): MailServerDirection {
return when (this) {
INCOMING -> MailServerDirection.INCOMING
OUTGOING -> MailServerDirection.OUTGOING
}
}
}
companion object {
const val ACTIVITY_REQUEST_CODE = 1
private const val EXTRA_ACCOUNT = "account"
private const val EXTRA_CHECK_DIRECTION = "checkDirection"
@JvmStatic
fun actionCheckSettings(context: Activity, account: Account, direction: CheckDirection) {
val intent = Intent(context, AccountSetupCheckSettings::class.java).apply {
putExtra(EXTRA_ACCOUNT, account.uuid)
putExtra(EXTRA_CHECK_DIRECTION, direction)
}
context.startActivityForResult(intent, ACTIVITY_REQUEST_CODE)
}
}
}

View file

@ -4,6 +4,9 @@ import android.os.Parcelable
import com.fsck.k9.mail.AuthType
import kotlinx.parcelize.Parcelize
@Deprecated(
message = "Remove once all usages have been removed for the new account edit feature",
)
@Parcelize
data class InitialAccountSettings(
val authenticationType: AuthType,

View file

@ -1,48 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
tools:context="com.fsck.k9.activity.setup.AccountSetupCheckSettings">
<include layout="@layout/toolbar" />
<ScrollView
android:layout_width="fill_parent"
android:layout_height="0dp"
android:layout_weight="1"
android:padding="6dip"
android:fadingEdge="none"
android:scrollbarStyle="outsideInset">
<LinearLayout
android:orientation="vertical"
android:layout_gravity="center_horizontal|center_vertical"
android:layout_height="wrap_content"
android:padding="6dip"
android:layout_width="fill_parent">
<TextView
android:id="@+id/message"
android:layout_height="wrap_content"
android:layout_width="fill_parent"
android:gravity="center_horizontal"
android:textAppearance="?android:attr/textAppearanceMedium"
android:textColor="?android:attr/textColorPrimary"
android:paddingBottom="6dip"
android:focusable="true"
/>
<ProgressBar
android:id="@+id/progress"
android:layout_height="wrap_content"
android:layout_width="wrap_content"
android:layout_gravity="center_horizontal|center_vertical"
android:focusable="true"/>
</LinearLayout>
</ScrollView>
<include layout="@layout/wizard_cancel"/>
</LinearLayout>

View file

@ -6,7 +6,6 @@
<item type="id" name="dialog_confirm_delete"/>
<item type="id" name="dialog_confirm_spam"/>
<item type="id" name="dialog_attachment_progress"/>
<item type="id" name="dialog_account_setup_error"/>
<item type="id" name="settings_export_list_general_item"/>
<item type="id" name="settings_export_list_account_item"/>

View file

@ -356,13 +356,7 @@
<string name="account_setup_basics_show_password_biometrics_title">Verify your identity</string>
<string name="account_setup_basics_show_password_biometrics_subtitle">Unlock to view your password</string>
<string name="account_setup_check_settings_title" />
<string name="account_setup_check_settings_retr_info_msg">Retrieving account information\u2026</string>
<string name="account_setup_check_settings_check_incoming_msg">Checking incoming server settings\u2026</string>
<string name="account_setup_check_settings_check_outgoing_msg">Checking outgoing server settings\u2026</string>
<string name="account_setup_check_settings_authenticate">Authenticating\u2026</string>
<string name="account_setup_check_settings_fetch">Fetching account settings\u2026</string>
<string name="account_setup_check_settings_canceling_msg">Canceling\u2026</string>
<string name="account_setup_auth_type_normal_password">Normal password</string>
<string name="account_setup_auth_type_insecure_password">Password, transmitted insecurely</string>
@ -430,16 +424,10 @@
<string name="move_copy_cannot_copy_unsynced_message">Cannot copy or move a message that is not synchronized with the server</string>
<string name="account_setup_failed_dlg_title">Setup could not finish</string>
<string name="account_setup_failed_dlg_auth_message_fmt">Username or password incorrect.\n(<xliff:g id="error">%s</xliff:g>)</string>
<string name="account_setup_failed_dlg_certificate_message_fmt">The server presented an invalid SSL certificate. Sometimes, this is because of a server misconfiguration. Sometimes it is because someone is trying to attack you or your mail server. If you\'re not sure what\'s up, click Reject and contact the folks who manage your mail server.\n\n(<xliff:g id="error">%s</xliff:g>)</string>
<string name="account_setup_failed_dlg_server_message_fmt">Cannot connect to server.\n(<xliff:g id="error">%s</xliff:g>)</string>
<string name="account_setup_failed_dlg_oauth_flow_canceled">Authorization canceled</string>
<string name="account_setup_failed_dlg_oauth_flow_failed">Authorization failed with the following error: <xliff:g id="error">%s</xliff:g></string>
<string name="account_setup_failed_dlg_oauth_not_supported">OAuth 2.0 is currently not supported with this provider.</string>
<string name="account_setup_failed_dlg_browser_not_found">The app couldn\'t find a browser to use for granting access to your account.</string>
<string name="account_setup_failed_dlg_edit_details_action">Edit details</string>
<string name="account_setup_failed_dlg_continue_action">Continue</string>
<string name="account_settings_push_advanced_title">Advanced</string>
<string name="account_settings_title_fmt">Account settings</string>
@ -716,10 +704,6 @@
<string name="account_delete_dlg_title">Remove Account</string>
<string name="account_setup_failed_dlg_invalid_certificate_title">Unrecognized Certificate</string>
<string name="account_setup_failed_dlg_invalid_certificate_accept">Accept Key</string>
<string name="account_setup_failed_dlg_invalid_certificate_reject">Reject Key</string>
<string name="folder_list_filter_hint">Folder name contains</string>
<string name="folder_list_display_mode_label">Show folders…</string>
@ -1011,13 +995,9 @@
<string name="fetching_attachment_dialog_title_save">Saving draft</string>
<string name="fetching_attachment_dialog_message">Fetching attachment…</string>
<string name="auth_external_error">Unable to authenticate. The server does not advertise the SASL EXTERNAL capability. This could be due to a problem with the client certificate (expired, unknown certificate authority) or some other configuration problem.</string>
<!-- === Client certificates specific ================================================================== -->
<string name="client_certificate_spinner_empty">No client certificate</string>
<string name="client_certificate_spinner_delete">Remove client certificate selection</string>
<string name="client_certificate_retrieval_failure">"Failed to retrieve client certificate for alias \"<xliff:g id="alias">%s</xliff:g>\""</string>
<string name="client_certificate_expired">"Client certificate \"<xliff:g id="certificate_alias">%1$s</xliff:g>\" has expired or is not yet valid (<xliff:g id="exception_message">%2$s</xliff:g>)"</string>
<!-- Note: This references message_view_download_remainder -->
<string name="preview_encrypted">*Encrypted*</string>