Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 9ae7d27e authored by cketti's avatar cketti
Browse files

Add support for OAuth flow after settings import

parent 94c61a79
Loading
Loading
Loading
Loading
+14 −5
Original line number Diff line number Diff line
@@ -77,17 +77,19 @@ public class SettingsImporter {
        public final AccountDescription original;
        public final AccountDescription imported;
        public final boolean overwritten;
        public final boolean authorizationNeeded;
        public final boolean incomingPasswordNeeded;
        public final boolean outgoingPasswordNeeded;
        public final String incomingServerName;
        public final String outgoingServerName;

        private AccountDescriptionPair(AccountDescription original, AccountDescription imported,
                boolean overwritten, boolean incomingPasswordNeeded, boolean outgoingPasswordNeeded,
                String incomingServerName, String outgoingServerName) {
                boolean overwritten, boolean authorizationNeeded, boolean incomingPasswordNeeded,
                boolean outgoingPasswordNeeded, String incomingServerName, String outgoingServerName) {
            this.original = original;
            this.imported = imported;
            this.overwritten = overwritten;
            this.authorizationNeeded = authorizationNeeded;
            this.incomingPasswordNeeded = incomingPasswordNeeded;
            this.outgoingPasswordNeeded = outgoingPasswordNeeded;
            this.incomingServerName = incomingServerName;
@@ -372,8 +374,11 @@ public class SettingsImporter {

        String incomingServerName = incoming.host;
        boolean incomingPasswordNeeded = AuthType.EXTERNAL != incoming.authenticationType &&
                AuthType.XOAUTH2 != incoming.authenticationType &&
                (incoming.password == null || incoming.password.isEmpty());

        boolean authorizationNeeded = incoming.authenticationType == AuthType.XOAUTH2;

        String incomingServerType = ServerTypeConverter.toServerSettingsType(account.incoming.type);
        if (account.outgoing == null && !incomingServerType.equals(Protocols.WEBDAV)) {
            // All account types except WebDAV need to provide outgoing server settings
@@ -395,15 +400,18 @@ public class SettingsImporter {
             */
            String outgoingServerType = ServerTypeConverter.toServerSettingsType(outgoing.type);
            outgoingPasswordNeeded = AuthType.EXTERNAL != outgoing.authenticationType &&
                    AuthType.XOAUTH2 != outgoing.authenticationType &&
                    !outgoingServerType.equals(Protocols.WEBDAV) &&
                    outgoing.username != null &&
                    !outgoing.username.isEmpty() &&
                    (outgoing.password == null || outgoing.password.isEmpty());

            authorizationNeeded |= outgoing.authenticationType == AuthType.XOAUTH2;

            outgoingServerName = outgoing.host;
        }

        boolean createAccountDisabled = incomingPasswordNeeded || outgoingPasswordNeeded;
        boolean createAccountDisabled = incomingPasswordNeeded || outgoingPasswordNeeded || authorizationNeeded;
        if (createAccountDisabled) {
            editor.putBoolean(accountKeyPrefix + "enabled", false);
        }
@@ -465,7 +473,7 @@ public class SettingsImporter {
        putString(editor, accountKeyPrefix + "messagesNotificationChannelVersion", messageNotificationChannelVersion);

        AccountDescription imported = new AccountDescription(accountName, uuid);
        return new AccountDescriptionPair(original, imported, mergeImportedAccount,
        return new AccountDescriptionPair(original, imported, mergeImportedAccount, authorizationNeeded,
                incomingPasswordNeeded, outgoingPasswordNeeded, incomingServerName, outgoingServerName);
    }

@@ -1061,11 +1069,12 @@ public class SettingsImporter {
        String type = ServerTypeConverter.toServerSettingsType(importedServer.type);
        int port = convertPort(importedServer.port);
        ConnectionSecurity connectionSecurity = convertConnectionSecurity(importedServer.connectionSecurity);
        String password = importedServer.authenticationType == AuthType.XOAUTH2 ? "" : importedServer.password;
        Map<String, String> extra = importedServer.extras != null ?
                unmodifiableMap(importedServer.extras.settings) : emptyMap();

        return new ServerSettings(type, importedServer.host, port, connectionSecurity,
                importedServer.authenticationType, importedServer.username, importedServer.password,
                importedServer.authenticationType, importedServer.username, password,
                importedServer.clientCertificateAlias, extra);
    }

+9 −0
Original line number Diff line number Diff line
@@ -18,7 +18,16 @@ class AccountActivator(
        val account = preferences.getAccount(accountUuid) ?: error("Account $accountUuid not found")

        setAccountPasswords(account, incomingServerPassword, outgoingServerPassword)
        enableAccount(account)
    }

    fun enableAccount(accountUuid: String) {
        val account = preferences.getAccount(accountUuid) ?: error("Account $accountUuid not found")

        enableAccount(account)
    }

    private fun enableAccount(account: Account) {
        // Start services if necessary
        Core.setServicesEnabled(context)

+25 −0
Original line number Diff line number Diff line
@@ -14,6 +14,7 @@ import androidx.core.view.isVisible
import androidx.fragment.app.Fragment
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.RecyclerView
import com.fsck.k9.activity.setup.OAuthFlowActivity
import com.fsck.k9.ui.R
import com.fsck.k9.ui.observeNotNull
import com.mikepenz.fastadapter.FastAdapter
@@ -103,6 +104,12 @@ class SettingsImportFragment : Fragment() {
            StatusText.IMPORT_SUCCESS_PASSWORD_REQUIRED -> {
                statusText.text = getString(R.string.settings_import_password_required)
            }
            StatusText.IMPORT_SUCCESS_AUTHORIZATION_REQUIRED -> {
                statusText.text = getString(R.string.settings_import_authorization_required)
            }
            StatusText.IMPORT_SUCCESS_PASSWORD_AND_AUTHORIZATION_REQUIRED -> {
                statusText.text = getString(R.string.settings_import_authorization_and_password_required)
            }
            StatusText.IMPORT_READ_FAILURE -> {
                statusText.text = getString(R.string.settings_import_read_failure)
            }
@@ -142,6 +149,7 @@ class SettingsImportFragment : Fragment() {
            is Action.Close -> closeImportScreen(action)
            is Action.PickDocument -> pickDocument()
            is Action.PasswordPrompt -> showPasswordPrompt(action)
            is Action.StartAuthorization -> startAuthorization(action)
        }
    }

@@ -160,6 +168,15 @@ class SettingsImportFragment : Fragment() {
        startActivityForResult(createDocumentIntent, REQUEST_PICK_DOCUMENT)
    }

    private fun startAuthorization(action: Action.StartAuthorization) {
        val intent = OAuthFlowActivity.buildLaunchIntent(
            context = requireContext(),
            accountUuid = action.accountUuid
        )

        startActivityForResult(intent, REQUEST_AUTHORIZATION)
    }

    private fun showPasswordPrompt(action: Action.PasswordPrompt) {
        val dialogFragment = PasswordPromptDialogFragment.create(
            action.accountUuid,
@@ -183,6 +200,7 @@ class SettingsImportFragment : Fragment() {
        when (requestCode) {
            REQUEST_PICK_DOCUMENT -> handlePickDocumentResult(resultCode, data)
            REQUEST_PASSWORD_PROMPT -> handlePasswordPromptResult(resultCode, data)
            REQUEST_AUTHORIZATION -> handleAuthorizationResult(resultCode)
        }
    }

@@ -203,9 +221,16 @@ class SettingsImportFragment : Fragment() {
        }
    }

    private fun handleAuthorizationResult(resultCode: Int) {
        if (resultCode == Activity.RESULT_OK) {
            viewModel.onReturnAfterAuthorization()
        }
    }

    companion object {
        private const val REQUEST_PICK_DOCUMENT = Activity.RESULT_FIRST_USER
        private const val REQUEST_PASSWORD_PROMPT = Activity.RESULT_FIRST_USER + 1
        private const val REQUEST_AUTHORIZATION = Activity.RESULT_FIRST_USER + 2
    }
}

+4 −2
Original line number Diff line number Diff line
@@ -32,8 +32,9 @@ abstract class ImportListItem<VH : ImportCheckBoxViewHolder>(
            val imageLevel = when (importStatus) {
                ImportStatus.IMPORT_SUCCESS -> 0
                ImportStatus.IMPORT_SUCCESS_PASSWORD_REQUIRED -> 1
                ImportStatus.NOT_SELECTED -> 2
                ImportStatus.IMPORT_FAILURE -> 3
                ImportStatus.IMPORT_SUCCESS_AUTHORIZATION_REQUIRED -> 2
                ImportStatus.NOT_SELECTED -> 3
                ImportStatus.IMPORT_FAILURE -> 4
                else -> error("Unexpected import status: $importStatus")
            }
            holder.statusIcon.setImageLevel(imageLevel)
@@ -41,6 +42,7 @@ abstract class ImportListItem<VH : ImportCheckBoxViewHolder>(
            val contentDescriptionStringResId = when (importStatus) {
                ImportStatus.IMPORT_SUCCESS -> R.string.settings_import_status_success
                ImportStatus.IMPORT_SUCCESS_PASSWORD_REQUIRED -> R.string.settings_import_status_password_required
                ImportStatus.IMPORT_SUCCESS_AUTHORIZATION_REQUIRED -> R.string.settings_import_status_log_in_required
                ImportStatus.NOT_SELECTED -> R.string.settings_import_status_not_imported
                ImportStatus.IMPORT_FAILURE -> R.string.settings_import_status_error
                else -> error("Unexpected import status: $importStatus")
+29 −20
Original line number Diff line number Diff line
@@ -67,13 +67,13 @@ class SettingsImportUiModel {
        statusText = StatusText.IMPORT_SUCCESS
    }

    private fun showPasswordRequiredText() {
    private fun showActionRequiredText(actionText: StatusText) {
        importButton = ButtonState.GONE
        closeButton = ButtonState.ENABLED
        closeButtonLabel = CloseButtonLabel.LATER
        isImportProgressVisible = false
        isSettingsListEnabled = true
        statusText = StatusText.IMPORT_SUCCESS_PASSWORD_REQUIRED
        statusText = actionText
    }

    fun showReadFailureText() {
@@ -120,7 +120,7 @@ class SettingsImportUiModel {

    fun setSettingsListState(position: Int, status: ImportStatus) {
        settingsList[position].importStatus = status
        settingsList[position].enabled = status == ImportStatus.IMPORT_SUCCESS_PASSWORD_REQUIRED
        settingsList[position].enabled = status.isActionRequired
    }

    private fun updateImportButtonFromSelection() {
@@ -141,12 +141,20 @@ class SettingsImportUiModel {
            return
        }

        val passwordsMissing = settingsList.any { it.importStatus == ImportStatus.IMPORT_SUCCESS_PASSWORD_REQUIRED }
        if (passwordsMissing) {
            showPasswordRequiredText()
            return
        val passwordsMissing = settingsList.any {
            it.importStatus == ImportStatus.IMPORT_SUCCESS_PASSWORD_REQUIRED
        }
        val authorizationRequired = settingsList.any {
            it.importStatus == ImportStatus.IMPORT_SUCCESS_AUTHORIZATION_REQUIRED
        }

        if (passwordsMissing && authorizationRequired) {
            showActionRequiredText(StatusText.IMPORT_SUCCESS_PASSWORD_AND_AUTHORIZATION_REQUIRED)
        } else if (passwordsMissing) {
            showActionRequiredText(StatusText.IMPORT_SUCCESS_PASSWORD_REQUIRED)
        } else if (authorizationRequired) {
            showActionRequiredText(StatusText.IMPORT_SUCCESS_AUTHORIZATION_REQUIRED)
        } else {
            val partialImportError = settingsList.any { it.importStatus == ImportStatus.IMPORT_FAILURE }
            if (partialImportError) {
                showPartialImportErrorText()
@@ -155,6 +163,7 @@ class SettingsImportUiModel {
            }
        }
    }
}

sealed class SettingsListItem {
    var selected: Boolean = true
@@ -165,15 +174,13 @@ sealed class SettingsListItem {
    class Account(val accountIndex: Int, var displayName: String) : SettingsListItem()
}

enum class ImportStatus {
    NOT_AVAILABLE,
    NOT_SELECTED,
    IMPORT_SUCCESS,
    IMPORT_SUCCESS_PASSWORD_REQUIRED,
    IMPORT_FAILURE;

    val isSuccess: Boolean
        get() = this == IMPORT_SUCCESS || this == IMPORT_SUCCESS_PASSWORD_REQUIRED
enum class ImportStatus(val isSuccess: Boolean, val isActionRequired: Boolean) {
    NOT_AVAILABLE(isSuccess = false, isActionRequired = false),
    NOT_SELECTED(isSuccess = false, isActionRequired = false),
    IMPORT_SUCCESS(isSuccess = true, isActionRequired = false),
    IMPORT_SUCCESS_PASSWORD_REQUIRED(isSuccess = true, isActionRequired = true),
    IMPORT_SUCCESS_AUTHORIZATION_REQUIRED(isSuccess = true, isActionRequired = true),
    IMPORT_FAILURE(isSuccess = false, isActionRequired = false)
}

enum class ButtonState {
@@ -188,6 +195,8 @@ enum class StatusText {
    IMPORTING_PROGRESS,
    IMPORT_SUCCESS,
    IMPORT_SUCCESS_PASSWORD_REQUIRED,
    IMPORT_SUCCESS_AUTHORIZATION_REQUIRED,
    IMPORT_SUCCESS_PASSWORD_AND_AUTHORIZATION_REQUIRED,
    IMPORT_READ_FAILURE,
    IMPORT_PARTIAL_FAILURE,
    IMPORT_FAILURE
Loading