Allow self-signed certificate (#1564)
Accepted fingerprint before the migration to RiotX should still work after the migration. The dialog to trust the certificate is displayed during the login flow. For the moment, it is not displayed if the certificate change on the server once the user is logged in. This use case will be handled later.
This commit is contained in:
parent
6cb82421e4
commit
4bb804fbf7
|
@ -11,6 +11,7 @@ Improvements 🙌:
|
|||
- Prioritising Recovery key over Recovery passphrase (#1463)
|
||||
- Room Settings: Name, Topic, Photo, Aliases, History Visibility (#1455)
|
||||
- Update user avatar (#1054)
|
||||
- Allow self-signed certificate (#1564)
|
||||
|
||||
Bugfix 🐛:
|
||||
- Fix dark theme issue on login screen (#1097)
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.api.failure
|
|||
|
||||
import im.vector.matrix.android.api.session.crypto.MXCryptoError
|
||||
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
|
||||
import im.vector.matrix.android.internal.network.ssl.Fingerprint
|
||||
import java.io.IOException
|
||||
|
||||
/**
|
||||
|
@ -32,9 +33,11 @@ import java.io.IOException
|
|||
sealed class Failure(cause: Throwable? = null) : Throwable(cause = cause) {
|
||||
data class Unknown(val throwable: Throwable? = null) : Failure(throwable)
|
||||
data class Cancelled(val throwable: Throwable? = null) : Failure(throwable)
|
||||
data class UnrecognizedCertificateFailure(val url: String, val fingerprint: Fingerprint) : Failure()
|
||||
data class NetworkConnection(val ioException: IOException? = null) : Failure(ioException)
|
||||
data class ServerError(val error: MatrixError, val httpCode: Int) : Failure(RuntimeException(error.toString()))
|
||||
object SuccessError : Failure(RuntimeException(RuntimeException("SuccessResult is false")))
|
||||
|
||||
// When server send an error, but it cannot be interpreted as a MatrixError
|
||||
data class OtherServerError(val errorBody: String, val httpCode: Int) : Failure(RuntimeException("HTTP $httpCode: $errorBody"))
|
||||
|
||||
|
|
|
@ -16,8 +16,11 @@
|
|||
|
||||
package im.vector.matrix.android.api.failure
|
||||
|
||||
import im.vector.matrix.android.internal.network.ssl.Fingerprint
|
||||
|
||||
// This class will be sent to the bus
|
||||
sealed class GlobalError {
|
||||
data class InvalidToken(val softLogout: Boolean) : GlobalError()
|
||||
data class ConsentNotGivenError(val consentUri: String) : GlobalError()
|
||||
data class CertificateError(val fingerprint: Fingerprint) : GlobalError()
|
||||
}
|
||||
|
|
|
@ -43,6 +43,8 @@ import im.vector.matrix.android.internal.auth.version.isSupportedBySdk
|
|||
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||
import im.vector.matrix.android.internal.network.executeRequest
|
||||
import im.vector.matrix.android.internal.network.httpclient.addSocketFactory
|
||||
import im.vector.matrix.android.internal.network.ssl.UnrecognizedCertificateException
|
||||
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||
import im.vector.matrix.android.internal.task.configureWith
|
||||
import im.vector.matrix.android.internal.task.launchToCallback
|
||||
|
@ -121,7 +123,11 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
callback.onSuccess(it)
|
||||
},
|
||||
{
|
||||
callback.onFailure(it)
|
||||
if (it is UnrecognizedCertificateException) {
|
||||
callback.onFailure(Failure.UnrecognizedCertificateFailure(homeServerConnectionConfig.homeServerUri.toString(), it.fingerprint))
|
||||
} else {
|
||||
callback.onFailure(it)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
@ -248,7 +254,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
?: let {
|
||||
pendingSessionData?.homeServerConnectionConfig?.let {
|
||||
DefaultRegistrationWizard(
|
||||
okHttpClient,
|
||||
buildClient(it),
|
||||
retrofitFactory,
|
||||
coroutineDispatchers,
|
||||
sessionCreator,
|
||||
|
@ -269,7 +275,7 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
?: let {
|
||||
pendingSessionData?.homeServerConnectionConfig?.let {
|
||||
DefaultLoginWizard(
|
||||
okHttpClient,
|
||||
buildClient(it),
|
||||
retrofitFactory,
|
||||
coroutineDispatchers,
|
||||
sessionCreator,
|
||||
|
@ -347,7 +353,14 @@ internal class DefaultAuthenticationService @Inject constructor(
|
|||
}
|
||||
|
||||
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
|
||||
val retrofit = retrofitFactory.create(okHttpClient, homeServerConnectionConfig.homeServerUri.toString())
|
||||
val retrofit = retrofitFactory.create(buildClient(homeServerConnectionConfig), homeServerConnectionConfig.homeServerUri.toString())
|
||||
return retrofit.create(AuthAPI::class.java)
|
||||
}
|
||||
|
||||
private fun buildClient(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient {
|
||||
return okHttpClient.get()
|
||||
.newBuilder()
|
||||
.addSocketFactory(homeServerConnectionConfig)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,7 +17,6 @@
|
|||
package im.vector.matrix.android.internal.auth.login
|
||||
|
||||
import android.util.Patterns
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.auth.login.LoginWizard
|
||||
|
@ -44,7 +43,7 @@ import kotlinx.coroutines.withContext
|
|||
import okhttp3.OkHttpClient
|
||||
|
||||
internal class DefaultLoginWizard(
|
||||
okHttpClient: Lazy<OkHttpClient>,
|
||||
okHttpClient: OkHttpClient,
|
||||
retrofitFactory: RetrofitFactory,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val sessionCreator: SessionCreator,
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
|
||||
package im.vector.matrix.android.internal.auth.registration
|
||||
|
||||
import dagger.Lazy
|
||||
import im.vector.matrix.android.api.MatrixCallback
|
||||
import im.vector.matrix.android.api.auth.data.LoginFlowTypes
|
||||
import im.vector.matrix.android.api.auth.registration.RegisterThreePid
|
||||
|
@ -41,7 +40,7 @@ import okhttp3.OkHttpClient
|
|||
* This class execute the registration request and is responsible to keep the session of interactive authentication
|
||||
*/
|
||||
internal class DefaultRegistrationWizard(
|
||||
private val okHttpClient: Lazy<OkHttpClient>,
|
||||
private val okHttpClient: OkHttpClient,
|
||||
private val retrofitFactory: RetrofitFactory,
|
||||
private val coroutineDispatchers: MatrixCoroutineDispatchers,
|
||||
private val sessionCreator: SessionCreator,
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.network
|
|||
|
||||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.failure.shouldBeRetried
|
||||
import im.vector.matrix.android.internal.network.ssl.CertUtil
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.delay
|
||||
import org.greenrobot.eventbus.EventBus
|
||||
|
@ -26,7 +27,7 @@ import retrofit2.awaitResponse
|
|||
import java.io.IOException
|
||||
|
||||
internal suspend inline fun <DATA : Any> executeRequest(eventBus: EventBus?,
|
||||
block: Request<DATA>.() -> Unit) = Request<DATA>(eventBus).apply(block).execute()
|
||||
block: Request<DATA>.() -> Unit) = Request<DATA>(eventBus).apply(block).execute()
|
||||
|
||||
internal class Request<DATA : Any>(private val eventBus: EventBus?) {
|
||||
|
||||
|
@ -48,6 +49,15 @@ internal class Request<DATA : Any>(private val eventBus: EventBus?) {
|
|||
throw response.toFailure(eventBus)
|
||||
}
|
||||
} catch (exception: Throwable) {
|
||||
// Check if this is a certificateException
|
||||
CertUtil.getCertificateException(exception)
|
||||
// TODO Support certificate error once logged
|
||||
//?.also { unrecognizedCertificateException ->
|
||||
// // Send the error to the bus, for a global management
|
||||
// eventBus?.post(GlobalError.CertificateError(unrecognizedCertificateException))
|
||||
//}
|
||||
?.also { unrecognizedCertificateException -> throw unrecognizedCertificateException }
|
||||
|
||||
if (isRetryable && currentRetryCount++ < maxRetryCount && exception.shouldBeRetried()) {
|
||||
delay(currentDelay)
|
||||
currentDelay = (currentDelay * 2L).coerceAtMost(maxDelay)
|
||||
|
|
|
@ -26,7 +26,19 @@ import retrofit2.Retrofit
|
|||
import retrofit2.converter.moshi.MoshiConverterFactory
|
||||
import javax.inject.Inject
|
||||
|
||||
class RetrofitFactory @Inject constructor(private val moshi: Moshi) {
|
||||
internal class RetrofitFactory @Inject constructor(private val moshi: Moshi) {
|
||||
|
||||
/**
|
||||
* Use only for authentication service
|
||||
*/
|
||||
fun create(okHttpClient: OkHttpClient, baseUrl: String): Retrofit {
|
||||
return Retrofit.Builder()
|
||||
.baseUrl(baseUrl.ensureTrailingSlash())
|
||||
.client(okHttpClient)
|
||||
.addConverterFactory(UnitConverterFactory)
|
||||
.addConverterFactory(MoshiConverterFactory.create(moshi))
|
||||
.build()
|
||||
}
|
||||
|
||||
fun create(okHttpClient: Lazy<OkHttpClient>, baseUrl: String): Retrofit {
|
||||
return Retrofit.Builder()
|
||||
|
|
|
@ -16,24 +16,38 @@
|
|||
|
||||
package im.vector.matrix.android.internal.network.httpclient
|
||||
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
|
||||
import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor
|
||||
import im.vector.matrix.android.internal.network.ssl.CertUtil
|
||||
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
|
||||
import okhttp3.OkHttpClient
|
||||
import timber.log.Timber
|
||||
|
||||
internal fun OkHttpClient.addAccessTokenInterceptor(accessTokenProvider: AccessTokenProvider): OkHttpClient {
|
||||
return newBuilder()
|
||||
.apply {
|
||||
// Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor
|
||||
val existingCurlInterceptors = interceptors().filterIsInstance<CurlLoggingInterceptor>()
|
||||
interceptors().removeAll(existingCurlInterceptors)
|
||||
internal fun OkHttpClient.Builder.addAccessTokenInterceptor(accessTokenProvider: AccessTokenProvider): OkHttpClient.Builder {
|
||||
// Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor
|
||||
val existingCurlInterceptors = interceptors().filterIsInstance<CurlLoggingInterceptor>()
|
||||
interceptors().removeAll(existingCurlInterceptors)
|
||||
|
||||
addInterceptor(AccessTokenInterceptor(accessTokenProvider))
|
||||
addInterceptor(AccessTokenInterceptor(accessTokenProvider))
|
||||
|
||||
// Re add eventually the curl logging interceptors
|
||||
existingCurlInterceptors.forEach {
|
||||
addInterceptor(it)
|
||||
}
|
||||
}
|
||||
.build()
|
||||
// Re add eventually the curl logging interceptors
|
||||
existingCurlInterceptors.forEach {
|
||||
addInterceptor(it)
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
internal fun OkHttpClient.Builder.addSocketFactory(homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient.Builder {
|
||||
try {
|
||||
val pair = CertUtil.newPinnedSSLSocketFactory(homeServerConnectionConfig)
|
||||
sslSocketFactory(pair.sslSocketFactory, pair.x509TrustManager)
|
||||
hostnameVerifier(CertUtil.newHostnameVerifier(homeServerConnectionConfig))
|
||||
connectionSpecs(CertUtil.newConnectionSpecs(homeServerConnectionConfig))
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "addSocketFactory failed")
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
|
|
@ -16,29 +16,30 @@
|
|||
|
||||
package im.vector.matrix.android.internal.network.ssl
|
||||
|
||||
import android.util.Pair
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import okhttp3.ConnectionSpec
|
||||
import okhttp3.internal.tls.OkHostnameVerifier
|
||||
import timber.log.Timber
|
||||
import java.security.KeyStore
|
||||
import java.security.MessageDigest
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.HostnameVerifier
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.SSLPeerUnverifiedException
|
||||
import javax.net.ssl.SSLSocketFactory
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.TrustManagerFactory
|
||||
import javax.net.ssl.X509TrustManager
|
||||
import kotlin.experimental.and
|
||||
|
||||
/**
|
||||
* Various utility classes for dealing with X509Certificates
|
||||
*/
|
||||
internal object CertUtil {
|
||||
|
||||
// Set to false to do some test
|
||||
private const val USE_DEFAULT_HOSTNAME_VERIFIER = true
|
||||
|
||||
private val hexArray = "0123456789ABCDEF".toCharArray()
|
||||
|
||||
/**
|
||||
|
@ -95,11 +96,10 @@ internal object CertUtil {
|
|||
* @param fingerprint the fingerprint
|
||||
* @return the hexa string.
|
||||
*/
|
||||
@JvmOverloads
|
||||
fun fingerprintToHexString(fingerprint: ByteArray, sep: Char = ' '): String {
|
||||
val hexChars = CharArray(fingerprint.size * 3)
|
||||
for (j in fingerprint.indices) {
|
||||
val v = (fingerprint[j] and 0xFF.toByte()).toInt()
|
||||
val v = (fingerprint[j].toInt() and 0xFF)
|
||||
hexChars[j * 3] = hexArray[v.ushr(4)]
|
||||
hexChars[j * 3 + 1] = hexArray[v and 0x0F]
|
||||
hexChars[j * 3 + 2] = sep
|
||||
|
@ -128,13 +128,18 @@ internal object CertUtil {
|
|||
return null
|
||||
}
|
||||
|
||||
internal data class PinnedSSLSocketFactory(
|
||||
val sslSocketFactory: SSLSocketFactory,
|
||||
val x509TrustManager: X509TrustManager
|
||||
)
|
||||
|
||||
/**
|
||||
* Create a SSLSocket factory for a HS config.
|
||||
*
|
||||
* @param hsConfig the HS config.
|
||||
* @return SSLSocket factory
|
||||
*/
|
||||
fun newPinnedSSLSocketFactory(hsConfig: HomeServerConnectionConfig): Pair<SSLSocketFactory, X509TrustManager> {
|
||||
fun newPinnedSSLSocketFactory(hsConfig: HomeServerConnectionConfig): PinnedSSLSocketFactory {
|
||||
try {
|
||||
var defaultTrustManager: X509TrustManager? = null
|
||||
|
||||
|
@ -155,7 +160,7 @@ internal object CertUtil {
|
|||
try {
|
||||
tf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## addRule : onBingRuleUpdateFailure failed")
|
||||
Timber.e(e, "## newPinnedSSLSocketFactory() : TrustManagerFactory.getInstance of default failed")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -183,7 +188,7 @@ internal object CertUtil {
|
|||
sslSocketFactory = sslContext.socketFactory
|
||||
}
|
||||
|
||||
return Pair<SSLSocketFactory, X509TrustManager>(sslSocketFactory, defaultTrustManager)
|
||||
return PinnedSSLSocketFactory(sslSocketFactory, defaultTrustManager!!)
|
||||
} catch (e: Exception) {
|
||||
throw RuntimeException(e)
|
||||
}
|
||||
|
@ -196,11 +201,14 @@ internal object CertUtil {
|
|||
* @return a new HostnameVerifier.
|
||||
*/
|
||||
fun newHostnameVerifier(hsConfig: HomeServerConnectionConfig): HostnameVerifier {
|
||||
val defaultVerifier = HttpsURLConnection.getDefaultHostnameVerifier()
|
||||
val defaultVerifier: HostnameVerifier = OkHostnameVerifier // HttpsURLConnection.getDefaultHostnameVerifier()
|
||||
val trustedFingerprints = hsConfig.allowedFingerprints
|
||||
|
||||
return HostnameVerifier { hostname, session ->
|
||||
if (defaultVerifier.verify(hostname, session)) return@HostnameVerifier true
|
||||
if (USE_DEFAULT_HOSTNAME_VERIFIER) {
|
||||
if (defaultVerifier.verify(hostname, session)) return@HostnameVerifier true
|
||||
}
|
||||
// TODO How to recover from this error?
|
||||
if (trustedFingerprints.isEmpty()) return@HostnameVerifier false
|
||||
|
||||
// If remote cert matches an allowed fingerprint, just accept it.
|
||||
|
|
|
@ -32,7 +32,7 @@ data class Fingerprint(
|
|||
}
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
fun matchesCert(cert: X509Certificate): Boolean {
|
||||
internal fun matchesCert(cert: X509Certificate): Boolean {
|
||||
val o: Fingerprint? = when (hashType) {
|
||||
HashType.SHA256 -> newSha256Fingerprint(cert)
|
||||
HashType.SHA1 -> newSha1Fingerprint(cert)
|
||||
|
@ -57,7 +57,7 @@ data class Fingerprint(
|
|||
return result
|
||||
}
|
||||
|
||||
companion object {
|
||||
internal companion object {
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
fun newSha256Fingerprint(cert: X509Certificate): Fingerprint {
|
||||
|
@ -79,6 +79,6 @@ data class Fingerprint(
|
|||
@JsonClass(generateAdapter = false)
|
||||
enum class HashType {
|
||||
@Json(name = "sha-1") SHA1,
|
||||
@Json(name = "sha-256")SHA256
|
||||
@Json(name = "sha-256") SHA256
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,16 +34,19 @@ import javax.net.ssl.X509TrustManager
|
|||
internal class PinnedTrustManager(private val fingerprints: List<Fingerprint>?,
|
||||
private val defaultTrustManager: X509TrustManager?) : X509TrustManager {
|
||||
|
||||
// Set to false to perform some test
|
||||
private val USE_DEAFULT_TRUST_MANAGER = true
|
||||
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate>, s: String) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
if (defaultTrustManager != null && USE_DEAFULT_TRUST_MANAGER) {
|
||||
defaultTrustManager.checkClientTrusted(chain, s)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints == null || fingerprints.isEmpty()) {
|
||||
if (fingerprints.isNullOrEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause)
|
||||
}
|
||||
}
|
||||
|
@ -54,14 +57,14 @@ internal class PinnedTrustManager(private val fingerprints: List<Fingerprint>?,
|
|||
@Throws(CertificateException::class)
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate>, s: String) {
|
||||
try {
|
||||
if (defaultTrustManager != null) {
|
||||
if (defaultTrustManager != null && USE_DEAFULT_TRUST_MANAGER) {
|
||||
defaultTrustManager.checkServerTrusted(chain, s)
|
||||
return
|
||||
}
|
||||
} catch (e: CertificateException) {
|
||||
// If there is an exception we fall back to checking fingerprints
|
||||
if (fingerprints == null || fingerprints.isEmpty()) {
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause)
|
||||
throw UnrecognizedCertificateException(chain[0], Fingerprint.newSha256Fingerprint(chain[0]), e.cause /* BMA: Shouldn't be `e` ? */)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -51,14 +51,14 @@ import im.vector.matrix.android.internal.di.Unauthenticated
|
|||
import im.vector.matrix.android.internal.di.UserId
|
||||
import im.vector.matrix.android.internal.di.UserMd5
|
||||
import im.vector.matrix.android.internal.eventbus.EventBusTimberLogger
|
||||
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
|
||||
import im.vector.matrix.android.internal.network.DefaultNetworkConnectivityChecker
|
||||
import im.vector.matrix.android.internal.network.FallbackNetworkCallbackStrategy
|
||||
import im.vector.matrix.android.internal.network.NetworkCallbackStrategy
|
||||
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
|
||||
import im.vector.matrix.android.internal.network.PreferredNetworkCallbackStrategy
|
||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||
import im.vector.matrix.android.internal.network.interceptors.CurlLoggingInterceptor
|
||||
import im.vector.matrix.android.internal.network.httpclient.addAccessTokenInterceptor
|
||||
import im.vector.matrix.android.internal.network.httpclient.addSocketFactory
|
||||
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
|
||||
import im.vector.matrix.android.internal.network.token.HomeserverAccessTokenProvider
|
||||
import im.vector.matrix.android.internal.session.call.CallEventObserver
|
||||
|
@ -189,23 +189,17 @@ internal abstract class SessionModule {
|
|||
@Authenticated
|
||||
fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient,
|
||||
@Authenticated accessTokenProvider: AccessTokenProvider,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||
@SessionId sessionId: String,
|
||||
@MockHttpInterceptor testInterceptor: TestInterceptor?): OkHttpClient {
|
||||
return okHttpClient.newBuilder()
|
||||
.addAccessTokenInterceptor(accessTokenProvider)
|
||||
.addSocketFactory(homeServerConnectionConfig)
|
||||
.apply {
|
||||
// Remove the previous CurlLoggingInterceptor, to add it after the accessTokenInterceptor
|
||||
val existingCurlInterceptors = interceptors().filterIsInstance<CurlLoggingInterceptor>()
|
||||
interceptors().removeAll(existingCurlInterceptors)
|
||||
|
||||
addInterceptor(AccessTokenInterceptor(accessTokenProvider))
|
||||
if (testInterceptor != null) {
|
||||
testInterceptor.sessionId = sessionId
|
||||
addInterceptor(testInterceptor)
|
||||
}
|
||||
// Re add eventually the curl logging interceptors
|
||||
existingCurlInterceptors.forEach {
|
||||
addInterceptor(it)
|
||||
}
|
||||
}
|
||||
.build()
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.identity
|
|||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
||||
import im.vector.matrix.android.internal.di.AuthenticatedIdentity
|
||||
import im.vector.matrix.android.internal.di.IdentityDatabase
|
||||
|
@ -26,6 +27,7 @@ import im.vector.matrix.android.internal.di.SessionFilesDirectory
|
|||
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||
import im.vector.matrix.android.internal.di.UserMd5
|
||||
import im.vector.matrix.android.internal.network.httpclient.addAccessTokenInterceptor
|
||||
import im.vector.matrix.android.internal.network.httpclient.addSocketFactory
|
||||
import im.vector.matrix.android.internal.network.token.AccessTokenProvider
|
||||
import im.vector.matrix.android.internal.session.SessionModule
|
||||
import im.vector.matrix.android.internal.session.SessionScope
|
||||
|
@ -46,8 +48,13 @@ internal abstract class IdentityModule {
|
|||
@SessionScope
|
||||
@AuthenticatedIdentity
|
||||
fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient,
|
||||
@AuthenticatedIdentity accessTokenProvider: AccessTokenProvider): OkHttpClient {
|
||||
return okHttpClient.addAccessTokenInterceptor(accessTokenProvider)
|
||||
@AuthenticatedIdentity accessTokenProvider: AccessTokenProvider,
|
||||
homeServerConnectionConfig: HomeServerConnectionConfig): OkHttpClient {
|
||||
return okHttpClient
|
||||
.newBuilder()
|
||||
.addAccessTokenInterceptor(accessTokenProvider)
|
||||
.addSocketFactory(homeServerConnectionConfig)
|
||||
.build()
|
||||
}
|
||||
|
||||
@JvmStatic
|
||||
|
|
|
@ -21,6 +21,7 @@ import androidx.fragment.app.FragmentFactory
|
|||
import androidx.lifecycle.ViewModelProvider
|
||||
import dagger.BindsInstance
|
||||
import dagger.Component
|
||||
import im.vector.riotx.core.dialogs.UnrecognizedCertificateDialog
|
||||
import im.vector.riotx.core.error.ErrorFormatter
|
||||
import im.vector.riotx.core.preference.UserAvatarPreference
|
||||
import im.vector.riotx.features.MainActivity
|
||||
|
@ -100,6 +101,7 @@ interface ScreenComponent {
|
|||
fun navigator(): Navigator
|
||||
fun errorFormatter(): ErrorFormatter
|
||||
fun uiStateRepository(): UiStateRepository
|
||||
fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog
|
||||
|
||||
/* ==========================================================================================
|
||||
* Activities
|
||||
|
|
|
@ -27,6 +27,7 @@ import im.vector.riotx.ActiveSessionDataSource
|
|||
import im.vector.riotx.EmojiCompatFontProvider
|
||||
import im.vector.riotx.EmojiCompatWrapper
|
||||
import im.vector.riotx.VectorApplication
|
||||
import im.vector.riotx.core.dialogs.UnrecognizedCertificateDialog
|
||||
import im.vector.riotx.core.error.ErrorFormatter
|
||||
import im.vector.riotx.core.pushers.PushersManager
|
||||
import im.vector.riotx.core.utils.AssetReader
|
||||
|
@ -89,6 +90,8 @@ interface VectorComponent {
|
|||
|
||||
fun activeSessionHolder(): ActiveSessionHolder
|
||||
|
||||
fun unrecognizedCertificateDialog(): UnrecognizedCertificateDialog
|
||||
|
||||
fun emojiCompatFontProvider(): EmojiCompatFontProvider
|
||||
|
||||
fun emojiCompatWrapper(): EmojiCompatWrapper
|
||||
|
|
|
@ -0,0 +1,171 @@
|
|||
/*
|
||||
* Copyright (c) 2020 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package im.vector.riotx.core.dialogs
|
||||
|
||||
import android.app.Activity
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import im.vector.matrix.android.internal.network.ssl.Fingerprint
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||
import im.vector.riotx.core.resources.StringProvider
|
||||
import timber.log.Timber
|
||||
import java.util.HashMap
|
||||
import java.util.HashSet
|
||||
import javax.inject.Inject
|
||||
|
||||
/**
|
||||
* This class displays the unknown certificate dialog
|
||||
*/
|
||||
class UnrecognizedCertificateDialog @Inject constructor(
|
||||
private val activeSessionHolder: ActiveSessionHolder,
|
||||
private val stringProvider: StringProvider
|
||||
) {
|
||||
private val ignoredFingerprints: MutableMap<String, MutableSet<Fingerprint>> = HashMap()
|
||||
private val openDialogIds: MutableSet<String> = HashSet()
|
||||
|
||||
/**
|
||||
* Display a certificate dialog box, asking the user about an unknown certificate
|
||||
* To use when user is currently logged in
|
||||
*
|
||||
* @param unrecognizedFingerprint the fingerprint for the unknown certificate
|
||||
* @param callback callback to fire when the user makes a decision
|
||||
*/
|
||||
fun show(activity: Activity,
|
||||
unrecognizedFingerprint: Fingerprint,
|
||||
callback: Callback) {
|
||||
val userId = activeSessionHolder.getSafeActiveSession()?.myUserId
|
||||
val hsConfig = activeSessionHolder.getSafeActiveSession()?.sessionParams?.homeServerConnectionConfig ?: return
|
||||
|
||||
internalShow(activity, unrecognizedFingerprint, true, callback, userId, hsConfig.homeServerUri.toString(), hsConfig.allowedFingerprints.isNotEmpty())
|
||||
}
|
||||
|
||||
/**
|
||||
* To use during login flow
|
||||
*/
|
||||
fun show(activity: Activity,
|
||||
unrecognizedFingerprint: Fingerprint,
|
||||
homeServerUrl: String,
|
||||
callback: Callback) {
|
||||
internalShow(activity, unrecognizedFingerprint, false, callback, null, homeServerUrl, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Display a certificate dialog box, asking the user about an unknown certificate
|
||||
*
|
||||
* @param unrecognizedFingerprint the fingerprint for the unknown certificate
|
||||
* @param existing the current session already exist, so it mean that something has changed server side
|
||||
* @param callback callback to fire when the user makes a decision
|
||||
*/
|
||||
private fun internalShow(activity: Activity,
|
||||
unrecognizedFingerprint: Fingerprint,
|
||||
existing: Boolean,
|
||||
callback: Callback,
|
||||
userId: String?,
|
||||
homeServerUrl: String,
|
||||
homeServerConnectionConfigHasFingerprints: Boolean) {
|
||||
val dialogId = userId ?: homeServerUrl + unrecognizedFingerprint.displayableHexRepr
|
||||
|
||||
if (openDialogIds.contains(dialogId)) {
|
||||
Timber.i("Not opening dialog $dialogId as one is already open.")
|
||||
return
|
||||
}
|
||||
|
||||
if (userId != null) {
|
||||
val f: Set<Fingerprint>? = ignoredFingerprints[userId]
|
||||
if (f != null && f.contains(unrecognizedFingerprint)) {
|
||||
callback.onIgnore()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
val builder = AlertDialog.Builder(activity)
|
||||
val inflater = activity.layoutInflater
|
||||
val layout: View = inflater.inflate(R.layout.dialog_ssl_fingerprint, null)
|
||||
val sslFingerprintTitle = layout.findViewById<TextView>(R.id.ssl_fingerprint_title)
|
||||
sslFingerprintTitle.text = stringProvider.getString(R.string.ssl_fingerprint_hash, unrecognizedFingerprint.hashType.toString())
|
||||
val sslFingerprint = layout.findViewById<TextView>(R.id.ssl_fingerprint)
|
||||
sslFingerprint.text = unrecognizedFingerprint.displayableHexRepr
|
||||
val sslUserId = layout.findViewById<TextView>(R.id.ssl_user_id)
|
||||
if (userId != null) {
|
||||
sslUserId.text = stringProvider.getString(R.string.generic_label_and_value,
|
||||
stringProvider.getString(R.string.username),
|
||||
userId)
|
||||
} else {
|
||||
sslUserId.text = stringProvider.getString(R.string.generic_label_and_value,
|
||||
stringProvider.getString(R.string.hs_url),
|
||||
homeServerUrl)
|
||||
}
|
||||
val sslExpl = layout.findViewById<TextView>(R.id.ssl_explanation)
|
||||
if (existing) {
|
||||
if (homeServerConnectionConfigHasFingerprints) {
|
||||
sslExpl.text = stringProvider.getString(R.string.ssl_expected_existing_expl)
|
||||
} else {
|
||||
sslExpl.text = stringProvider.getString(R.string.ssl_unexpected_existing_expl)
|
||||
}
|
||||
} else {
|
||||
sslExpl.text = stringProvider.getString(R.string.ssl_cert_new_account_expl)
|
||||
}
|
||||
builder.setView(layout)
|
||||
builder.setTitle(R.string.ssl_could_not_verify)
|
||||
builder.setPositiveButton(R.string.ssl_trust) { _, _ ->
|
||||
callback.onAccept()
|
||||
}
|
||||
if (existing) {
|
||||
builder.setNegativeButton(R.string.ssl_remain_offline) { _, _ ->
|
||||
if (userId != null) {
|
||||
var f = ignoredFingerprints[userId]
|
||||
if (f == null) {
|
||||
f = HashSet()
|
||||
ignoredFingerprints[userId] = f
|
||||
}
|
||||
f.add(unrecognizedFingerprint)
|
||||
}
|
||||
callback.onIgnore()
|
||||
}
|
||||
builder.setNeutralButton(R.string.ssl_logout_account) { _, _ -> callback.onReject() }
|
||||
} else {
|
||||
builder.setNegativeButton(R.string.cancel) { _, _ -> callback.onReject() }
|
||||
}
|
||||
|
||||
builder.setOnDismissListener {
|
||||
Timber.d("Dismissed!")
|
||||
openDialogIds.remove(dialogId)
|
||||
|
||||
}
|
||||
|
||||
builder.show()
|
||||
openDialogIds.add(dialogId)
|
||||
}
|
||||
|
||||
interface Callback {
|
||||
/**
|
||||
* The certificate was explicitly accepted
|
||||
*/
|
||||
fun onAccept()
|
||||
|
||||
/**
|
||||
* The warning was ignored by the user
|
||||
*/
|
||||
fun onIgnore()
|
||||
|
||||
/**
|
||||
* The unknown certificate was explicitly rejected
|
||||
*/
|
||||
fun onReject()
|
||||
}
|
||||
}
|
|
@ -26,6 +26,8 @@ import java.net.HttpURLConnection
|
|||
import java.net.SocketTimeoutException
|
||||
import java.net.UnknownHostException
|
||||
import javax.inject.Inject
|
||||
import javax.net.ssl.SSLException
|
||||
import javax.net.ssl.SSLPeerUnverifiedException
|
||||
|
||||
interface ErrorFormatter {
|
||||
fun toHumanReadable(throwable: Throwable?): String
|
||||
|
@ -41,13 +43,17 @@ class DefaultErrorFormatter @Inject constructor(
|
|||
is IdentityServiceError -> identityServerError(throwable)
|
||||
is Failure.NetworkConnection -> {
|
||||
when (throwable.ioException) {
|
||||
is SocketTimeoutException ->
|
||||
is SocketTimeoutException ->
|
||||
stringProvider.getString(R.string.error_network_timeout)
|
||||
is UnknownHostException ->
|
||||
is UnknownHostException ->
|
||||
// Invalid homeserver?
|
||||
// TODO Check network state, airplane mode, etc.
|
||||
stringProvider.getString(R.string.login_error_unknown_host)
|
||||
else ->
|
||||
is SSLPeerUnverifiedException ->
|
||||
stringProvider.getString(R.string.login_error_ssl_peer_unverified)
|
||||
is SSLException ->
|
||||
stringProvider.getString(R.string.login_error_ssl_other)
|
||||
else ->
|
||||
stringProvider.getString(R.string.error_no_network)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,7 +55,10 @@ import im.vector.riotx.core.di.HasVectorInjector
|
|||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.di.VectorComponent
|
||||
import im.vector.riotx.core.dialogs.DialogLocker
|
||||
import im.vector.riotx.core.dialogs.UnrecognizedCertificateDialog
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.extensions.observeEvent
|
||||
import im.vector.riotx.core.extensions.vectorComponent
|
||||
import im.vector.riotx.core.utils.toast
|
||||
import im.vector.riotx.features.MainActivity
|
||||
import im.vector.riotx.features.MainActivityArgs
|
||||
|
@ -231,7 +234,30 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
|
|||
is GlobalError.ConsentNotGivenError ->
|
||||
consentNotGivenHelper.displayDialog(globalError.consentUri,
|
||||
activeSessionHolder.getActiveSession().sessionParams.homeServerHost ?: "")
|
||||
}
|
||||
is GlobalError.CertificateError ->
|
||||
handleCertificateError(globalError)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleCertificateError(certificateError: GlobalError.CertificateError) {
|
||||
vectorComponent()
|
||||
.unrecognizedCertificateDialog()
|
||||
.show(this,
|
||||
certificateError.fingerprint,
|
||||
object : UnrecognizedCertificateDialog.Callback {
|
||||
override fun onAccept() {
|
||||
// TODO Support certificate error once logged
|
||||
}
|
||||
|
||||
override fun onIgnore() {
|
||||
// TODO Support certificate error once logged
|
||||
}
|
||||
|
||||
override fun onReject() {
|
||||
// TODO Support certificate error once logged
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
protected open fun handleInvalidToken(globalError: GlobalError.InvalidToken) {
|
||||
|
|
|
@ -44,6 +44,7 @@ import im.vector.riotx.R
|
|||
import im.vector.riotx.core.di.DaggerScreenComponent
|
||||
import im.vector.riotx.core.di.HasScreenInjector
|
||||
import im.vector.riotx.core.di.ScreenComponent
|
||||
import im.vector.riotx.core.dialogs.UnrecognizedCertificateDialog
|
||||
import im.vector.riotx.core.error.ErrorFormatter
|
||||
import im.vector.riotx.features.navigation.Navigator
|
||||
import io.reactivex.android.schedulers.AndroidSchedulers
|
||||
|
@ -69,6 +70,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
|||
|
||||
protected lateinit var navigator: Navigator
|
||||
protected lateinit var errorFormatter: ErrorFormatter
|
||||
protected lateinit var unrecognizedCertificateDialog: UnrecognizedCertificateDialog
|
||||
|
||||
private var progress: ProgressDialog? = null
|
||||
|
||||
|
@ -92,6 +94,7 @@ abstract class VectorBaseFragment : BaseMvRxFragment(), HasScreenInjector {
|
|||
screenComponent = DaggerScreenComponent.factory().create(vectorBaseActivity.getVectorComponent(), vectorBaseActivity)
|
||||
navigator = screenComponent.navigator()
|
||||
errorFormatter = screenComponent.errorFormatter()
|
||||
unrecognizedCertificateDialog = screenComponent.unrecognizedCertificateDialog()
|
||||
viewModelFactory = screenComponent.viewModelFactory()
|
||||
childFragmentManager.fragmentFactory = screenComponent.fragmentFactory()
|
||||
injectWith(injector())
|
||||
|
|
|
@ -27,6 +27,7 @@ import com.airbnb.mvrx.withState
|
|||
import im.vector.matrix.android.api.failure.Failure
|
||||
import im.vector.matrix.android.api.failure.MatrixError
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.dialogs.UnrecognizedCertificateDialog
|
||||
import im.vector.riotx.core.extensions.exhaustive
|
||||
import im.vector.riotx.core.platform.OnBackPressed
|
||||
import im.vector.riotx.core.platform.VectorBaseFragment
|
||||
|
@ -72,7 +73,7 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
|||
|
||||
override fun showFailure(throwable: Throwable) {
|
||||
when (throwable) {
|
||||
is Failure.ServerError -> {
|
||||
is Failure.ServerError ->
|
||||
if (throwable.error.code == MatrixError.M_FORBIDDEN
|
||||
&& throwable.httpCode == HttpsURLConnection.HTTP_FORBIDDEN /* 403 */) {
|
||||
AlertDialog.Builder(requireActivity())
|
||||
|
@ -83,11 +84,34 @@ abstract class AbstractLoginFragment : VectorBaseFragment(), OnBackPressed {
|
|||
} else {
|
||||
onError(throwable)
|
||||
}
|
||||
}
|
||||
else -> onError(throwable)
|
||||
is Failure.UnrecognizedCertificateFailure ->
|
||||
showUnrecognizedCertificateFailure(throwable)
|
||||
else ->
|
||||
onError(throwable)
|
||||
}
|
||||
}
|
||||
|
||||
private fun showUnrecognizedCertificateFailure(failure: Failure.UnrecognizedCertificateFailure) {
|
||||
// Ask the user to accept the certificate
|
||||
unrecognizedCertificateDialog.show(requireActivity(),
|
||||
failure.fingerprint,
|
||||
failure.url,
|
||||
object : UnrecognizedCertificateDialog.Callback {
|
||||
override fun onAccept() {
|
||||
// User accept the certificate
|
||||
loginViewModel.handle(LoginAction.UserAcceptCertificate(failure.fingerprint))
|
||||
}
|
||||
|
||||
override fun onIgnore() {
|
||||
// Cannot happen in this case
|
||||
}
|
||||
|
||||
override fun onReject() {
|
||||
// Nothing to do in this case
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
open fun onError(throwable: Throwable) {
|
||||
super.showFailure(throwable)
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.riotx.features.login
|
|||
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.auth.registration.RegisterThreePid
|
||||
import im.vector.matrix.android.internal.network.ssl.Fingerprint
|
||||
import im.vector.riotx.core.platform.VectorViewModelAction
|
||||
|
||||
sealed class LoginAction : VectorViewModelAction {
|
||||
|
@ -61,4 +62,6 @@ sealed class LoginAction : VectorViewModelAction {
|
|||
data class SetupSsoForSessionRecovery(val homeServerUrl: String, val deviceId: String) : LoginAction()
|
||||
|
||||
data class PostViewEvent(val viewEvent: LoginViewEvents) : LoginAction()
|
||||
|
||||
data class UserAcceptCertificate(val fingerprint: Fingerprint) : LoginAction()
|
||||
}
|
||||
|
|
|
@ -133,15 +133,14 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
}
|
||||
}
|
||||
}
|
||||
is LoginViewEvents.OutdatedHomeserver ->
|
||||
is LoginViewEvents.OutdatedHomeserver -> {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.login_error_outdated_homeserver_title)
|
||||
.setMessage(R.string.login_error_outdated_homeserver_content)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
is LoginViewEvents.Failure ->
|
||||
// This is handled by the Fragments
|
||||
Unit
|
||||
}
|
||||
is LoginViewEvents.OpenServerSelection ->
|
||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||
LoginServerSelectionFragment::class.java,
|
||||
|
@ -195,7 +194,11 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
|||
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginViewEvents.msisdn),
|
||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||
option = commonOption)
|
||||
}
|
||||
is LoginViewEvents.Failure,
|
||||
is LoginViewEvents.Loading ->
|
||||
// This is handled by the Fragments
|
||||
Unit
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun updateWithState(loginViewState: LoginViewState) {
|
||||
|
|
|
@ -87,6 +87,8 @@ class LoginViewModel @AssistedInject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private var currentHomeServerConnectionConfig: HomeServerConnectionConfig? = null
|
||||
|
||||
val currentThreePid: String?
|
||||
get() = registrationWizard?.currentThreePid
|
||||
|
||||
|
@ -118,10 +120,18 @@ class LoginViewModel @AssistedInject constructor(
|
|||
is LoginAction.RegisterAction -> handleRegisterAction(action)
|
||||
is LoginAction.ResetAction -> handleResetAction(action)
|
||||
is LoginAction.SetupSsoForSessionRecovery -> handleSetupSsoForSessionRecovery(action)
|
||||
is LoginAction.UserAcceptCertificate -> handleUserAcceptCertificate(action)
|
||||
is LoginAction.PostViewEvent -> _viewEvents.post(action.viewEvent)
|
||||
}.exhaustive
|
||||
}
|
||||
|
||||
private fun handleUserAcceptCertificate(action: LoginAction.UserAcceptCertificate) {
|
||||
// It happen when we get the login flow, so alter the homeserver config and retrieve again the login flow
|
||||
currentHomeServerConnectionConfig
|
||||
?.let { it.copy(allowedFingerprints = it.allowedFingerprints + action.fingerprint) }
|
||||
?.let { getLoginFlow(it) }
|
||||
}
|
||||
|
||||
private fun handleLoginWithToken(action: LoginAction.LoginWithToken) {
|
||||
val safeLoginWizard = loginWizard
|
||||
|
||||
|
@ -649,67 +659,74 @@ class LoginViewModel @AssistedInject constructor(
|
|||
// This is invalid
|
||||
_viewEvents.post(LoginViewEvents.Failure(Throwable("Unable to create a HomeServerConnectionConfig")))
|
||||
} else {
|
||||
currentTask?.cancel()
|
||||
currentTask = null
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
getLoginFlow(homeServerConnectionConfig)
|
||||
}
|
||||
}
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Loading()
|
||||
)
|
||||
private fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig) {
|
||||
currentHomeServerConnectionConfig = homeServerConnectionConfig
|
||||
|
||||
currentTask?.cancel()
|
||||
currentTask = null
|
||||
authenticationService.cancelPendingLoginOrRegistration()
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Loading()
|
||||
)
|
||||
}
|
||||
|
||||
currentTask = authenticationService.getLoginFlow(homeServerConnectionConfig, object : MatrixCallback<LoginFlowResult> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
currentTask = authenticationService.getLoginFlow(homeServerConnectionConfig, object : MatrixCallback<LoginFlowResult> {
|
||||
override fun onFailure(failure: Throwable) {
|
||||
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onSuccess(data: LoginFlowResult) {
|
||||
when (data) {
|
||||
is LoginFlowResult.Success -> {
|
||||
val loginMode = when {
|
||||
// SSO login is taken first
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
}
|
||||
|
||||
if (loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) {
|
||||
notSupported()
|
||||
} else {
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
homeServerUrl = data.homeServerUrl,
|
||||
loginMode = loginMode,
|
||||
loginModeSupportedTypes = data.supportedLoginTypes.toList()
|
||||
)
|
||||
}
|
||||
}
|
||||
override fun onSuccess(data: LoginFlowResult) {
|
||||
when (data) {
|
||||
is LoginFlowResult.Success -> {
|
||||
val loginMode = when {
|
||||
// SSO login is taken first
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.SSO) -> LoginMode.Sso
|
||||
data.supportedLoginTypes.contains(LoginFlowTypes.PASSWORD) -> LoginMode.Password
|
||||
else -> LoginMode.Unsupported
|
||||
}
|
||||
is LoginFlowResult.OutdatedHomeserver -> {
|
||||
|
||||
if (loginMode == LoginMode.Password && !data.isLoginAndRegistrationSupported) {
|
||||
notSupported()
|
||||
} else {
|
||||
// FIXME We should post a view event here normally?
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized,
|
||||
homeServerUrl = data.homeServerUrl,
|
||||
loginMode = loginMode,
|
||||
loginModeSupportedTypes = data.supportedLoginTypes.toList()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun notSupported() {
|
||||
// Notify the UI
|
||||
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized
|
||||
)
|
||||
is LoginFlowResult.OutdatedHomeserver -> {
|
||||
notSupported()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private fun notSupported() {
|
||||
// Notify the UI
|
||||
_viewEvents.post(LoginViewEvents.OutdatedHomeserver)
|
||||
|
||||
setState {
|
||||
copy(
|
||||
asyncHomeServerLoginFlowRequest = Uninitialized
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onCleared() {
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.core.widget.NestedScrollView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:paddingStart="?dialogPreferredPadding"
|
||||
android:paddingLeft="?dialogPreferredPadding"
|
||||
android:paddingTop="12dp"
|
||||
android:paddingEnd="?dialogPreferredPadding"
|
||||
android:paddingRight="?dialogPreferredPadding">
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="6dp"
|
||||
android:text="@string/ssl_cert_not_trust" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ssl_explanation"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="6dp"
|
||||
android:text="@string/ssl_cert_new_account_expl" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ssl_user_id"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="6dp"
|
||||
tools:text="Home Server URL: http://matrix.org" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ssl_fingerprint_title"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="6dp"
|
||||
android:text="@string/ssl_fingerprint_hash" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/ssl_fingerprint"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="12dp"
|
||||
tools:text="07 89 7A A0 30 82 99 95 E6 17 5D 1F 34 5D 8D 0C 67 82 63 1C 1F 57 20 75 42 91 F7 8B 28 03 54 A2" />
|
||||
|
||||
<TextView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="6dp"
|
||||
android:text="@string/ssl_only_accept" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.core.widget.NestedScrollView>
|
|
@ -298,6 +298,8 @@
|
|||
<string name="login_error_unknown_host">This URL is not reachable, please check it</string>
|
||||
<string name="login_error_no_homeserver_found">This is not a valid Matrix server address</string>
|
||||
<string name="login_error_homeserver_not_found">Cannot reach a homeserver at this URL, please check it</string>
|
||||
<string name="login_error_ssl_peer_unverified">"SSL Error: the peer's identity has not been verified."</string>
|
||||
<string name="login_error_ssl_other">"SSL Error."</string>
|
||||
<string name="login_error_ssl_handshake">Your device is using an outdated TLS security protocol, vulnerable to attack, for your security you will not be able to connect</string>
|
||||
<string name="login_mobile_device">Mobile</string>
|
||||
|
||||
|
|
Loading…
Reference in New Issue