mirror of
https://github.com/thunderbird/thunderbird-android.git
synced 2024-09-19 19:52:14 +02:00
Add QR code payload parser
This commit is contained in:
parent
008a5e281c
commit
b57719ed6d
14
feature/migration/qrcode/build.gradle.kts
Normal file
14
feature/migration/qrcode/build.gradle.kts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
plugins {
|
||||||
|
id(ThunderbirdPlugins.Library.android)
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "app.k9mail.feature.migration.qrcode"
|
||||||
|
resourcePrefix = "migration_qrcode_"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation(projects.core.common)
|
||||||
|
implementation(libs.moshi)
|
||||||
|
implementation(libs.timber)
|
||||||
|
}
|
@ -0,0 +1,105 @@
|
|||||||
|
package app.k9mail.feature.migration.qrcode
|
||||||
|
|
||||||
|
import app.k9mail.core.common.mail.EmailAddress
|
||||||
|
import app.k9mail.core.common.net.Hostname
|
||||||
|
import app.k9mail.core.common.net.Port
|
||||||
|
|
||||||
|
internal data class AccountData(
|
||||||
|
val sequenceNumber: Int,
|
||||||
|
val sequenceEnd: Int,
|
||||||
|
val accounts: List<Account>,
|
||||||
|
) {
|
||||||
|
data class Account(
|
||||||
|
val accountName: String,
|
||||||
|
val incomingServer: IncomingServer,
|
||||||
|
val outgoingServerGroups: List<OutgoingServerGroup>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class IncomingServer(
|
||||||
|
val protocol: IncomingServerProtocol,
|
||||||
|
val hostname: Hostname,
|
||||||
|
val port: Port,
|
||||||
|
val connectionSecurity: ConnectionSecurity,
|
||||||
|
val authenticationType: AuthenticationType,
|
||||||
|
val username: String,
|
||||||
|
val password: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class OutgoingServer(
|
||||||
|
val protocol: OutgoingServerProtocol,
|
||||||
|
val hostname: Hostname,
|
||||||
|
val port: Port,
|
||||||
|
val connectionSecurity: ConnectionSecurity,
|
||||||
|
val authenticationType: AuthenticationType,
|
||||||
|
val username: String,
|
||||||
|
val password: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class OutgoingServerGroup(
|
||||||
|
val outgoingServer: OutgoingServer,
|
||||||
|
val identities: List<Identity>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Identity(
|
||||||
|
val emailAddress: EmailAddress,
|
||||||
|
val displayName: String,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
enum class IncomingServerProtocol(val intValue: Int) {
|
||||||
|
Imap(0),
|
||||||
|
Pop3(1),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromInt(value: Int): IncomingServerProtocol {
|
||||||
|
return requireNotNull(entries.find { it.intValue == value }) { "Unsupported value: $value" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
enum class OutgoingServerProtocol(val intValue: Int) {
|
||||||
|
Smtp(0),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromInt(value: Int): OutgoingServerProtocol {
|
||||||
|
return requireNotNull(entries.find { it.intValue == value }) { "Unsupported value: $value" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
enum class ConnectionSecurity(val intValue: Int) {
|
||||||
|
Plain(0),
|
||||||
|
TryStartTls(1),
|
||||||
|
AlwaysStartTls(2),
|
||||||
|
Tls(3),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromInt(value: Int): ConnectionSecurity {
|
||||||
|
return requireNotNull(entries.find { it.intValue == value }) { "Unsupported value: $value" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Suppress("MagicNumber")
|
||||||
|
enum class AuthenticationType(val intValue: Int) {
|
||||||
|
None(0),
|
||||||
|
PasswordCleartext(1),
|
||||||
|
PasswordEncrypted(2),
|
||||||
|
Gssapi(3),
|
||||||
|
Ntlm(4),
|
||||||
|
TlsCertificate(5),
|
||||||
|
OAuth2(6),
|
||||||
|
;
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun fromInt(value: Int): AuthenticationType {
|
||||||
|
return requireNotNull(entries.find { it.intValue == value }) { "Unsupported value: $value" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,44 @@
|
|||||||
|
package app.k9mail.feature.migration.qrcode
|
||||||
|
|
||||||
|
internal data class QrCodeData(
|
||||||
|
val version: Int,
|
||||||
|
val misc: Misc,
|
||||||
|
val accounts: List<Account>,
|
||||||
|
) {
|
||||||
|
data class Misc(
|
||||||
|
val sequenceNumber: Int,
|
||||||
|
val sequenceEnd: Int,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Account(
|
||||||
|
val incomingServer: IncomingServer,
|
||||||
|
val outgoingServers: List<OutgoingServer>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class IncomingServer(
|
||||||
|
val protocol: Int,
|
||||||
|
val hostname: String,
|
||||||
|
val port: Int,
|
||||||
|
val connectionSecurity: Int,
|
||||||
|
val authenticationType: Int,
|
||||||
|
val username: String,
|
||||||
|
val accountName: String?,
|
||||||
|
val password: String?,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class OutgoingServer(
|
||||||
|
val protocol: Int,
|
||||||
|
val hostname: String,
|
||||||
|
val port: Int,
|
||||||
|
val connectionSecurity: Int,
|
||||||
|
val authenticationType: Int,
|
||||||
|
val username: String,
|
||||||
|
val password: String?,
|
||||||
|
val identities: List<Identity>,
|
||||||
|
)
|
||||||
|
|
||||||
|
data class Identity(
|
||||||
|
val emailAddress: String,
|
||||||
|
val displayName: String,
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,153 @@
|
|||||||
|
package app.k9mail.feature.migration.qrcode
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonAdapter
|
||||||
|
import com.squareup.moshi.JsonReader
|
||||||
|
import com.squareup.moshi.JsonWriter
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
internal class QrCodePayloadAdapter : JsonAdapter<QrCodeData>() {
|
||||||
|
override fun fromJson(jsonReader: JsonReader): QrCodeData? {
|
||||||
|
jsonReader.beginArray()
|
||||||
|
|
||||||
|
val version = jsonReader.nextInt()
|
||||||
|
if (version != 1) {
|
||||||
|
// We don't even attempt to read something that is newer than version 1.
|
||||||
|
Timber.d("Unsupported version: %s", version)
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
val misc = readMiscellaneousData(jsonReader)
|
||||||
|
|
||||||
|
val accounts = buildList {
|
||||||
|
do {
|
||||||
|
add(readAccount(jsonReader))
|
||||||
|
} while (jsonReader.hasNext())
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonReader.endArray()
|
||||||
|
|
||||||
|
return QrCodeData(version, misc, accounts)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readMiscellaneousData(jsonReader: JsonReader): QrCodeData.Misc {
|
||||||
|
jsonReader.beginArray()
|
||||||
|
|
||||||
|
val sequenceNumber = jsonReader.nextInt()
|
||||||
|
val sequenceEnd = jsonReader.nextInt()
|
||||||
|
|
||||||
|
skipAdditionalArrayEntries(jsonReader)
|
||||||
|
jsonReader.endArray()
|
||||||
|
|
||||||
|
return QrCodeData.Misc(
|
||||||
|
sequenceNumber,
|
||||||
|
sequenceEnd,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readAccount(jsonReader: JsonReader): QrCodeData.Account {
|
||||||
|
val incomingServer = readIncomingServer(jsonReader)
|
||||||
|
val outgoingServers = readOutgoingServers(jsonReader)
|
||||||
|
|
||||||
|
return QrCodeData.Account(incomingServer, outgoingServers)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readIncomingServer(jsonReader: JsonReader): QrCodeData.IncomingServer {
|
||||||
|
jsonReader.beginArray()
|
||||||
|
|
||||||
|
val protocol = jsonReader.nextInt()
|
||||||
|
val hostname = jsonReader.nextString()
|
||||||
|
val port = jsonReader.nextInt()
|
||||||
|
val connectionSecurity = jsonReader.nextInt()
|
||||||
|
val authenticationType = jsonReader.nextInt()
|
||||||
|
val username = jsonReader.nextString()
|
||||||
|
val accountName = if (jsonReader.hasNext()) jsonReader.nextString() else null
|
||||||
|
val password = if (jsonReader.hasNext()) jsonReader.nextString() else null
|
||||||
|
|
||||||
|
skipAdditionalArrayEntries(jsonReader)
|
||||||
|
jsonReader.endArray()
|
||||||
|
|
||||||
|
return QrCodeData.IncomingServer(
|
||||||
|
protocol,
|
||||||
|
hostname,
|
||||||
|
port,
|
||||||
|
connectionSecurity,
|
||||||
|
authenticationType,
|
||||||
|
username,
|
||||||
|
accountName,
|
||||||
|
password,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readOutgoingServers(jsonReader: JsonReader): List<QrCodeData.OutgoingServer> {
|
||||||
|
jsonReader.beginArray()
|
||||||
|
|
||||||
|
val outgoingServers = buildList {
|
||||||
|
do {
|
||||||
|
add(readOutgoingServer(jsonReader))
|
||||||
|
} while (jsonReader.hasNext())
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonReader.endArray()
|
||||||
|
|
||||||
|
return outgoingServers
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readOutgoingServer(jsonReader: JsonReader): QrCodeData.OutgoingServer {
|
||||||
|
jsonReader.beginArray()
|
||||||
|
|
||||||
|
jsonReader.beginArray()
|
||||||
|
|
||||||
|
val protocol = jsonReader.nextInt()
|
||||||
|
val hostname = jsonReader.nextString()
|
||||||
|
val port = jsonReader.nextInt()
|
||||||
|
val connectionSecurity = jsonReader.nextInt()
|
||||||
|
val authenticationType = jsonReader.nextInt()
|
||||||
|
val username = jsonReader.nextString()
|
||||||
|
val password = if (jsonReader.hasNext()) jsonReader.nextString() else null
|
||||||
|
|
||||||
|
skipAdditionalArrayEntries(jsonReader)
|
||||||
|
jsonReader.endArray()
|
||||||
|
|
||||||
|
val identities = buildList {
|
||||||
|
do {
|
||||||
|
add(readIdentity(jsonReader))
|
||||||
|
} while (jsonReader.hasNext())
|
||||||
|
}
|
||||||
|
|
||||||
|
jsonReader.endArray()
|
||||||
|
|
||||||
|
return QrCodeData.OutgoingServer(
|
||||||
|
protocol,
|
||||||
|
hostname,
|
||||||
|
port,
|
||||||
|
connectionSecurity,
|
||||||
|
authenticationType,
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
identities,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun readIdentity(jsonReader: JsonReader): QrCodeData.Identity {
|
||||||
|
jsonReader.beginArray()
|
||||||
|
|
||||||
|
val emailAddress = jsonReader.nextString()
|
||||||
|
val displayName = jsonReader.nextString()
|
||||||
|
|
||||||
|
skipAdditionalArrayEntries(jsonReader)
|
||||||
|
jsonReader.endArray()
|
||||||
|
|
||||||
|
return QrCodeData.Identity(emailAddress, displayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun skipAdditionalArrayEntries(jsonReader: JsonReader) {
|
||||||
|
// For forward compatibility allow additional array elements.
|
||||||
|
while (jsonReader.hasNext()) {
|
||||||
|
jsonReader.readJsonValue()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun toJson(jsonWriter: JsonWriter, value: QrCodeData?) {
|
||||||
|
throw UnsupportedOperationException("not implemented")
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,94 @@
|
|||||||
|
package app.k9mail.feature.migration.qrcode
|
||||||
|
|
||||||
|
import app.k9mail.core.common.mail.toUserEmailAddress
|
||||||
|
import app.k9mail.core.common.net.toHostname
|
||||||
|
import app.k9mail.core.common.net.toPort
|
||||||
|
|
||||||
|
internal class QrCodePayloadMapper(
|
||||||
|
private val qrCodePayloadValidator: QrCodePayloadValidator = QrCodePayloadValidator(),
|
||||||
|
) {
|
||||||
|
fun toAccountData(data: QrCodeData): AccountData? {
|
||||||
|
return if (qrCodePayloadValidator.isValid(data)) {
|
||||||
|
mapToAccountData(data)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapToAccountData(data: QrCodeData): AccountData {
|
||||||
|
return AccountData(
|
||||||
|
sequenceNumber = data.misc.sequenceNumber,
|
||||||
|
sequenceEnd = data.misc.sequenceEnd,
|
||||||
|
accounts = data.accounts.map { account -> mapAccount(account) },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapAccount(account: QrCodeData.Account): AccountData.Account {
|
||||||
|
val incomingServer = mapIncomingServer(account.incomingServer)
|
||||||
|
val outgoingServerGroups = mapOutgoingServerGroups(account.outgoingServers)
|
||||||
|
val accountName = mapAccountName(
|
||||||
|
accountName = account.incomingServer.accountName,
|
||||||
|
identity = outgoingServerGroups.first().identities.first(),
|
||||||
|
)
|
||||||
|
|
||||||
|
return AccountData.Account(
|
||||||
|
accountName = accountName,
|
||||||
|
incomingServer = incomingServer,
|
||||||
|
outgoingServerGroups = outgoingServerGroups,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapAccountName(accountName: String?, identity: AccountData.Identity): String {
|
||||||
|
// When setting up an account in Thunderbird, the account name matches the email address. We can avoid this
|
||||||
|
// duplication in the encoded data by omitting the account name when it matches the email address.
|
||||||
|
// This method will return the email address of the first identity in case the account name is null or the empty
|
||||||
|
// string.
|
||||||
|
return accountName?.takeIf { it.isNotEmpty() } ?: identity.emailAddress.toString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapIncomingServer(incomingServer: QrCodeData.IncomingServer): AccountData.IncomingServer {
|
||||||
|
return AccountData.IncomingServer(
|
||||||
|
protocol = AccountData.IncomingServerProtocol.fromInt(incomingServer.protocol),
|
||||||
|
hostname = incomingServer.hostname.toHostname(),
|
||||||
|
port = incomingServer.port.toPort(),
|
||||||
|
connectionSecurity = AccountData.ConnectionSecurity.fromInt(incomingServer.connectionSecurity),
|
||||||
|
authenticationType = AccountData.AuthenticationType.fromInt(incomingServer.authenticationType),
|
||||||
|
username = incomingServer.username,
|
||||||
|
password = incomingServer.password,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapOutgoingServerGroups(
|
||||||
|
outgoingServers: List<QrCodeData.OutgoingServer>,
|
||||||
|
): List<AccountData.OutgoingServerGroup> {
|
||||||
|
return outgoingServers.map { outgoingServer ->
|
||||||
|
AccountData.OutgoingServerGroup(
|
||||||
|
outgoingServer = mapOutgoingServer(outgoingServer),
|
||||||
|
identities = mapIdentities(outgoingServer.identities),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapOutgoingServer(outgoingServer: QrCodeData.OutgoingServer): AccountData.OutgoingServer {
|
||||||
|
return AccountData.OutgoingServer(
|
||||||
|
protocol = AccountData.OutgoingServerProtocol.fromInt(outgoingServer.protocol),
|
||||||
|
hostname = outgoingServer.hostname.toHostname(),
|
||||||
|
port = outgoingServer.port.toPort(),
|
||||||
|
connectionSecurity = AccountData.ConnectionSecurity.fromInt(outgoingServer.connectionSecurity),
|
||||||
|
authenticationType = AccountData.AuthenticationType.fromInt(outgoingServer.authenticationType),
|
||||||
|
username = outgoingServer.username,
|
||||||
|
password = outgoingServer.password,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapIdentities(identities: List<QrCodeData.Identity>): List<AccountData.Identity> {
|
||||||
|
return identities.map { identity -> mapIdentity(identity) }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun mapIdentity(identity: QrCodeData.Identity): AccountData.Identity {
|
||||||
|
return AccountData.Identity(
|
||||||
|
emailAddress = identity.emailAddress.toUserEmailAddress(),
|
||||||
|
displayName = identity.displayName,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
package app.k9mail.feature.migration.qrcode
|
||||||
|
|
||||||
|
import com.squareup.moshi.JsonDataException
|
||||||
|
import java.io.IOException
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
internal class QrCodePayloadParser(
|
||||||
|
private val qrCodePayloadAdapter: QrCodePayloadAdapter = QrCodePayloadAdapter(),
|
||||||
|
) {
|
||||||
|
/**
|
||||||
|
* Parses the QR code payload as JSON and reads it into [QrCodeData].
|
||||||
|
*
|
||||||
|
* @return [QrCodeData] if the JSON was parsed successfully and has the correct structure, `null` otherwise.
|
||||||
|
*/
|
||||||
|
fun parse(payload: String): QrCodeData? {
|
||||||
|
return try {
|
||||||
|
qrCodePayloadAdapter.fromJson(payload)
|
||||||
|
} catch (e: JsonDataException) {
|
||||||
|
Timber.d(e, "Failed to parse JSON")
|
||||||
|
null
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Timber.d(e, "Unexpected IOException")
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
package app.k9mail.feature.migration.qrcode
|
||||||
|
|
||||||
|
internal class QrCodePayloadReader(
|
||||||
|
private val parser: QrCodePayloadParser = QrCodePayloadParser(),
|
||||||
|
private val mapper: QrCodePayloadMapper = QrCodePayloadMapper(),
|
||||||
|
) {
|
||||||
|
fun read(payload: String): AccountData? {
|
||||||
|
val parsedData = parser.parse(payload) ?: return null
|
||||||
|
|
||||||
|
return mapper.toAccountData(parsedData)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,138 @@
|
|||||||
|
package app.k9mail.feature.migration.qrcode
|
||||||
|
|
||||||
|
import app.k9mail.core.common.mail.EmailAddressParserException
|
||||||
|
import app.k9mail.core.common.mail.toUserEmailAddress
|
||||||
|
import app.k9mail.core.common.net.toHostname
|
||||||
|
import app.k9mail.core.common.net.toPort
|
||||||
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@Suppress("TooManyFunctions")
|
||||||
|
internal class QrCodePayloadValidator {
|
||||||
|
fun isValid(data: QrCodeData): Boolean {
|
||||||
|
if (data.version != 1) {
|
||||||
|
Timber.d("Unsupported version: %s", data.version)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return try {
|
||||||
|
validateData(data)
|
||||||
|
true
|
||||||
|
} catch (e: IllegalArgumentException) {
|
||||||
|
Timber.d(e, "QR code payload failed validation")
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateData(data: QrCodeData) {
|
||||||
|
require(data.accounts.isNotEmpty()) { "Account array must not be empty" }
|
||||||
|
|
||||||
|
for (account in data.accounts) {
|
||||||
|
validateAccount(account)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateAccount(account: QrCodeData.Account) {
|
||||||
|
validateIncomingServer(account.incomingServer)
|
||||||
|
validateOutgoingServers(account.outgoingServers)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateIncomingServer(incomingServer: QrCodeData.IncomingServer) {
|
||||||
|
validateIncomingServerProtocol(incomingServer.protocol)
|
||||||
|
validateHostname(incomingServer.hostname)
|
||||||
|
validatePort(incomingServer.port)
|
||||||
|
validateConnectionSecurity(incomingServer.connectionSecurity)
|
||||||
|
validateAuthenticationType(incomingServer.authenticationType)
|
||||||
|
validateUsername(incomingServer.username)
|
||||||
|
validateAccountName(incomingServer.accountName)
|
||||||
|
validatePassword(incomingServer.password)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateOutgoingServers(outgoingServers: List<QrCodeData.OutgoingServer>) {
|
||||||
|
require(outgoingServers.isNotEmpty()) { "List of outgoing servers must not be empty" }
|
||||||
|
|
||||||
|
for (outgoingServer in outgoingServers) {
|
||||||
|
validateOutgoingServer(outgoingServer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateOutgoingServer(outgoingServer: QrCodeData.OutgoingServer) {
|
||||||
|
validateOutgoingServerProtocol(outgoingServer.protocol)
|
||||||
|
validateHostname(outgoingServer.hostname)
|
||||||
|
validatePort(outgoingServer.port)
|
||||||
|
validateConnectionSecurity(outgoingServer.connectionSecurity)
|
||||||
|
validateAuthenticationType(outgoingServer.authenticationType)
|
||||||
|
validateUsername(outgoingServer.username)
|
||||||
|
validatePassword(outgoingServer.password)
|
||||||
|
|
||||||
|
validateIdentities(outgoingServer.identities)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateIdentities(identities: List<QrCodeData.Identity>) {
|
||||||
|
require(identities.isNotEmpty()) { "List of identities must not be empty" }
|
||||||
|
|
||||||
|
for (identity in identities) {
|
||||||
|
validateIdentity(identity)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateIdentity(identity: QrCodeData.Identity) {
|
||||||
|
validateEmailAddress(identity.emailAddress)
|
||||||
|
validateDisplayName(identity.displayName)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateAccountName(accountName: String?) {
|
||||||
|
require(accountName == null || isSingleLine(accountName)) { "Account name must not contain line break" }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateIncomingServerProtocol(protocol: Int) {
|
||||||
|
AccountData.IncomingServerProtocol.fromInt(protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateOutgoingServerProtocol(protocol: Int) {
|
||||||
|
AccountData.OutgoingServerProtocol.fromInt(protocol)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateHostname(hostname: String) {
|
||||||
|
hostname.toHostname()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validatePort(port: Int) {
|
||||||
|
port.toPort()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateConnectionSecurity(value: Int) {
|
||||||
|
AccountData.ConnectionSecurity.fromInt(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateAuthenticationType(value: Int) {
|
||||||
|
AccountData.AuthenticationType.fromInt(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateUsername(username: String) {
|
||||||
|
require(isSingleLine(username)) { "Username must not contain line break" }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validatePassword(password: String?) {
|
||||||
|
require(password == null || isSingleLine(password)) { "Password must not contain line break" }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateEmailAddress(emailAddress: String) {
|
||||||
|
try {
|
||||||
|
emailAddress.toUserEmailAddress()
|
||||||
|
} catch (e: EmailAddressParserException) {
|
||||||
|
throw IllegalArgumentException("Email address failed to parse", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun validateDisplayName(displayName: String) {
|
||||||
|
require(isSingleLine(displayName)) { "Display name must not contain a line break" }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isSingleLine(text: String): Boolean {
|
||||||
|
return !text.contains(LINE_BREAK)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val LINE_BREAK = "[\\r\\n]".toRegex()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,141 @@
|
|||||||
|
package app.k9mail.feature.migration.qrcode
|
||||||
|
|
||||||
|
import app.k9mail.core.common.mail.toUserEmailAddress
|
||||||
|
import app.k9mail.core.common.net.toHostname
|
||||||
|
import app.k9mail.core.common.net.toPort
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.first
|
||||||
|
import assertk.assertions.isEqualTo
|
||||||
|
import assertk.assertions.isNotNull
|
||||||
|
import assertk.assertions.prop
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class QrCodePayloadMapperTest {
|
||||||
|
private val mapper = QrCodePayloadMapper()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `valid input should be mapped to expected output`() {
|
||||||
|
val input = INPUT
|
||||||
|
|
||||||
|
val result = mapper.toAccountData(input)
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo(OUTPUT)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `use email address of first identity when account name is the empty string`() {
|
||||||
|
val input = INPUT.updateIncomingServer { server ->
|
||||||
|
server.copy(accountName = "")
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = mapper.toAccountData(input)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull()
|
||||||
|
.prop(AccountData::accounts).first()
|
||||||
|
.prop(AccountData.Account::accountName).isEqualTo("user@domain.example")
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `use email address of first identity when account name is missing`() {
|
||||||
|
val input = INPUT.updateIncomingServer { server ->
|
||||||
|
server.copy(accountName = null, password = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = mapper.toAccountData(input)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull()
|
||||||
|
.prop(AccountData::accounts).first()
|
||||||
|
.prop(AccountData.Account::accountName).isEqualTo("user@domain.example")
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val INPUT = QrCodeData(
|
||||||
|
version = 1,
|
||||||
|
misc = QrCodeData.Misc(
|
||||||
|
sequenceNumber = 1,
|
||||||
|
sequenceEnd = 1,
|
||||||
|
),
|
||||||
|
accounts = listOf(
|
||||||
|
QrCodeData.Account(
|
||||||
|
incomingServer = QrCodeData.IncomingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "imap.domain.example",
|
||||||
|
port = 993,
|
||||||
|
connectionSecurity = 3,
|
||||||
|
authenticationType = 1,
|
||||||
|
username = "user@domain.example",
|
||||||
|
accountName = "Account name",
|
||||||
|
password = "password",
|
||||||
|
),
|
||||||
|
outgoingServers = listOf(
|
||||||
|
QrCodeData.OutgoingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "smtp.domain.example",
|
||||||
|
port = 465,
|
||||||
|
connectionSecurity = 3,
|
||||||
|
authenticationType = 1,
|
||||||
|
username = "user@domain.example",
|
||||||
|
password = "password",
|
||||||
|
identities = listOf(
|
||||||
|
QrCodeData.Identity(
|
||||||
|
emailAddress = "user@domain.example",
|
||||||
|
displayName = "Firstname Lastname",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
private val OUTPUT = AccountData(
|
||||||
|
sequenceNumber = 1,
|
||||||
|
sequenceEnd = 1,
|
||||||
|
accounts = listOf(
|
||||||
|
AccountData.Account(
|
||||||
|
accountName = "Account name",
|
||||||
|
incomingServer = AccountData.IncomingServer(
|
||||||
|
protocol = AccountData.IncomingServerProtocol.Imap,
|
||||||
|
hostname = "imap.domain.example".toHostname(),
|
||||||
|
port = 993.toPort(),
|
||||||
|
connectionSecurity = AccountData.ConnectionSecurity.Tls,
|
||||||
|
authenticationType = AccountData.AuthenticationType.PasswordCleartext,
|
||||||
|
username = "user@domain.example",
|
||||||
|
password = "password",
|
||||||
|
),
|
||||||
|
outgoingServerGroups = listOf(
|
||||||
|
AccountData.OutgoingServerGroup(
|
||||||
|
outgoingServer = AccountData.OutgoingServer(
|
||||||
|
protocol = AccountData.OutgoingServerProtocol.Smtp,
|
||||||
|
hostname = "smtp.domain.example".toHostname(),
|
||||||
|
port = 465.toPort(),
|
||||||
|
connectionSecurity = AccountData.ConnectionSecurity.Tls,
|
||||||
|
authenticationType = AccountData.AuthenticationType.PasswordCleartext,
|
||||||
|
username = "user@domain.example",
|
||||||
|
password = "password",
|
||||||
|
),
|
||||||
|
identities = listOf(
|
||||||
|
AccountData.Identity(
|
||||||
|
emailAddress = "user@domain.example".toUserEmailAddress(),
|
||||||
|
displayName = "Firstname Lastname",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun QrCodeData.updateIncomingServer(
|
||||||
|
block: (QrCodeData.IncomingServer) -> QrCodeData.IncomingServer,
|
||||||
|
): QrCodeData {
|
||||||
|
return copy(
|
||||||
|
accounts = accounts.map { account ->
|
||||||
|
account.copy(
|
||||||
|
incomingServer = block(account.incomingServer),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,446 @@
|
|||||||
|
package app.k9mail.feature.migration.qrcode
|
||||||
|
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.isEqualTo
|
||||||
|
import assertk.assertions.isNotNull
|
||||||
|
import assertk.assertions.isNull
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
@Suppress("LongMethod")
|
||||||
|
class QrCodePayloadParserTest {
|
||||||
|
private val parser = QrCodePayloadParser()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `one account, one identity, no account name, no passwords`() {
|
||||||
|
val payload = """[1,[1,1],""" +
|
||||||
|
"""[0,"imap.domain.example",993,3,2,"username"],""" +
|
||||||
|
"""[[[0,"smtp.domain.example",587,2,1,"username"],""" +
|
||||||
|
"""["user@domain.example","Firstname Lastname"]]]]"""
|
||||||
|
|
||||||
|
val result = parser.parse(payload)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull().isEqualTo(
|
||||||
|
QrCodeData(
|
||||||
|
version = 1,
|
||||||
|
misc = QrCodeData.Misc(
|
||||||
|
sequenceNumber = 1,
|
||||||
|
sequenceEnd = 1,
|
||||||
|
),
|
||||||
|
accounts = listOf(
|
||||||
|
QrCodeData.Account(
|
||||||
|
incomingServer = QrCodeData.IncomingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "imap.domain.example",
|
||||||
|
port = 993,
|
||||||
|
connectionSecurity = 3,
|
||||||
|
authenticationType = 2,
|
||||||
|
username = "username",
|
||||||
|
accountName = null,
|
||||||
|
password = null,
|
||||||
|
),
|
||||||
|
outgoingServers = listOf(
|
||||||
|
QrCodeData.OutgoingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "smtp.domain.example",
|
||||||
|
port = 587,
|
||||||
|
connectionSecurity = 2,
|
||||||
|
authenticationType = 1,
|
||||||
|
username = "username",
|
||||||
|
password = null,
|
||||||
|
identities = listOf(
|
||||||
|
QrCodeData.Identity(
|
||||||
|
emailAddress = "user@domain.example",
|
||||||
|
displayName = "Firstname Lastname",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `one account, one identity, with account name, no passwords`() {
|
||||||
|
val payload = """[1,[1,1],""" +
|
||||||
|
"""[0,"imap.domain.example",993,3,2,"username","Personal"],""" +
|
||||||
|
"""[[[0,"smtp.domain.example",587,2,1,"username"],""" +
|
||||||
|
"""["user@domain.example","Firstname Lastname"]]]]"""
|
||||||
|
|
||||||
|
val result = parser.parse(payload)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull().isEqualTo(
|
||||||
|
QrCodeData(
|
||||||
|
version = 1,
|
||||||
|
misc = QrCodeData.Misc(
|
||||||
|
sequenceNumber = 1,
|
||||||
|
sequenceEnd = 1,
|
||||||
|
),
|
||||||
|
accounts = listOf(
|
||||||
|
QrCodeData.Account(
|
||||||
|
incomingServer = QrCodeData.IncomingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "imap.domain.example",
|
||||||
|
port = 993,
|
||||||
|
connectionSecurity = 3,
|
||||||
|
authenticationType = 2,
|
||||||
|
username = "username",
|
||||||
|
accountName = "Personal",
|
||||||
|
password = null,
|
||||||
|
),
|
||||||
|
outgoingServers = listOf(
|
||||||
|
QrCodeData.OutgoingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "smtp.domain.example",
|
||||||
|
port = 587,
|
||||||
|
connectionSecurity = 2,
|
||||||
|
authenticationType = 1,
|
||||||
|
username = "username",
|
||||||
|
password = null,
|
||||||
|
identities = listOf(
|
||||||
|
QrCodeData.Identity(
|
||||||
|
emailAddress = "user@domain.example",
|
||||||
|
displayName = "Firstname Lastname",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `one account, one identity, no account name, with passwords`() {
|
||||||
|
val payload = """[1,[1,1],""" +
|
||||||
|
"""[0,"imap.domain.example",993,3,2,"username","","imap-password"],""" +
|
||||||
|
"""[[[0,"smtp.domain.example",587,2,1,"username","smtp-password"],""" +
|
||||||
|
"""["user@domain.example","Firstname Lastname"]]]]"""
|
||||||
|
|
||||||
|
val result = parser.parse(payload)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull().isEqualTo(
|
||||||
|
QrCodeData(
|
||||||
|
version = 1,
|
||||||
|
misc = QrCodeData.Misc(
|
||||||
|
sequenceNumber = 1,
|
||||||
|
sequenceEnd = 1,
|
||||||
|
),
|
||||||
|
accounts = listOf(
|
||||||
|
QrCodeData.Account(
|
||||||
|
incomingServer = QrCodeData.IncomingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "imap.domain.example",
|
||||||
|
port = 993,
|
||||||
|
connectionSecurity = 3,
|
||||||
|
authenticationType = 2,
|
||||||
|
username = "username",
|
||||||
|
accountName = "",
|
||||||
|
password = "imap-password",
|
||||||
|
),
|
||||||
|
outgoingServers = listOf(
|
||||||
|
QrCodeData.OutgoingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "smtp.domain.example",
|
||||||
|
port = 587,
|
||||||
|
connectionSecurity = 2,
|
||||||
|
authenticationType = 1,
|
||||||
|
username = "username",
|
||||||
|
password = "smtp-password",
|
||||||
|
identities = listOf(
|
||||||
|
QrCodeData.Identity(
|
||||||
|
emailAddress = "user@domain.example",
|
||||||
|
displayName = "Firstname Lastname",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `one account, one identity, with account name, with passwords`() {
|
||||||
|
val payload = """[1,[1,1],""" +
|
||||||
|
"""[0,"imap.domain.example",993,3,2,"username","Personal","imap-password"],""" +
|
||||||
|
"""[[[0,"smtp.domain.example",587,2,1,"username","smtp-password"],""" +
|
||||||
|
"""["user@domain.example","Firstname Lastname"]]]]"""
|
||||||
|
|
||||||
|
val result = parser.parse(payload)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull().isEqualTo(
|
||||||
|
QrCodeData(
|
||||||
|
version = 1,
|
||||||
|
misc = QrCodeData.Misc(
|
||||||
|
sequenceNumber = 1,
|
||||||
|
sequenceEnd = 1,
|
||||||
|
),
|
||||||
|
accounts = listOf(
|
||||||
|
QrCodeData.Account(
|
||||||
|
incomingServer = QrCodeData.IncomingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "imap.domain.example",
|
||||||
|
port = 993,
|
||||||
|
connectionSecurity = 3,
|
||||||
|
authenticationType = 2,
|
||||||
|
username = "username",
|
||||||
|
accountName = "Personal",
|
||||||
|
password = "imap-password",
|
||||||
|
),
|
||||||
|
outgoingServers = listOf(
|
||||||
|
QrCodeData.OutgoingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "smtp.domain.example",
|
||||||
|
port = 587,
|
||||||
|
connectionSecurity = 2,
|
||||||
|
authenticationType = 1,
|
||||||
|
username = "username",
|
||||||
|
password = "smtp-password",
|
||||||
|
identities = listOf(
|
||||||
|
QrCodeData.Identity(
|
||||||
|
emailAddress = "user@domain.example",
|
||||||
|
displayName = "Firstname Lastname",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `one account, two identities`() {
|
||||||
|
val payload = """[1,[1,1],""" +
|
||||||
|
"""[0,"imap.domain.example",993,3,2,"username","","imap-password"],""" +
|
||||||
|
"""[[[0,"smtp.domain.example",587,2,1,"username","smtp-password"],""" +
|
||||||
|
"""["user@domain.example","Firstname Lastname"],""" +
|
||||||
|
"""["alias@domain.example","Nickname Lastname"]]]]"""
|
||||||
|
|
||||||
|
val result = parser.parse(payload)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull().isEqualTo(
|
||||||
|
QrCodeData(
|
||||||
|
version = 1,
|
||||||
|
misc = QrCodeData.Misc(
|
||||||
|
sequenceNumber = 1,
|
||||||
|
sequenceEnd = 1,
|
||||||
|
),
|
||||||
|
accounts = listOf(
|
||||||
|
QrCodeData.Account(
|
||||||
|
incomingServer = QrCodeData.IncomingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "imap.domain.example",
|
||||||
|
port = 993,
|
||||||
|
connectionSecurity = 3,
|
||||||
|
authenticationType = 2,
|
||||||
|
username = "username",
|
||||||
|
accountName = "",
|
||||||
|
password = "imap-password",
|
||||||
|
),
|
||||||
|
outgoingServers = listOf(
|
||||||
|
QrCodeData.OutgoingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "smtp.domain.example",
|
||||||
|
port = 587,
|
||||||
|
connectionSecurity = 2,
|
||||||
|
authenticationType = 1,
|
||||||
|
username = "username",
|
||||||
|
password = "smtp-password",
|
||||||
|
identities = listOf(
|
||||||
|
QrCodeData.Identity(
|
||||||
|
emailAddress = "user@domain.example",
|
||||||
|
displayName = "Firstname Lastname",
|
||||||
|
),
|
||||||
|
QrCodeData.Identity(
|
||||||
|
emailAddress = "alias@domain.example",
|
||||||
|
displayName = "Nickname Lastname",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `two accounts, two identities each`() {
|
||||||
|
val payload = """[1,[1,1],""" +
|
||||||
|
"""[0,"imap.domain.example",993,3,2,"username","","imap-password"],""" +
|
||||||
|
"""[[[0,"smtp.domain.example",587,2,1,"username","smtp-password"],""" +
|
||||||
|
"""["user@domain.example","Firstname Lastname"],""" +
|
||||||
|
"""["alias@domain.example","Nickname"]]],""" +
|
||||||
|
"""[0,"imap.company.example",143,2,1,"user@company.example","","company-password"],""" +
|
||||||
|
"""[[[0,"smtp.company.example",465,3,1,"user@company.example","company-password"],""" +
|
||||||
|
"""["user@company.example","Firstname Lastname"],""" +
|
||||||
|
"""["alias@company.example","Nickname Lastname"]]]]"""
|
||||||
|
|
||||||
|
val result = parser.parse(payload)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull().isEqualTo(
|
||||||
|
QrCodeData(
|
||||||
|
version = 1,
|
||||||
|
misc = QrCodeData.Misc(
|
||||||
|
sequenceNumber = 1,
|
||||||
|
sequenceEnd = 1,
|
||||||
|
),
|
||||||
|
accounts = listOf(
|
||||||
|
QrCodeData.Account(
|
||||||
|
incomingServer = QrCodeData.IncomingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "imap.domain.example",
|
||||||
|
port = 993,
|
||||||
|
connectionSecurity = 3,
|
||||||
|
authenticationType = 2,
|
||||||
|
username = "username",
|
||||||
|
accountName = "",
|
||||||
|
password = "imap-password",
|
||||||
|
),
|
||||||
|
outgoingServers = listOf(
|
||||||
|
QrCodeData.OutgoingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "smtp.domain.example",
|
||||||
|
port = 587,
|
||||||
|
connectionSecurity = 2,
|
||||||
|
authenticationType = 1,
|
||||||
|
username = "username",
|
||||||
|
password = "smtp-password",
|
||||||
|
identities = listOf(
|
||||||
|
QrCodeData.Identity(
|
||||||
|
emailAddress = "user@domain.example",
|
||||||
|
displayName = "Firstname Lastname",
|
||||||
|
),
|
||||||
|
QrCodeData.Identity(
|
||||||
|
emailAddress = "alias@domain.example",
|
||||||
|
displayName = "Nickname",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
QrCodeData.Account(
|
||||||
|
incomingServer = QrCodeData.IncomingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "imap.company.example",
|
||||||
|
port = 143,
|
||||||
|
connectionSecurity = 2,
|
||||||
|
authenticationType = 1,
|
||||||
|
username = "user@company.example",
|
||||||
|
accountName = "",
|
||||||
|
password = "company-password",
|
||||||
|
),
|
||||||
|
outgoingServers = listOf(
|
||||||
|
QrCodeData.OutgoingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "smtp.company.example",
|
||||||
|
port = 465,
|
||||||
|
connectionSecurity = 3,
|
||||||
|
authenticationType = 1,
|
||||||
|
username = "user@company.example",
|
||||||
|
password = "company-password",
|
||||||
|
identities = listOf(
|
||||||
|
QrCodeData.Identity(
|
||||||
|
emailAddress = "user@company.example",
|
||||||
|
displayName = "Firstname Lastname",
|
||||||
|
),
|
||||||
|
QrCodeData.Identity(
|
||||||
|
emailAddress = "alias@company.example",
|
||||||
|
displayName = "Nickname Lastname",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `additional array entries in incoming server array, outgoing server array, and identity array`() {
|
||||||
|
val payload = """[1,[1,1],""" +
|
||||||
|
"""[0,"imap.domain.example",993,3,2,"username","","password","extra"],""" +
|
||||||
|
"""[[[0,"smtp.domain.example",587,2,1,"username","password","extra"],""" +
|
||||||
|
"""["user@domain.example","Firstname Lastname","extra"]]]]"""
|
||||||
|
|
||||||
|
val result = parser.parse(payload)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull().isEqualTo(
|
||||||
|
QrCodeData(
|
||||||
|
version = 1,
|
||||||
|
misc = QrCodeData.Misc(
|
||||||
|
sequenceNumber = 1,
|
||||||
|
sequenceEnd = 1,
|
||||||
|
),
|
||||||
|
accounts = listOf(
|
||||||
|
QrCodeData.Account(
|
||||||
|
incomingServer = QrCodeData.IncomingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "imap.domain.example",
|
||||||
|
port = 993,
|
||||||
|
connectionSecurity = 3,
|
||||||
|
authenticationType = 2,
|
||||||
|
username = "username",
|
||||||
|
accountName = "",
|
||||||
|
password = "password",
|
||||||
|
),
|
||||||
|
outgoingServers = listOf(
|
||||||
|
QrCodeData.OutgoingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "smtp.domain.example",
|
||||||
|
port = 587,
|
||||||
|
connectionSecurity = 2,
|
||||||
|
authenticationType = 1,
|
||||||
|
username = "username",
|
||||||
|
password = "password",
|
||||||
|
identities = listOf(
|
||||||
|
QrCodeData.Identity(
|
||||||
|
emailAddress = "user@domain.example",
|
||||||
|
displayName = "Firstname Lastname",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `additional array entries in meta array`() {
|
||||||
|
val payload = """[1,[1,1,"extra"],""" +
|
||||||
|
"""[0,"imap.domain.example",993,3,2,"username","","password","extra"],""" +
|
||||||
|
"""[[[0,"smtp.domain.example",587,2,1,"username","password","extra"],""" +
|
||||||
|
"""["user@domain.example","Firstname Lastname","extra"]]]]"""
|
||||||
|
|
||||||
|
val result = parser.parse(payload)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `URL instead of valid payload`() {
|
||||||
|
val payload = "https://domain.example/path"
|
||||||
|
|
||||||
|
val result = parser.parse(payload)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `incomplete payload`() {
|
||||||
|
val payload = "[1,["
|
||||||
|
|
||||||
|
val result = parser.parse(payload)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,168 @@
|
|||||||
|
package app.k9mail.feature.migration.qrcode
|
||||||
|
|
||||||
|
import app.k9mail.core.common.mail.toUserEmailAddress
|
||||||
|
import app.k9mail.core.common.net.toHostname
|
||||||
|
import app.k9mail.core.common.net.toPort
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.isEqualTo
|
||||||
|
import assertk.assertions.isNotNull
|
||||||
|
import assertk.assertions.isNull
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
@Suppress("LongMethod")
|
||||||
|
class QrCodePayloadReaderTest {
|
||||||
|
private val reader = QrCodePayloadReader()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `one account, one identity, no passwords`() {
|
||||||
|
val payload = """[1,[1,1],""" +
|
||||||
|
"""[0,"imap.domain.example",993,3,2,"username","My Account"],""" +
|
||||||
|
"""[[[0,"smtp.domain.example",587,2,1,"username"],""" +
|
||||||
|
"""["user@domain.example","Firstname Lastname"]]]]"""
|
||||||
|
|
||||||
|
val result = reader.read(payload)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull().isEqualTo(
|
||||||
|
AccountData(
|
||||||
|
sequenceNumber = 1,
|
||||||
|
sequenceEnd = 1,
|
||||||
|
accounts = listOf(
|
||||||
|
AccountData.Account(
|
||||||
|
accountName = "My Account",
|
||||||
|
incomingServer = AccountData.IncomingServer(
|
||||||
|
protocol = AccountData.IncomingServerProtocol.Imap,
|
||||||
|
hostname = "imap.domain.example".toHostname(),
|
||||||
|
port = 993.toPort(),
|
||||||
|
connectionSecurity = AccountData.ConnectionSecurity.Tls,
|
||||||
|
authenticationType = AccountData.AuthenticationType.PasswordEncrypted,
|
||||||
|
username = "username",
|
||||||
|
password = null,
|
||||||
|
),
|
||||||
|
outgoingServerGroups = listOf(
|
||||||
|
AccountData.OutgoingServerGroup(
|
||||||
|
outgoingServer = AccountData.OutgoingServer(
|
||||||
|
protocol = AccountData.OutgoingServerProtocol.Smtp,
|
||||||
|
hostname = "smtp.domain.example".toHostname(),
|
||||||
|
port = 587.toPort(),
|
||||||
|
connectionSecurity = AccountData.ConnectionSecurity.AlwaysStartTls,
|
||||||
|
authenticationType = AccountData.AuthenticationType.PasswordCleartext,
|
||||||
|
username = "username",
|
||||||
|
password = null,
|
||||||
|
),
|
||||||
|
identities = listOf(
|
||||||
|
AccountData.Identity(
|
||||||
|
emailAddress = "user@domain.example".toUserEmailAddress(),
|
||||||
|
displayName = "Firstname Lastname",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `two accounts, two identities each`() {
|
||||||
|
val payload = """[1,[2,3],""" +
|
||||||
|
"""[0,"imap.domain.example",993,3,2,"username","","imap-password"],""" +
|
||||||
|
"""[[[0,"smtp.domain.example",587,2,1,"username","smtp-password"],""" +
|
||||||
|
"""["user@domain.example","Firstname Lastname"],""" +
|
||||||
|
"""["alias@domain.example","Nickname"]]],""" +
|
||||||
|
"""[0,"imap.company.example",143,2,1,"user@company.example","","company-password"],""" +
|
||||||
|
"""[[[0,"smtp.company.example",465,3,1,"user@company.example","company-password"],""" +
|
||||||
|
"""["user@company.example","Firstname Lastname"],""" +
|
||||||
|
"""["alias@company.example","Nickname Lastname"]]]]"""
|
||||||
|
|
||||||
|
val result = reader.read(payload)
|
||||||
|
|
||||||
|
assertThat(result).isNotNull().isEqualTo(
|
||||||
|
AccountData(
|
||||||
|
sequenceNumber = 2,
|
||||||
|
sequenceEnd = 3,
|
||||||
|
accounts = listOf(
|
||||||
|
AccountData.Account(
|
||||||
|
accountName = "user@domain.example",
|
||||||
|
incomingServer = AccountData.IncomingServer(
|
||||||
|
protocol = AccountData.IncomingServerProtocol.Imap,
|
||||||
|
hostname = "imap.domain.example".toHostname(),
|
||||||
|
port = 993.toPort(),
|
||||||
|
connectionSecurity = AccountData.ConnectionSecurity.Tls,
|
||||||
|
authenticationType = AccountData.AuthenticationType.PasswordEncrypted,
|
||||||
|
username = "username",
|
||||||
|
password = "imap-password",
|
||||||
|
),
|
||||||
|
outgoingServerGroups = listOf(
|
||||||
|
AccountData.OutgoingServerGroup(
|
||||||
|
outgoingServer = AccountData.OutgoingServer(
|
||||||
|
protocol = AccountData.OutgoingServerProtocol.Smtp,
|
||||||
|
hostname = "smtp.domain.example".toHostname(),
|
||||||
|
port = 587.toPort(),
|
||||||
|
connectionSecurity = AccountData.ConnectionSecurity.AlwaysStartTls,
|
||||||
|
authenticationType = AccountData.AuthenticationType.PasswordCleartext,
|
||||||
|
username = "username",
|
||||||
|
password = "smtp-password",
|
||||||
|
),
|
||||||
|
identities = listOf(
|
||||||
|
AccountData.Identity(
|
||||||
|
emailAddress = "user@domain.example".toUserEmailAddress(),
|
||||||
|
displayName = "Firstname Lastname",
|
||||||
|
),
|
||||||
|
AccountData.Identity(
|
||||||
|
emailAddress = "alias@domain.example".toUserEmailAddress(),
|
||||||
|
displayName = "Nickname",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
AccountData.Account(
|
||||||
|
accountName = "user@company.example",
|
||||||
|
incomingServer = AccountData.IncomingServer(
|
||||||
|
protocol = AccountData.IncomingServerProtocol.Imap,
|
||||||
|
hostname = "imap.company.example".toHostname(),
|
||||||
|
port = 143.toPort(),
|
||||||
|
connectionSecurity = AccountData.ConnectionSecurity.AlwaysStartTls,
|
||||||
|
authenticationType = AccountData.AuthenticationType.PasswordCleartext,
|
||||||
|
username = "user@company.example",
|
||||||
|
password = "company-password",
|
||||||
|
),
|
||||||
|
outgoingServerGroups = listOf(
|
||||||
|
AccountData.OutgoingServerGroup(
|
||||||
|
outgoingServer = AccountData.OutgoingServer(
|
||||||
|
protocol = AccountData.OutgoingServerProtocol.Smtp,
|
||||||
|
hostname = "smtp.company.example".toHostname(),
|
||||||
|
port = 465.toPort(),
|
||||||
|
connectionSecurity = AccountData.ConnectionSecurity.Tls,
|
||||||
|
authenticationType = AccountData.AuthenticationType.PasswordCleartext,
|
||||||
|
username = "user@company.example",
|
||||||
|
password = "company-password",
|
||||||
|
),
|
||||||
|
identities = listOf(
|
||||||
|
AccountData.Identity(
|
||||||
|
emailAddress = "user@company.example".toUserEmailAddress(),
|
||||||
|
displayName = "Firstname Lastname",
|
||||||
|
),
|
||||||
|
AccountData.Identity(
|
||||||
|
emailAddress = "alias@company.example".toUserEmailAddress(),
|
||||||
|
displayName = "Nickname Lastname",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid payload`() {
|
||||||
|
val payload = "https://domain.example/path"
|
||||||
|
|
||||||
|
val result = reader.read(payload)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,355 @@
|
|||||||
|
package app.k9mail.feature.migration.qrcode
|
||||||
|
|
||||||
|
import assertk.assertThat
|
||||||
|
import assertk.assertions.isFalse
|
||||||
|
import assertk.assertions.isTrue
|
||||||
|
import kotlin.test.Test
|
||||||
|
|
||||||
|
class QrCodePayloadValidatorTest {
|
||||||
|
private val validator = QrCodePayloadValidator()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `valid input`() {
|
||||||
|
val input = INPUT
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid account name`() {
|
||||||
|
val input = INPUT.updateIncomingServer { server ->
|
||||||
|
server.copy(accountName = "contains\nline break")
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `incoming server with missing password`() {
|
||||||
|
val input = INPUT.updateIncomingServer { server ->
|
||||||
|
server.copy(password = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `outgoing server with missing password`() {
|
||||||
|
val input = INPUT.updateOutgoingServer { server ->
|
||||||
|
server.copy(password = null)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isTrue()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `unsupported version number`() {
|
||||||
|
val input = INPUT.copy(version = 2)
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `empty list of accounts`() {
|
||||||
|
val input = INPUT.copy(accounts = emptyList())
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `empty list of outgoing servers`() {
|
||||||
|
val input = INPUT.copy(
|
||||||
|
accounts = INPUT.accounts.map { it.copy(outgoingServers = emptyList()) },
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `empty list of identities`() {
|
||||||
|
val input = INPUT.copy(
|
||||||
|
accounts = INPUT.accounts.map { account ->
|
||||||
|
account.copy(
|
||||||
|
outgoingServers = account.outgoingServers.map { outgoingServer ->
|
||||||
|
outgoingServer.copy(identities = emptyList())
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid incoming server protocol`() {
|
||||||
|
val input = INPUT.updateIncomingServer { server ->
|
||||||
|
server.copy(protocol = 2)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid incoming server hostname`() {
|
||||||
|
val input = INPUT.updateIncomingServer { server ->
|
||||||
|
server.copy(hostname = "invalid hostname")
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid incoming server port`() {
|
||||||
|
val input = INPUT.updateIncomingServer { server ->
|
||||||
|
server.copy(port = 100_000)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid incoming server connection security`() {
|
||||||
|
val input = INPUT.updateIncomingServer { server ->
|
||||||
|
server.copy(connectionSecurity = 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid incoming server authentication type`() {
|
||||||
|
val input = INPUT.updateIncomingServer { server ->
|
||||||
|
server.copy(authenticationType = 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid incoming server username`() {
|
||||||
|
val input = INPUT.updateIncomingServer { server ->
|
||||||
|
server.copy(username = "contains\nline break")
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid incoming server password`() {
|
||||||
|
val input = INPUT.updateIncomingServer { server ->
|
||||||
|
server.copy(password = "contains\nline break")
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid outgoing server protocol`() {
|
||||||
|
val input = INPUT.updateOutgoingServer { server ->
|
||||||
|
server.copy(protocol = 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid outgoing server hostname`() {
|
||||||
|
val input = INPUT.updateOutgoingServer { server ->
|
||||||
|
server.copy(hostname = "invalid hostname")
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid outgoing server port`() {
|
||||||
|
val input = INPUT.updateOutgoingServer { server ->
|
||||||
|
server.copy(port = 100_000)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid outgoing server connection security`() {
|
||||||
|
val input = INPUT.updateOutgoingServer { server ->
|
||||||
|
server.copy(connectionSecurity = 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid outgoing server authentication type`() {
|
||||||
|
val input = INPUT.updateOutgoingServer { server ->
|
||||||
|
server.copy(authenticationType = 100)
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid outgoing server username`() {
|
||||||
|
val input = INPUT.updateOutgoingServer { server ->
|
||||||
|
server.copy(username = "contains\nline break")
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid outgoing server password`() {
|
||||||
|
val input = INPUT.updateOutgoingServer { server ->
|
||||||
|
server.copy(password = "contains\nline break")
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid identity email address`() {
|
||||||
|
val input = INPUT.updateIdentity { identity ->
|
||||||
|
identity.copy(emailAddress = "invalid")
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid identity display name`() {
|
||||||
|
val input = INPUT.updateIdentity { identity ->
|
||||||
|
identity.copy(displayName = "contains\nline break")
|
||||||
|
}
|
||||||
|
|
||||||
|
val result = validator.isValid(input)
|
||||||
|
|
||||||
|
assertThat(result).isFalse()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val INPUT = QrCodeData(
|
||||||
|
version = 1,
|
||||||
|
misc = QrCodeData.Misc(
|
||||||
|
sequenceNumber = 1,
|
||||||
|
sequenceEnd = 1,
|
||||||
|
),
|
||||||
|
accounts = listOf(
|
||||||
|
QrCodeData.Account(
|
||||||
|
incomingServer = QrCodeData.IncomingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "imap.domain.example",
|
||||||
|
port = 993,
|
||||||
|
connectionSecurity = 3,
|
||||||
|
authenticationType = 1,
|
||||||
|
username = "user@domain.example",
|
||||||
|
accountName = "Account name",
|
||||||
|
password = "password",
|
||||||
|
),
|
||||||
|
outgoingServers = listOf(
|
||||||
|
QrCodeData.OutgoingServer(
|
||||||
|
protocol = 0,
|
||||||
|
hostname = "smtp.domain.example",
|
||||||
|
port = 465,
|
||||||
|
connectionSecurity = 3,
|
||||||
|
authenticationType = 1,
|
||||||
|
username = "user@domain.example",
|
||||||
|
password = "password",
|
||||||
|
identities = listOf(
|
||||||
|
QrCodeData.Identity(
|
||||||
|
emailAddress = "user@domain.example",
|
||||||
|
displayName = "Firstname Lastname",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun QrCodeData.updateIncomingServer(
|
||||||
|
block: (QrCodeData.IncomingServer) -> QrCodeData.IncomingServer,
|
||||||
|
): QrCodeData {
|
||||||
|
return copy(
|
||||||
|
accounts = accounts.map { account ->
|
||||||
|
account.copy(
|
||||||
|
incomingServer = block(account.incomingServer),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun QrCodeData.updateOutgoingServer(
|
||||||
|
block: (QrCodeData.OutgoingServer) -> QrCodeData.OutgoingServer,
|
||||||
|
): QrCodeData {
|
||||||
|
return copy(
|
||||||
|
accounts = accounts.map { account ->
|
||||||
|
account.copy(
|
||||||
|
outgoingServers = account.outgoingServers.map(block),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun QrCodeData.updateIdentity(
|
||||||
|
block: (QrCodeData.Identity) -> QrCodeData.Identity,
|
||||||
|
): QrCodeData {
|
||||||
|
return copy(
|
||||||
|
accounts = accounts.map { account ->
|
||||||
|
account.copy(
|
||||||
|
outgoingServers = account.outgoingServers.map { server ->
|
||||||
|
server.copy(
|
||||||
|
identities = server.identities.map(block),
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -74,6 +74,7 @@ include(
|
|||||||
|
|
||||||
include(
|
include(
|
||||||
":feature:migration:provider",
|
":feature:migration:provider",
|
||||||
|
":feature:migration:qrcode",
|
||||||
)
|
)
|
||||||
|
|
||||||
include(
|
include(
|
||||||
|
Loading…
Reference in New Issue
Block a user