Use JSON as serialization format for 'ServerSettings'
This commit is contained in:
parent
de2560b90b
commit
696901f9c1
4 changed files with 205 additions and 32 deletions
|
@ -31,8 +31,12 @@ class AccountPreferenceSerializer(
|
|||
fun loadAccount(account: Account, storage: Storage) {
|
||||
val accountUuid = account.uuid
|
||||
with(account) {
|
||||
incomingServerSettings = serverSettingsSerializer.deserializeIncoming(storage.getString("$accountUuid.storeUri", ""))
|
||||
outgoingServerSettings = serverSettingsSerializer.deserializeOutgoing(storage.getString("$accountUuid.transportUri", ""))
|
||||
incomingServerSettings = serverSettingsSerializer.deserialize(
|
||||
storage.getString("$accountUuid.$INCOMING_SERVER_SETTINGS_KEY", "")
|
||||
)
|
||||
outgoingServerSettings = serverSettingsSerializer.deserialize(
|
||||
storage.getString("$accountUuid.$OUTGOING_SERVER_SETTINGS_KEY", "")
|
||||
)
|
||||
localStorageProviderId = storage.getString("$accountUuid.localStorageProvider", storageManager.defaultProviderId)
|
||||
description = storage.getString("$accountUuid.description", null)
|
||||
alwaysBcc = storage.getString("$accountUuid.alwaysBcc", alwaysBcc)
|
||||
|
@ -244,8 +248,8 @@ class AccountPreferenceSerializer(
|
|||
}
|
||||
|
||||
with(account) {
|
||||
editor.putString("$accountUuid.storeUri", serverSettingsSerializer.serializeIncoming(incomingServerSettings))
|
||||
editor.putString("$accountUuid.transportUri", serverSettingsSerializer.serializeOutgoing(outgoingServerSettings))
|
||||
editor.putString("$accountUuid.$INCOMING_SERVER_SETTINGS_KEY", serverSettingsSerializer.serialize(incomingServerSettings))
|
||||
editor.putString("$accountUuid.$OUTGOING_SERVER_SETTINGS_KEY", serverSettingsSerializer.serialize(outgoingServerSettings))
|
||||
editor.putString("$accountUuid.localStorageProvider", localStorageProviderId)
|
||||
editor.putString("$accountUuid.description", description)
|
||||
editor.putString("$accountUuid.alwaysBcc", alwaysBcc)
|
||||
|
@ -372,8 +376,8 @@ class AccountPreferenceSerializer(
|
|||
editor.putString("accountUuids", accountUuids)
|
||||
}
|
||||
|
||||
editor.remove("$accountUuid.storeUri")
|
||||
editor.remove("$accountUuid.transportUri")
|
||||
editor.remove("$accountUuid.$INCOMING_SERVER_SETTINGS_KEY")
|
||||
editor.remove("$accountUuid.$OUTGOING_SERVER_SETTINGS_KEY")
|
||||
editor.remove("$accountUuid.description")
|
||||
editor.remove("$accountUuid.name")
|
||||
editor.remove("$accountUuid.email")
|
||||
|
@ -640,8 +644,8 @@ class AccountPreferenceSerializer(
|
|||
|
||||
companion object {
|
||||
const val ACCOUNT_DESCRIPTION_KEY = "description"
|
||||
const val STORE_URI_KEY = "storeUri"
|
||||
const val TRANSPORT_URI_KEY = "transportUri"
|
||||
const val INCOMING_SERVER_SETTINGS_KEY = "incomingServerSettings"
|
||||
const val OUTGOING_SERVER_SETTINGS_KEY = "outgoingServerSettings"
|
||||
|
||||
const val IDENTITY_NAME_KEY = "name"
|
||||
const val IDENTITY_EMAIL_KEY = "email"
|
||||
|
|
|
@ -1,27 +1,122 @@
|
|||
package com.fsck.k9
|
||||
|
||||
import com.fsck.k9.backend.BackendManager
|
||||
import com.fsck.k9.mail.AuthType
|
||||
import com.fsck.k9.mail.ConnectionSecurity
|
||||
import com.fsck.k9.mail.ServerSettings
|
||||
import com.fsck.k9.mail.filter.Base64
|
||||
import org.koin.core.KoinComponent
|
||||
import org.koin.core.inject
|
||||
import com.squareup.moshi.JsonAdapter
|
||||
import com.squareup.moshi.JsonReader
|
||||
import com.squareup.moshi.JsonReader.Token
|
||||
import com.squareup.moshi.JsonWriter
|
||||
|
||||
class ServerSettingsSerializer : KoinComponent {
|
||||
private val backendManager: BackendManager by inject()
|
||||
class ServerSettingsSerializer {
|
||||
private val adapter = ServerSettingsAdapter()
|
||||
|
||||
fun serializeIncoming(serverSettings: ServerSettings): String {
|
||||
return Base64.encode(backendManager.createStoreUri(serverSettings))
|
||||
fun serialize(serverSettings: ServerSettings): String {
|
||||
return adapter.toJson(serverSettings)
|
||||
}
|
||||
|
||||
fun serializeOutgoing(serverSettings: ServerSettings): String {
|
||||
return Base64.encode(backendManager.createTransportUri(serverSettings))
|
||||
}
|
||||
|
||||
fun deserializeIncoming(uri: String): ServerSettings {
|
||||
return backendManager.decodeStoreUri(Base64.decode(uri))
|
||||
}
|
||||
|
||||
fun deserializeOutgoing(uri: String): ServerSettings {
|
||||
return backendManager.decodeTransportUri(Base64.decode(uri))
|
||||
fun deserialize(json: String): ServerSettings {
|
||||
return adapter.fromJson(json)!!
|
||||
}
|
||||
}
|
||||
|
||||
private const val KEY_TYPE = "type"
|
||||
private const val KEY_HOST = "host"
|
||||
private const val KEY_PORT = "port"
|
||||
private const val KEY_CONNECTION_SECURITY = "connectionSecurity"
|
||||
private const val KEY_AUTHENTICATION_TYPE = "authenticationType"
|
||||
private const val KEY_USERNAME = "username"
|
||||
private const val KEY_PASSWORD = "password"
|
||||
private const val KEY_CLIENT_CERTIFICATE_ALIAS = "clientCertificateAlias"
|
||||
|
||||
private val JSON_KEYS = JsonReader.Options.of(
|
||||
KEY_TYPE,
|
||||
KEY_HOST,
|
||||
KEY_PORT,
|
||||
KEY_CONNECTION_SECURITY,
|
||||
KEY_AUTHENTICATION_TYPE,
|
||||
KEY_USERNAME,
|
||||
KEY_PASSWORD,
|
||||
KEY_CLIENT_CERTIFICATE_ALIAS
|
||||
)
|
||||
|
||||
private class ServerSettingsAdapter : JsonAdapter<ServerSettings>() {
|
||||
override fun fromJson(reader: JsonReader): ServerSettings {
|
||||
reader.beginObject()
|
||||
|
||||
var type: String? = null
|
||||
var host: String? = null
|
||||
var port: Int? = null
|
||||
var connectionSecurity: ConnectionSecurity? = null
|
||||
var authenticationType: AuthType? = null
|
||||
var username: String? = null
|
||||
var password: String? = null
|
||||
var clientCertificateAlias: String? = null
|
||||
val extra = mutableMapOf<String, String?>()
|
||||
|
||||
while (reader.hasNext()) {
|
||||
when (reader.selectName(JSON_KEYS)) {
|
||||
0 -> type = reader.nextString()
|
||||
1 -> host = reader.nextString()
|
||||
2 -> port = reader.nextInt()
|
||||
3 -> connectionSecurity = ConnectionSecurity.valueOf(reader.nextString())
|
||||
4 -> authenticationType = AuthType.valueOf(reader.nextString())
|
||||
5 -> username = reader.nextString()
|
||||
6 -> password = reader.nextStringOrNull()
|
||||
7 -> clientCertificateAlias = reader.nextStringOrNull()
|
||||
else -> {
|
||||
val key = reader.nextName()
|
||||
val value = reader.nextStringOrNull()
|
||||
extra[key] = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
reader.endObject()
|
||||
|
||||
requireNotNull(type) { "'type' must not be missing" }
|
||||
requireNotNull(host) { "'host' must not be missing" }
|
||||
requireNotNull(port) { "'port' must not be missing" }
|
||||
requireNotNull(connectionSecurity) { "'connectionSecurity' must not be missing" }
|
||||
requireNotNull(authenticationType) { "'authenticationType' must not be missing" }
|
||||
requireNotNull(username) { "'username' must not be missing" }
|
||||
|
||||
return ServerSettings(
|
||||
type,
|
||||
host,
|
||||
port,
|
||||
connectionSecurity,
|
||||
authenticationType,
|
||||
username,
|
||||
password,
|
||||
clientCertificateAlias,
|
||||
extra
|
||||
)
|
||||
}
|
||||
|
||||
override fun toJson(writer: JsonWriter, serverSettings: ServerSettings?) {
|
||||
requireNotNull(serverSettings)
|
||||
|
||||
writer.beginObject()
|
||||
writer.serializeNulls = true
|
||||
|
||||
writer.name(KEY_TYPE).value(serverSettings.type)
|
||||
writer.name(KEY_HOST).value(serverSettings.host)
|
||||
writer.name(KEY_PORT).value(serverSettings.port)
|
||||
writer.name(KEY_CONNECTION_SECURITY).value(serverSettings.connectionSecurity.name)
|
||||
writer.name(KEY_AUTHENTICATION_TYPE).value(serverSettings.authenticationType.name)
|
||||
writer.name(KEY_USERNAME).value(serverSettings.username)
|
||||
writer.name(KEY_PASSWORD).value(serverSettings.password)
|
||||
writer.name(KEY_CLIENT_CERTIFICATE_ALIAS).value(serverSettings.clientCertificateAlias)
|
||||
|
||||
for ((key, value) in serverSettings.extra) {
|
||||
writer.name(key).value(value)
|
||||
}
|
||||
|
||||
writer.endObject()
|
||||
}
|
||||
|
||||
private fun JsonReader.nextStringOrNull(): String? {
|
||||
return if (peek() == Token.NULL) nextNull() else nextString()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -371,8 +371,8 @@ public class SettingsImporter {
|
|||
// Write incoming server settings
|
||||
ServerSettings incoming = createServerSettings(account.incoming);
|
||||
ServerSettingsSerializer serverSettingsSerializer = DI.get(ServerSettingsSerializer.class);
|
||||
String storeUri = serverSettingsSerializer.serializeIncoming(incoming);
|
||||
putString(editor, accountKeyPrefix + AccountPreferenceSerializer.STORE_URI_KEY, storeUri);
|
||||
String incomingServer = serverSettingsSerializer.serialize(incoming);
|
||||
putString(editor, accountKeyPrefix + AccountPreferenceSerializer.INCOMING_SERVER_SETTINGS_KEY, incomingServer);
|
||||
|
||||
String incomingServerName = incoming.host;
|
||||
boolean incomingPasswordNeeded = AuthType.EXTERNAL != incoming.authenticationType &&
|
||||
|
@ -389,8 +389,8 @@ public class SettingsImporter {
|
|||
if (account.outgoing != null) {
|
||||
// Write outgoing server settings
|
||||
ServerSettings outgoing = createServerSettings(account.outgoing);
|
||||
String transportUri = serverSettingsSerializer.serializeOutgoing(outgoing);
|
||||
putString(editor, accountKeyPrefix + AccountPreferenceSerializer.TRANSPORT_URI_KEY, transportUri);
|
||||
String outgoingServer = serverSettingsSerializer.serialize(outgoing);
|
||||
putString(editor, accountKeyPrefix + AccountPreferenceSerializer.OUTGOING_SERVER_SETTINGS_KEY, outgoingServer);
|
||||
|
||||
/*
|
||||
* Mark account as disabled if the settings file contained a username but no password. However, no password
|
||||
|
@ -643,8 +643,8 @@ public class SettingsImporter {
|
|||
if (K9.isDebugLoggingEnabled()) {
|
||||
String outputValue = value;
|
||||
if (!K9.isSensitiveDebugLoggingEnabled() &&
|
||||
(key.endsWith("." + AccountPreferenceSerializer.TRANSPORT_URI_KEY) ||
|
||||
key.endsWith("." + AccountPreferenceSerializer.STORE_URI_KEY))) {
|
||||
(key.endsWith("." + AccountPreferenceSerializer.OUTGOING_SERVER_SETTINGS_KEY) ||
|
||||
key.endsWith("." + AccountPreferenceSerializer.INCOMING_SERVER_SETTINGS_KEY))) {
|
||||
outputValue = "*sensitive*";
|
||||
}
|
||||
Timber.v("Setting %s=%s", key, outputValue);
|
||||
|
|
|
@ -0,0 +1,74 @@
|
|||
package com.fsck.k9
|
||||
|
||||
import com.fsck.k9.mail.AuthType
|
||||
import com.fsck.k9.mail.ConnectionSecurity
|
||||
import com.fsck.k9.mail.ServerSettings
|
||||
import com.fsck.k9.mail.store.imap.ImapStoreSettings
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import org.junit.Assert.fail
|
||||
import org.junit.Test
|
||||
|
||||
class ServerSettingsSerializerTest {
|
||||
private val serverSettingsSerializer = ServerSettingsSerializer()
|
||||
|
||||
@Test
|
||||
fun `serialize and deserialize IMAP server settings`() {
|
||||
val serverSettings = ServerSettings(
|
||||
type = "imap",
|
||||
host = "imap.domain.example",
|
||||
port = 143,
|
||||
connectionSecurity = ConnectionSecurity.STARTTLS_REQUIRED,
|
||||
authenticationType = AuthType.PLAIN,
|
||||
username = "user",
|
||||
password = null,
|
||||
clientCertificateAlias = "alias",
|
||||
extra = ImapStoreSettings.createExtra(autoDetectNamespace = true, pathPrefix = null)
|
||||
)
|
||||
|
||||
val json = serverSettingsSerializer.serialize(serverSettings)
|
||||
val deserializedServerSettings = serverSettingsSerializer.deserialize(json)
|
||||
|
||||
assertThat(deserializedServerSettings).isEqualTo(serverSettings)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `serialize and deserialize POP3 server settings`() {
|
||||
val serverSettings = ServerSettings(
|
||||
type = "pop3",
|
||||
host = "pop3.domain.example",
|
||||
port = 995,
|
||||
connectionSecurity = ConnectionSecurity.SSL_TLS_REQUIRED,
|
||||
authenticationType = AuthType.PLAIN,
|
||||
username = "user",
|
||||
password = "password",
|
||||
clientCertificateAlias = null
|
||||
)
|
||||
|
||||
val json = serverSettingsSerializer.serialize(serverSettings)
|
||||
val deserializedServerSettings = serverSettingsSerializer.deserialize(json)
|
||||
|
||||
assertThat(deserializedServerSettings).isEqualTo(serverSettings)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `deserialize JSON with missing type`() {
|
||||
val json = """
|
||||
{
|
||||
"host": "imap.domain.example",
|
||||
"port": 993,
|
||||
"connectionSecurity": "SSL_TLS_REQUIRED",
|
||||
"authenticationType": "PLAIN",
|
||||
"username": "user",
|
||||
"password": "pass",
|
||||
"clientCertificateAlias": null
|
||||
}
|
||||
""".trimIndent()
|
||||
|
||||
try {
|
||||
serverSettingsSerializer.deserialize(json)
|
||||
fail("Expected exception")
|
||||
} catch (e: IllegalArgumentException) {
|
||||
assertThat(e).hasMessageThat().isEqualTo("'type' must not be missing")
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue