Support homeserver discovery from MXID - Wellknown (#476)
This commit is contained in:
parent
63c18e82c8
commit
c9bc6f4a9e
|
@ -7,6 +7,7 @@ Features ✨:
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
- Invite member(s) to an existing room (#1276)
|
- Invite member(s) to an existing room (#1276)
|
||||||
- Improve notification accessibility with ticker text (#1226)
|
- Improve notification accessibility with ticker text (#1226)
|
||||||
|
- Support homeserver discovery from MXID (#476)
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
- Sometimes the same device appears twice in the list of devices of a user (#1329)
|
- Sometimes the same device appears twice in the list of devices of a user (#1329)
|
||||||
|
|
|
@ -23,6 +23,7 @@ import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||||
import im.vector.matrix.android.api.auth.data.SessionParams
|
import im.vector.matrix.android.api.auth.data.SessionParams
|
||||||
import im.vector.matrix.android.api.auth.login.LoginWizard
|
import im.vector.matrix.android.api.auth.login.LoginWizard
|
||||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||||
|
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
|
||||||
|
@ -30,7 +31,6 @@ import im.vector.matrix.android.api.util.Cancelable
|
||||||
* This interface defines methods to authenticate or to create an account to a matrix server.
|
* This interface defines methods to authenticate or to create an account to a matrix server.
|
||||||
*/
|
*/
|
||||||
interface AuthenticationService {
|
interface AuthenticationService {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Request the supported login flows for this homeserver.
|
* Request the supported login flows for this homeserver.
|
||||||
* This is the first method to call to be able to get a wizard to login or the create an account
|
* This is the first method to call to be able to get a wizard to login or the create an account
|
||||||
|
@ -89,4 +89,20 @@ interface AuthenticationService {
|
||||||
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
fun createSessionFromSso(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
credentials: Credentials,
|
credentials: Credentials,
|
||||||
callback: MatrixCallback<Session>): Cancelable
|
callback: MatrixCallback<Session>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Perform a wellknown request, using the domain from the matrixId
|
||||||
|
*/
|
||||||
|
fun getWellKnownData(matrixId: String,
|
||||||
|
callback: MatrixCallback<WellknownResult>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authenticate with a matrixId and a password
|
||||||
|
* Usually call this after a successful call to getWellKnownData()
|
||||||
|
*/
|
||||||
|
fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
|
matrixId: String,
|
||||||
|
password: String,
|
||||||
|
initialDeviceName: String,
|
||||||
|
callback: MatrixCallback<Session>): Cancelable
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ package im.vector.matrix.android.api.auth.data
|
||||||
|
|
||||||
import com.squareup.moshi.Json
|
import com.squareup.moshi.Json
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
|
import im.vector.matrix.android.api.util.JsonDict
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||||
|
@ -52,7 +53,7 @@ data class WellKnown(
|
||||||
val identityServer: WellKnownBaseConfig? = null,
|
val identityServer: WellKnownBaseConfig? = null,
|
||||||
|
|
||||||
@Json(name = "m.integrations")
|
@Json(name = "m.integrations")
|
||||||
val integrations: Map<String, @JvmSuppressWildcards Any>? = null
|
val integrations: JsonDict? = null
|
||||||
) {
|
) {
|
||||||
/**
|
/**
|
||||||
* Returns the list of integration managers proposed
|
* Returns the list of integration managers proposed
|
||||||
|
|
|
@ -16,6 +16,6 @@
|
||||||
package im.vector.matrix.android.api.auth.data
|
package im.vector.matrix.android.api.auth.data
|
||||||
|
|
||||||
data class WellKnownManagerConfig(
|
data class WellKnownManagerConfig(
|
||||||
val apiUrl : String,
|
val apiUrl: String,
|
||||||
val uiUrl: String
|
val uiUrl: String
|
||||||
)
|
)
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.api.auth.wellknown
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.auth.data.WellKnown
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#well-known-uri
|
||||||
|
*/
|
||||||
|
sealed class WellknownResult {
|
||||||
|
/**
|
||||||
|
* The provided matrixId is no valid. Unable to extract a domain name.
|
||||||
|
*/
|
||||||
|
object InvalidMatrixId : WellknownResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the specific piece of information from the user in a way which fits within the existing client user experience,
|
||||||
|
* if the client is inclined to do so. Failure can take place instead if no good user experience for this is possible at this point.
|
||||||
|
*/
|
||||||
|
data class Prompt(val homerServerUrl: String,
|
||||||
|
val identityServerUrl: String?,
|
||||||
|
val wellKnown: WellKnown) : WellknownResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stop the current auto-discovery mechanism. If no more auto-discovery mechanisms are available,
|
||||||
|
* then the client may use other methods of determining the required parameters, such as prompting the user, or using default values.
|
||||||
|
*/
|
||||||
|
object Ignore : WellknownResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the user that auto-discovery failed due to invalid/empty data and PROMPT for the parameter.
|
||||||
|
*/
|
||||||
|
object FailPrompt : WellknownResult()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inform the user that auto-discovery did not return any usable URLs. Do not continue further with the current login process.
|
||||||
|
* At this point, valid data was obtained, but no homeserver is available to serve the client.
|
||||||
|
* No further guess should be attempted and the user should make a conscientious decision what to do next.
|
||||||
|
*/
|
||||||
|
object FailError : WellknownResult()
|
||||||
|
}
|
|
@ -25,6 +25,10 @@ import im.vector.matrix.android.internal.auth.db.AuthRealmMigration
|
||||||
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
|
import im.vector.matrix.android.internal.auth.db.AuthRealmModule
|
||||||
import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore
|
import im.vector.matrix.android.internal.auth.db.RealmPendingSessionStore
|
||||||
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
|
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
|
||||||
|
import im.vector.matrix.android.internal.auth.wellknown.DefaultDirectLoginTask
|
||||||
|
import im.vector.matrix.android.internal.auth.wellknown.DefaultGetWellknownTask
|
||||||
|
import im.vector.matrix.android.internal.auth.wellknown.DirectLoginTask
|
||||||
|
import im.vector.matrix.android.internal.auth.wellknown.GetWellknownTask
|
||||||
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
import im.vector.matrix.android.internal.database.RealmKeysUtils
|
||||||
import im.vector.matrix.android.internal.di.AuthDatabase
|
import im.vector.matrix.android.internal.di.AuthDatabase
|
||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
|
@ -59,14 +63,20 @@ internal abstract class AuthModule {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore
|
abstract fun bindSessionParamsStore(store: RealmSessionParamsStore): SessionParamsStore
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindPendingSessionStore(pendingSessionStore: RealmPendingSessionStore): PendingSessionStore
|
abstract fun bindPendingSessionStore(store: RealmPendingSessionStore): PendingSessionStore
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindAuthenticationService(authenticationService: DefaultAuthenticationService): AuthenticationService
|
abstract fun bindAuthenticationService(service: DefaultAuthenticationService): AuthenticationService
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindSessionCreator(sessionCreator: DefaultSessionCreator): SessionCreator
|
abstract fun bindSessionCreator(creator: DefaultSessionCreator): SessionCreator
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindGetWellknownTask(task: DefaultGetWellknownTask): GetWellknownTask
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindDirectLoginTask(task: DefaultDirectLoginTask): DirectLoginTask
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import im.vector.matrix.android.api.auth.data.isLoginAndRegistrationSupportedByS
|
||||||
import im.vector.matrix.android.api.auth.data.isSupportedBySdk
|
import im.vector.matrix.android.api.auth.data.isSupportedBySdk
|
||||||
import im.vector.matrix.android.api.auth.login.LoginWizard
|
import im.vector.matrix.android.api.auth.login.LoginWizard
|
||||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||||
|
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||||
import im.vector.matrix.android.api.failure.Failure
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
|
@ -38,9 +39,13 @@ import im.vector.matrix.android.internal.auth.data.RiotConfig
|
||||||
import im.vector.matrix.android.internal.auth.db.PendingSessionData
|
import im.vector.matrix.android.internal.auth.db.PendingSessionData
|
||||||
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
|
import im.vector.matrix.android.internal.auth.login.DefaultLoginWizard
|
||||||
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
|
import im.vector.matrix.android.internal.auth.registration.DefaultRegistrationWizard
|
||||||
|
import im.vector.matrix.android.internal.auth.wellknown.DirectLoginTask
|
||||||
|
import im.vector.matrix.android.internal.auth.wellknown.GetWellknownTask
|
||||||
import im.vector.matrix.android.internal.di.Unauthenticated
|
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
import im.vector.matrix.android.internal.network.RetrofitFactory
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
import im.vector.matrix.android.internal.network.executeRequest
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.task.TaskExecutor
|
||||||
|
import im.vector.matrix.android.internal.task.configureWith
|
||||||
import im.vector.matrix.android.internal.task.launchToCallback
|
import im.vector.matrix.android.internal.task.launchToCallback
|
||||||
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
|
||||||
import im.vector.matrix.android.internal.util.toCancelable
|
import im.vector.matrix.android.internal.util.toCancelable
|
||||||
|
@ -59,7 +64,10 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
private val sessionParamsStore: SessionParamsStore,
|
private val sessionParamsStore: SessionParamsStore,
|
||||||
private val sessionManager: SessionManager,
|
private val sessionManager: SessionManager,
|
||||||
private val sessionCreator: SessionCreator,
|
private val sessionCreator: SessionCreator,
|
||||||
private val pendingSessionStore: PendingSessionStore
|
private val pendingSessionStore: PendingSessionStore,
|
||||||
|
private val getWellknownTask: GetWellknownTask,
|
||||||
|
private val directLoginTask: DirectLoginTask,
|
||||||
|
private val taskExecutor: TaskExecutor
|
||||||
) : AuthenticationService {
|
) : AuthenticationService {
|
||||||
|
|
||||||
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
|
private var pendingSessionData: PendingSessionData? = pendingSessionStore.getPendingSessionData()
|
||||||
|
@ -260,6 +268,26 @@ internal class DefaultAuthenticationService @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getWellKnownData(matrixId: String, callback: MatrixCallback<WellknownResult>): Cancelable {
|
||||||
|
return getWellknownTask
|
||||||
|
.configureWith(GetWellknownTask.Params(matrixId)) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun directAuthentication(homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
|
matrixId: String,
|
||||||
|
password: String,
|
||||||
|
initialDeviceName: String,
|
||||||
|
callback: MatrixCallback<Session>): Cancelable {
|
||||||
|
return directLoginTask
|
||||||
|
.configureWith(DirectLoginTask.Params(homeServerConnectionConfig, matrixId, password, initialDeviceName)) {
|
||||||
|
this.callback = callback
|
||||||
|
}
|
||||||
|
.executeBy(taskExecutor)
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun createSessionFromSso(credentials: Credentials,
|
private suspend fun createSessionFromSso(credentials: Credentials,
|
||||||
homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) {
|
homeServerConnectionConfig: HomeServerConnectionConfig): Session = withContext(coroutineDispatchers.computation) {
|
||||||
sessionCreator.createSession(credentials, homeServerConnectionConfig)
|
sessionCreator.createSession(credentials, homeServerConnectionConfig)
|
||||||
|
|
|
@ -0,0 +1,61 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.auth.wellknown
|
||||||
|
|
||||||
|
import dagger.Lazy
|
||||||
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
|
import im.vector.matrix.android.api.session.Session
|
||||||
|
import im.vector.matrix.android.internal.auth.AuthAPI
|
||||||
|
import im.vector.matrix.android.internal.auth.SessionCreator
|
||||||
|
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||||
|
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.task.Task
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface DirectLoginTask : Task<DirectLoginTask.Params, Session> {
|
||||||
|
data class Params(
|
||||||
|
val homeServerConnectionConfig: HomeServerConnectionConfig,
|
||||||
|
val userId: String,
|
||||||
|
val password: String,
|
||||||
|
val deviceName: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultDirectLoginTask @Inject constructor(
|
||||||
|
@Unauthenticated
|
||||||
|
private val okHttpClient: Lazy<OkHttpClient>,
|
||||||
|
private val retrofitFactory: RetrofitFactory,
|
||||||
|
private val sessionCreator: SessionCreator
|
||||||
|
) : DirectLoginTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: DirectLoginTask.Params): Session {
|
||||||
|
val authAPI = retrofitFactory.create(okHttpClient, params.homeServerConnectionConfig.homeServerUri.toString())
|
||||||
|
.create(AuthAPI::class.java)
|
||||||
|
|
||||||
|
val loginParams = PasswordLoginParams.userIdentifier(params.userId, params.password, params.deviceName)
|
||||||
|
|
||||||
|
val credentials = executeRequest<Credentials>(null) {
|
||||||
|
apiCall = authAPI.login(loginParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
return sessionCreator.createSession(credentials, params.homeServerConnectionConfig)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,199 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.auth.wellknown
|
||||||
|
|
||||||
|
import android.util.MalformedJsonException
|
||||||
|
import dagger.Lazy
|
||||||
|
import im.vector.matrix.android.api.MatrixPatterns
|
||||||
|
import im.vector.matrix.android.api.auth.data.WellKnown
|
||||||
|
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||||
|
import im.vector.matrix.android.api.failure.Failure
|
||||||
|
import im.vector.matrix.android.internal.di.Unauthenticated
|
||||||
|
import im.vector.matrix.android.internal.identity.IdentityPingApi
|
||||||
|
import im.vector.matrix.android.internal.network.RetrofitFactory
|
||||||
|
import im.vector.matrix.android.internal.network.executeRequest
|
||||||
|
import im.vector.matrix.android.internal.session.homeserver.CapabilitiesAPI
|
||||||
|
import im.vector.matrix.android.internal.task.Task
|
||||||
|
import im.vector.matrix.android.internal.util.isValidUrl
|
||||||
|
import okhttp3.OkHttpClient
|
||||||
|
import java.io.EOFException
|
||||||
|
import javax.inject.Inject
|
||||||
|
import javax.net.ssl.HttpsURLConnection
|
||||||
|
|
||||||
|
internal interface GetWellknownTask : Task<GetWellknownTask.Params, WellknownResult> {
|
||||||
|
data class Params(
|
||||||
|
val matrixId: String
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inspired from AutoDiscovery class from legacy Matrix Android SDK
|
||||||
|
*/
|
||||||
|
internal class DefaultGetWellknownTask @Inject constructor(
|
||||||
|
@Unauthenticated
|
||||||
|
private val okHttpClient: Lazy<OkHttpClient>,
|
||||||
|
private val retrofitFactory: RetrofitFactory
|
||||||
|
) : GetWellknownTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: GetWellknownTask.Params): WellknownResult {
|
||||||
|
if (!MatrixPatterns.isUserId(params.matrixId)) {
|
||||||
|
return WellknownResult.InvalidMatrixId
|
||||||
|
}
|
||||||
|
|
||||||
|
val homeServerDomain = params.matrixId.substringAfter(":")
|
||||||
|
|
||||||
|
return findClientConfig(homeServerDomain)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Find client config
|
||||||
|
*
|
||||||
|
* - Do the .well-known request
|
||||||
|
* - validate homeserver url and identity server url if provide in .well-known result
|
||||||
|
* - return action and .well-known data
|
||||||
|
*
|
||||||
|
* @param domain: homeserver domain, deduced from mx userId (ex: "matrix.org" from userId "@user:matrix.org")
|
||||||
|
*/
|
||||||
|
private suspend fun findClientConfig(domain: String): WellknownResult {
|
||||||
|
val wellKnownAPI = retrofitFactory.create(okHttpClient, "https://dummy.org")
|
||||||
|
.create(WellKnownAPI::class.java)
|
||||||
|
|
||||||
|
return try {
|
||||||
|
val wellKnown = executeRequest<WellKnown>(null) {
|
||||||
|
apiCall = wellKnownAPI.getWellKnown(domain)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
val homeServerBaseUrl = wellKnown.homeServer?.baseURL
|
||||||
|
if (homeServerBaseUrl.isNullOrBlank()) {
|
||||||
|
WellknownResult.FailPrompt
|
||||||
|
} else {
|
||||||
|
if (homeServerBaseUrl.isValidUrl()) {
|
||||||
|
// Check that HS is a real one
|
||||||
|
validateHomeServer(homeServerBaseUrl, wellKnown)
|
||||||
|
} else {
|
||||||
|
WellknownResult.FailError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
when (throwable) {
|
||||||
|
is Failure.NetworkConnection -> {
|
||||||
|
WellknownResult.Ignore
|
||||||
|
}
|
||||||
|
is Failure.OtherServerError -> {
|
||||||
|
when (throwable.httpCode) {
|
||||||
|
HttpsURLConnection.HTTP_NOT_FOUND -> WellknownResult.Ignore
|
||||||
|
else -> WellknownResult.FailPrompt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is MalformedJsonException, is EOFException -> {
|
||||||
|
WellknownResult.FailPrompt
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
throw throwable
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if home server is valid, and (if applicable) if identity server is pingable
|
||||||
|
*/
|
||||||
|
private suspend fun validateHomeServer(homeServerBaseUrl: String, wellKnown: WellKnown): WellknownResult {
|
||||||
|
val capabilitiesAPI = retrofitFactory.create(okHttpClient, homeServerBaseUrl)
|
||||||
|
.create(CapabilitiesAPI::class.java)
|
||||||
|
|
||||||
|
try {
|
||||||
|
executeRequest<Unit>(null) {
|
||||||
|
apiCall = capabilitiesAPI.getVersions()
|
||||||
|
}
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
return WellknownResult.FailError
|
||||||
|
}
|
||||||
|
|
||||||
|
return if (wellKnown.identityServer == null) {
|
||||||
|
// No identity server
|
||||||
|
WellknownResult.Prompt(homeServerBaseUrl, null, wellKnown)
|
||||||
|
} else {
|
||||||
|
// if m.identity_server is present it must be valid
|
||||||
|
val identityServerBaseUrl = wellKnown.identityServer.baseURL
|
||||||
|
if (identityServerBaseUrl.isNullOrBlank()) {
|
||||||
|
WellknownResult.FailError
|
||||||
|
} else {
|
||||||
|
if (identityServerBaseUrl.isValidUrl()) {
|
||||||
|
if (validateIdentityServer(identityServerBaseUrl)) {
|
||||||
|
// All is ok
|
||||||
|
WellknownResult.Prompt(homeServerBaseUrl, identityServerBaseUrl, wellKnown)
|
||||||
|
} else {
|
||||||
|
WellknownResult.FailError
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
WellknownResult.FailError
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return true if identity server is pingable
|
||||||
|
*/
|
||||||
|
private suspend fun validateIdentityServer(identityServerBaseUrl: String): Boolean {
|
||||||
|
val identityPingApi = retrofitFactory.create(okHttpClient, identityServerBaseUrl)
|
||||||
|
.create(IdentityPingApi::class.java)
|
||||||
|
|
||||||
|
return try {
|
||||||
|
executeRequest<Unit>(null) {
|
||||||
|
apiCall = identityPingApi.ping()
|
||||||
|
}
|
||||||
|
|
||||||
|
true
|
||||||
|
} catch (throwable: Throwable) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to get an identity server URL from a home server URL, using a .wellknown request
|
||||||
|
*/
|
||||||
|
/*
|
||||||
|
fun getIdentityServer(homeServerUrl: String, callback: ApiCallback<String?>) {
|
||||||
|
if (homeServerUrl.startsWith("https://")) {
|
||||||
|
wellKnownRestClient.getWellKnown(homeServerUrl.substring("https://".length),
|
||||||
|
object : SimpleApiCallback<WellKnown>(callback) {
|
||||||
|
override fun onSuccess(info: WellKnown) {
|
||||||
|
callback.onSuccess(info.identityServer?.baseURL)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
callback.onUnexpectedError(InvalidParameterException("malformed url"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getServerPreferredIntegrationManagers(homeServerUrl: String, callback: ApiCallback<List<WellKnownManagerConfig>>) {
|
||||||
|
if (homeServerUrl.startsWith("https://")) {
|
||||||
|
wellKnownRestClient.getWellKnown(homeServerUrl.substring("https://".length),
|
||||||
|
object : SimpleApiCallback<WellKnown>(callback) {
|
||||||
|
override fun onSuccess(info: WellKnown) {
|
||||||
|
callback.onSuccess(info.getIntegrationManagers())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
callback.onUnexpectedError(InvalidParameterException("malformed url"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.auth.wellknown
|
||||||
|
|
||||||
|
import im.vector.matrix.android.api.auth.data.WellKnown
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.GET
|
||||||
|
import retrofit2.http.Path
|
||||||
|
|
||||||
|
internal interface WellKnownAPI {
|
||||||
|
@GET("https://{domain}/.well-known/matrix/client")
|
||||||
|
fun getWellKnown(@Path("domain") domain: String): Call<WellKnown>
|
||||||
|
}
|
|
@ -0,0 +1,34 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.identity
|
||||||
|
|
||||||
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
|
import retrofit2.Call
|
||||||
|
import retrofit2.http.GET
|
||||||
|
|
||||||
|
internal interface IdentityPingApi {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* https://matrix.org/docs/spec/client_server/r0.4.0.html#server-discovery
|
||||||
|
* Simple ping call to check if server alive
|
||||||
|
*
|
||||||
|
* Ref: https://matrix.org/docs/spec/identity_service/unstable#status-check
|
||||||
|
*
|
||||||
|
* @return 200 in case of success
|
||||||
|
*/
|
||||||
|
@GET(NetworkConstants.URI_API_PREFIX_IDENTITY)
|
||||||
|
fun ping(): Call<Unit>
|
||||||
|
}
|
|
@ -26,4 +26,10 @@ internal object NetworkConstants {
|
||||||
// Media
|
// Media
|
||||||
private const val URI_API_MEDIA_PREFIX_PATH = "_matrix/media"
|
private const val URI_API_MEDIA_PREFIX_PATH = "_matrix/media"
|
||||||
const val URI_API_MEDIA_PREFIX_PATH_R0 = "$URI_API_MEDIA_PREFIX_PATH/r0/"
|
const val URI_API_MEDIA_PREFIX_PATH_R0 = "$URI_API_MEDIA_PREFIX_PATH/r0/"
|
||||||
|
|
||||||
|
// Identity server
|
||||||
|
const val URI_IDENTITY_PATH = "_matrix/identity/api/v1/"
|
||||||
|
const val URI_IDENTITY_PATH_V2 = "_matrix/identity/v2/"
|
||||||
|
|
||||||
|
const val URI_API_PREFIX_IDENTITY = "_matrix/identity/api/v1"
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
|
|
||||||
package im.vector.matrix.android.internal.session.user
|
package im.vector.matrix.android.internal.session.user
|
||||||
|
|
||||||
import im.vector.matrix.android.internal.network.NetworkConstants.URI_API_PREFIX_PATH_R0
|
import im.vector.matrix.android.internal.network.NetworkConstants
|
||||||
import im.vector.matrix.android.internal.session.user.model.SearchUsersParams
|
import im.vector.matrix.android.internal.session.user.model.SearchUsersParams
|
||||||
import im.vector.matrix.android.internal.session.user.model.SearchUsersResponse
|
import im.vector.matrix.android.internal.session.user.model.SearchUsersResponse
|
||||||
import retrofit2.Call
|
import retrofit2.Call
|
||||||
|
@ -30,6 +30,6 @@ internal interface SearchUserAPI {
|
||||||
*
|
*
|
||||||
* @param searchUsersParams the search params.
|
* @param searchUsersParams the search params.
|
||||||
*/
|
*/
|
||||||
@POST(URI_API_PREFIX_PATH_R0 + "user_directory/search")
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "user_directory/search")
|
||||||
fun searchUsers(@Body searchUsersParams: SearchUsersParams): Call<SearchUsersResponse>
|
fun searchUsers(@Body searchUsersParams: SearchUsersParams): Call<SearchUsersResponse>
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,28 @@
|
||||||
|
/*
|
||||||
|
* 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.matrix.android.internal.util
|
||||||
|
|
||||||
|
import java.net.URL
|
||||||
|
|
||||||
|
internal fun String.isValidUrl(): Boolean {
|
||||||
|
return try {
|
||||||
|
URL(this)
|
||||||
|
true
|
||||||
|
} catch (t: Throwable) {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
|
@ -38,6 +38,7 @@ import im.vector.riotx.core.di.ScreenComponent
|
||||||
import im.vector.riotx.core.extensions.POP_BACK_STACK_EXCLUSIVE
|
import im.vector.riotx.core.extensions.POP_BACK_STACK_EXCLUSIVE
|
||||||
import im.vector.riotx.core.extensions.addFragment
|
import im.vector.riotx.core.extensions.addFragment
|
||||||
import im.vector.riotx.core.extensions.addFragmentToBackstack
|
import im.vector.riotx.core.extensions.addFragmentToBackstack
|
||||||
|
import im.vector.riotx.core.extensions.exhaustive
|
||||||
import im.vector.riotx.core.platform.ToolbarConfigurable
|
import im.vector.riotx.core.platform.ToolbarConfigurable
|
||||||
import im.vector.riotx.core.platform.VectorBaseActivity
|
import im.vector.riotx.core.platform.VectorBaseActivity
|
||||||
import im.vector.riotx.features.home.HomeActivity
|
import im.vector.riotx.features.home.HomeActivity
|
||||||
|
@ -125,9 +126,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLoginNavigation(loginNavigation: LoginNavigation) {
|
private fun handleLoginNavigation(loginNavigation: LoginNavigation) {
|
||||||
// Assigning to dummy make sure we do not forget a case
|
when (loginNavigation) {
|
||||||
@Suppress("UNUSED_VARIABLE")
|
|
||||||
val dummy = when (loginNavigation) {
|
|
||||||
is LoginNavigation.OpenServerSelection ->
|
is LoginNavigation.OpenServerSelection ->
|
||||||
addFragmentToBackstack(R.id.loginFragmentContainer,
|
addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
LoginServerSelectionFragment::class.java,
|
LoginServerSelectionFragment::class.java,
|
||||||
|
@ -177,7 +176,7 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginNavigation.msisdn),
|
LoginGenericTextInputFormFragmentArgument(TextInputFormFragmentMode.ConfirmMsisdn, true, loginNavigation.msisdn),
|
||||||
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
tag = FRAGMENT_REGISTRATION_STAGE_TAG,
|
||||||
option = commonOption)
|
option = commonOption)
|
||||||
}
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) {
|
private fun handleLoginViewEvents(loginViewEvents: LoginViewEvents) {
|
||||||
|
@ -254,11 +253,11 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
|
|
||||||
private fun onSignModeSelected() = withState(loginViewModel) { state ->
|
private fun onSignModeSelected() = withState(loginViewModel) { state ->
|
||||||
when (state.signMode) {
|
when (state.signMode) {
|
||||||
SignMode.Unknown -> error("Sign mode has to be set before calling this method")
|
SignMode.Unknown -> error("Sign mode has to be set before calling this method")
|
||||||
SignMode.SignUp -> {
|
SignMode.SignUp -> {
|
||||||
// This is managed by the LoginViewEvents
|
// This is managed by the LoginViewEvents
|
||||||
}
|
}
|
||||||
SignMode.SignIn -> {
|
SignMode.SignIn -> {
|
||||||
// It depends on the LoginMode
|
// It depends on the LoginMode
|
||||||
when (state.loginMode) {
|
when (state.loginMode) {
|
||||||
LoginMode.Unknown -> error("Developer error")
|
LoginMode.Unknown -> error("Developer error")
|
||||||
|
@ -272,7 +271,11 @@ open class LoginActivity : VectorBaseActivity(), ToolbarConfigurable {
|
||||||
LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes)
|
LoginMode.Unsupported -> onLoginModeNotSupported(state.loginModeSupportedTypes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
SignMode.SignInWithMatrixId -> addFragmentToBackstack(R.id.loginFragmentContainer,
|
||||||
|
LoginFragment::class.java,
|
||||||
|
tag = FRAGMENT_LOGIN_TAG,
|
||||||
|
option = commonOption)
|
||||||
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun onRegistrationStageNotSupported() {
|
private fun onRegistrationStageNotSupported() {
|
||||||
|
|
|
@ -31,6 +31,7 @@ import im.vector.matrix.android.api.failure.Failure
|
||||||
import im.vector.matrix.android.api.failure.MatrixError
|
import im.vector.matrix.android.api.failure.MatrixError
|
||||||
import im.vector.matrix.android.api.failure.isInvalidPassword
|
import im.vector.matrix.android.api.failure.isInvalidPassword
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
|
import im.vector.riotx.core.extensions.exhaustive
|
||||||
import im.vector.riotx.core.extensions.hideKeyboard
|
import im.vector.riotx.core.extensions.hideKeyboard
|
||||||
import im.vector.riotx.core.extensions.showPassword
|
import im.vector.riotx.core.extensions.showPassword
|
||||||
import im.vector.riotx.core.extensions.toReducedUrl
|
import im.vector.riotx.core.extensions.toReducedUrl
|
||||||
|
@ -73,16 +74,17 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||||
private fun setupAutoFill(state: LoginViewState) {
|
private fun setupAutoFill(state: LoginViewState) {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
when (state.signMode) {
|
when (state.signMode) {
|
||||||
SignMode.Unknown -> error("developer error")
|
SignMode.Unknown -> error("developer error")
|
||||||
SignMode.SignUp -> {
|
SignMode.SignUp -> {
|
||||||
loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME)
|
loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_USERNAME)
|
||||||
passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
|
passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_NEW_PASSWORD)
|
||||||
}
|
}
|
||||||
SignMode.SignIn -> {
|
SignMode.SignIn,
|
||||||
|
SignMode.SignInWithMatrixId -> {
|
||||||
loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME)
|
loginField.setAutofillHints(HintConstants.AUTOFILL_HINT_USERNAME)
|
||||||
passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD)
|
passwordField.setAutofillHints(HintConstants.AUTOFILL_HINT_PASSWORD)
|
||||||
}
|
}
|
||||||
}
|
}.exhaustive
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -116,35 +118,44 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setupUi(state: LoginViewState) {
|
private fun setupUi(state: LoginViewState) {
|
||||||
val resId = when (state.signMode) {
|
|
||||||
SignMode.Unknown -> error("developer error")
|
|
||||||
SignMode.SignUp -> R.string.login_signup_to
|
|
||||||
SignMode.SignIn -> R.string.login_connect_to
|
|
||||||
}
|
|
||||||
|
|
||||||
loginFieldTil.hint = getString(when (state.signMode) {
|
loginFieldTil.hint = getString(when (state.signMode) {
|
||||||
SignMode.Unknown -> error("developer error")
|
SignMode.Unknown -> error("developer error")
|
||||||
SignMode.SignUp -> R.string.login_signup_username_hint
|
SignMode.SignUp -> R.string.login_signup_username_hint
|
||||||
SignMode.SignIn -> R.string.login_signin_username_hint
|
SignMode.SignIn -> R.string.login_signin_username_hint
|
||||||
|
SignMode.SignInWithMatrixId -> R.string.login_signin_matrix_id_hint
|
||||||
})
|
})
|
||||||
|
|
||||||
when (state.serverType) {
|
// Handle direct signin first
|
||||||
ServerType.MatrixOrg -> {
|
if (state.signMode == SignMode.SignInWithMatrixId) {
|
||||||
loginServerIcon.isVisible = true
|
loginServerIcon.isVisible = false
|
||||||
loginServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
|
loginTitle.text = getString(R.string.login_signin_matrix_id_title)
|
||||||
loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
|
loginNotice.text = getString(R.string.login_signin_matrix_id_notice)
|
||||||
loginNotice.text = getString(R.string.login_server_matrix_org_text)
|
} else {
|
||||||
|
val resId = when (state.signMode) {
|
||||||
|
SignMode.Unknown -> error("developer error")
|
||||||
|
SignMode.SignUp -> R.string.login_signup_to
|
||||||
|
SignMode.SignIn -> R.string.login_connect_to
|
||||||
|
SignMode.SignInWithMatrixId -> R.string.login_connect_to
|
||||||
}
|
}
|
||||||
ServerType.Modular -> {
|
|
||||||
loginServerIcon.isVisible = true
|
when (state.serverType) {
|
||||||
loginServerIcon.setImageResource(R.drawable.ic_logo_modular)
|
ServerType.MatrixOrg -> {
|
||||||
loginTitle.text = getString(resId, "Modular")
|
loginServerIcon.isVisible = true
|
||||||
loginNotice.text = getString(R.string.login_server_modular_text)
|
loginServerIcon.setImageResource(R.drawable.ic_logo_matrix_org)
|
||||||
}
|
loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
|
||||||
ServerType.Other -> {
|
loginNotice.text = getString(R.string.login_server_matrix_org_text)
|
||||||
loginServerIcon.isVisible = false
|
}
|
||||||
loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
|
ServerType.Modular -> {
|
||||||
loginNotice.text = getString(R.string.login_server_other_text)
|
loginServerIcon.isVisible = true
|
||||||
|
loginServerIcon.setImageResource(R.drawable.ic_logo_modular)
|
||||||
|
loginTitle.text = getString(resId, "Modular")
|
||||||
|
loginNotice.text = getString(R.string.login_server_modular_text)
|
||||||
|
}
|
||||||
|
ServerType.Other -> {
|
||||||
|
loginServerIcon.isVisible = false
|
||||||
|
loginTitle.text = getString(resId, state.homeServerUrl.toReducedUrl())
|
||||||
|
loginNotice.text = getString(R.string.login_server_other_text)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,9 +164,10 @@ class LoginFragment @Inject constructor() : AbstractLoginFragment() {
|
||||||
forgetPasswordButton.isVisible = state.signMode == SignMode.SignIn
|
forgetPasswordButton.isVisible = state.signMode == SignMode.SignIn
|
||||||
|
|
||||||
loginSubmit.text = getString(when (state.signMode) {
|
loginSubmit.text = getString(when (state.signMode) {
|
||||||
SignMode.Unknown -> error("developer error")
|
SignMode.Unknown -> error("developer error")
|
||||||
SignMode.SignUp -> R.string.login_signup_submit
|
SignMode.SignUp -> R.string.login_signup_submit
|
||||||
SignMode.SignIn -> R.string.login_signin
|
SignMode.SignIn,
|
||||||
|
SignMode.SignInWithMatrixId -> R.string.login_signin
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -99,6 +99,12 @@ class LoginServerSelectionFragment @Inject constructor() : AbstractLoginFragment
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OnClick(R.id.loginServerIKnowMyIdSubmit)
|
||||||
|
fun loginWithMatrixId() {
|
||||||
|
loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignInWithMatrixId))
|
||||||
|
loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected)
|
||||||
|
}
|
||||||
|
|
||||||
override fun resetViewModel() {
|
override fun resetViewModel() {
|
||||||
loginViewModel.handle(LoginAction.ResetHomeServerType)
|
loginViewModel.handle(LoginAction.ResetHomeServerType)
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
package im.vector.riotx.features.login
|
package im.vector.riotx.features.login
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
import android.net.Uri
|
||||||
import androidx.fragment.app.FragmentActivity
|
import androidx.fragment.app.FragmentActivity
|
||||||
import com.airbnb.mvrx.ActivityViewModelContext
|
import com.airbnb.mvrx.ActivityViewModelContext
|
||||||
import com.airbnb.mvrx.Fail
|
import com.airbnb.mvrx.Fail
|
||||||
|
@ -29,19 +30,24 @@ import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import im.vector.matrix.android.api.MatrixCallback
|
import im.vector.matrix.android.api.MatrixCallback
|
||||||
import im.vector.matrix.android.api.auth.AuthenticationService
|
import im.vector.matrix.android.api.auth.AuthenticationService
|
||||||
|
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
|
||||||
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
import im.vector.matrix.android.api.auth.data.LoginFlowResult
|
||||||
import im.vector.matrix.android.api.auth.login.LoginWizard
|
import im.vector.matrix.android.api.auth.login.LoginWizard
|
||||||
import im.vector.matrix.android.api.auth.registration.FlowResult
|
import im.vector.matrix.android.api.auth.registration.FlowResult
|
||||||
import im.vector.matrix.android.api.auth.registration.RegistrationResult
|
import im.vector.matrix.android.api.auth.registration.RegistrationResult
|
||||||
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
import im.vector.matrix.android.api.auth.registration.RegistrationWizard
|
||||||
import im.vector.matrix.android.api.auth.registration.Stage
|
import im.vector.matrix.android.api.auth.registration.Stage
|
||||||
|
import im.vector.matrix.android.api.auth.wellknown.WellknownResult
|
||||||
import im.vector.matrix.android.api.session.Session
|
import im.vector.matrix.android.api.session.Session
|
||||||
import im.vector.matrix.android.api.util.Cancelable
|
import im.vector.matrix.android.api.util.Cancelable
|
||||||
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
|
||||||
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
import im.vector.matrix.android.internal.crypto.model.rest.UserPasswordAuth
|
||||||
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.di.ActiveSessionHolder
|
import im.vector.riotx.core.di.ActiveSessionHolder
|
||||||
import im.vector.riotx.core.extensions.configureAndStart
|
import im.vector.riotx.core.extensions.configureAndStart
|
||||||
|
import im.vector.riotx.core.extensions.exhaustive
|
||||||
import im.vector.riotx.core.platform.VectorViewModel
|
import im.vector.riotx.core.platform.VectorViewModel
|
||||||
|
import im.vector.riotx.core.resources.StringProvider
|
||||||
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
import im.vector.riotx.features.notifications.PushRuleTriggerListener
|
||||||
import im.vector.riotx.features.session.SessionListener
|
import im.vector.riotx.features.session.SessionListener
|
||||||
import im.vector.riotx.features.signout.soft.SoftLogoutActivity
|
import im.vector.riotx.features.signout.soft.SoftLogoutActivity
|
||||||
|
@ -51,14 +57,16 @@ import java.util.concurrent.CancellationException
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginViewState,
|
class LoginViewModel @AssistedInject constructor(
|
||||||
private val applicationContext: Context,
|
@Assisted initialState: LoginViewState,
|
||||||
private val authenticationService: AuthenticationService,
|
private val applicationContext: Context,
|
||||||
private val activeSessionHolder: ActiveSessionHolder,
|
private val authenticationService: AuthenticationService,
|
||||||
private val pushRuleTriggerListener: PushRuleTriggerListener,
|
private val activeSessionHolder: ActiveSessionHolder,
|
||||||
private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory,
|
private val pushRuleTriggerListener: PushRuleTriggerListener,
|
||||||
private val sessionListener: SessionListener,
|
private val homeServerConnectionConfigFactory: HomeServerConnectionConfigFactory,
|
||||||
private val reAuthHelper: ReAuthHelper)
|
private val sessionListener: SessionListener,
|
||||||
|
private val reAuthHelper: ReAuthHelper,
|
||||||
|
private val stringProvider: StringProvider)
|
||||||
: VectorViewModel<LoginViewState, LoginAction, LoginViewEvents>(initialState) {
|
: VectorViewModel<LoginViewState, LoginAction, LoginViewEvents>(initialState) {
|
||||||
|
|
||||||
@AssistedInject.Factory
|
@AssistedInject.Factory
|
||||||
|
@ -421,10 +429,73 @@ class LoginViewModel @AssistedInject constructor(@Assisted initialState: LoginVi
|
||||||
|
|
||||||
private fun handleLoginOrRegister(action: LoginAction.LoginOrRegister) = withState { state ->
|
private fun handleLoginOrRegister(action: LoginAction.LoginOrRegister) = withState { state ->
|
||||||
when (state.signMode) {
|
when (state.signMode) {
|
||||||
SignMode.SignIn -> handleLogin(action)
|
SignMode.Unknown -> error("Developer error, invalid sign mode")
|
||||||
SignMode.SignUp -> handleRegisterWith(action)
|
SignMode.SignIn -> handleLogin(action)
|
||||||
else -> error("Developer error, invalid sign mode")
|
SignMode.SignUp -> handleRegisterWith(action)
|
||||||
|
SignMode.SignInWithMatrixId -> handleDirectLogin(action)
|
||||||
|
}.exhaustive
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun handleDirectLogin(action: LoginAction.LoginOrRegister) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Loading()
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
authenticationService.getWellKnownData(action.username, object : MatrixCallback<WellknownResult> {
|
||||||
|
override fun onSuccess(data: WellknownResult) {
|
||||||
|
when (data) {
|
||||||
|
is WellknownResult.Prompt ->
|
||||||
|
onWellknownSuccess(action, data)
|
||||||
|
is WellknownResult.InvalidMatrixId -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Uninitialized
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.login_signin_matrix_id_error_invalid_matrix_id))))
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Uninitialized
|
||||||
|
)
|
||||||
|
}
|
||||||
|
_viewEvents.post(LoginViewEvents.Failure(Exception(stringProvider.getString(R.string.autodiscover_well_known_error))))
|
||||||
|
}
|
||||||
|
}.exhaustive
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun onWellknownSuccess(action: LoginAction.LoginOrRegister, wellKnownPrompt: WellknownResult.Prompt) {
|
||||||
|
val homeServerConnectionConfig = HomeServerConnectionConfig(
|
||||||
|
homeServerUri = Uri.parse(wellKnownPrompt.homerServerUrl),
|
||||||
|
identityServerUri = wellKnownPrompt.identityServerUrl?.let { Uri.parse(it) }
|
||||||
|
)
|
||||||
|
|
||||||
|
authenticationService.directAuthentication(homeServerConnectionConfig, action.username, action.password, action.initialDeviceName, object : MatrixCallback<Session> {
|
||||||
|
override fun onSuccess(data: Session) {
|
||||||
|
onSessionCreated(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleLogin(action: LoginAction.LoginOrRegister) {
|
private fun handleLogin(action: LoginAction.LoginOrRegister) {
|
||||||
|
|
|
@ -21,5 +21,7 @@ enum class SignMode {
|
||||||
// Account creation
|
// Account creation
|
||||||
SignUp,
|
SignUp,
|
||||||
// Login
|
// Login
|
||||||
SignIn
|
SignIn,
|
||||||
|
// Login directly with matrix Id
|
||||||
|
SignInWithMatrixId
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,11 +184,34 @@
|
||||||
android:layout_marginTop="24dp"
|
android:layout_marginTop="24dp"
|
||||||
android:text="@string/login_continue"
|
android:text="@string/login_continue"
|
||||||
android:transitionName="loginSubmitTransition"
|
android:transitionName="loginSubmitTransition"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toTopOf="@+id/loginServerIKnowMyIdNotice"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/loginServerChoiceOther" />
|
app:layout_constraintTop_toBottomOf="@+id/loginServerChoiceOther" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/loginServerIKnowMyIdNotice"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="24dp"
|
||||||
|
android:gravity="start"
|
||||||
|
android:text="@string/login_connect_using_matrix_id_notice"
|
||||||
|
android:textAppearance="@style/TextAppearance.Vector.Login.Text.Small"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/loginServerSubmit" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/loginServerIKnowMyIdSubmit"
|
||||||
|
style="@style/Style.Vector.Login.Button.Text"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="@string/login_connect_using_matrix_id_submit"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/loginServerIKnowMyIdNotice" />
|
||||||
|
|
||||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
|
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
|
|
@ -57,4 +57,12 @@
|
||||||
|
|
||||||
<!-- END Strings added by Others -->
|
<!-- END Strings added by Others -->
|
||||||
|
|
||||||
|
<string name="login_connect_using_matrix_id_notice">Alternatively, if you already have an account and you know your Matrix identifier and your password, you can use this method:</string>
|
||||||
|
<string name="login_connect_using_matrix_id_submit">Sign in with my Matrix identifier</string>
|
||||||
|
<string name="login_signin_matrix_id_title">Sign in</string>
|
||||||
|
<string name="login_signin_matrix_id_notice">Enter your identifier and your password</string>
|
||||||
|
<string name="login_signin_matrix_id_hint">User identifier</string>
|
||||||
|
<string name="login_signin_matrix_id_error_invalid_matrix_id">This is not a valid user identifier. Expected format: \'@user:homeserver.org\'</string>
|
||||||
|
<string name="autodiscover_well_known_error">Unable to find a valid homeserver. Please check your identifier</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
|
Loading…
Reference in New Issue