Correctly handle SSO login redirection
This commit is contained in:
parent
2e244dd448
commit
ae7a52cecf
|
@ -9,6 +9,7 @@ Improvements 🙌:
|
||||||
- New wording for notice when current user is the sender
|
- New wording for notice when current user is the sender
|
||||||
- Hide "X made no changes" event by default in timeline (#1430)
|
- Hide "X made no changes" event by default in timeline (#1430)
|
||||||
- Hide left rooms in breadcrumbs (#766)
|
- Hide left rooms in breadcrumbs (#766)
|
||||||
|
- Correctly handle SSO login redirection
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
- Switch theme is not fully taken into account without restarting the app
|
- Switch theme is not fully taken into account without restarting the app
|
||||||
|
|
|
@ -34,6 +34,12 @@ interface LoginWizard {
|
||||||
deviceName: String,
|
deviceName: String,
|
||||||
callback: MatrixCallback<Session>): Cancelable
|
callback: MatrixCallback<Session>): Cancelable
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Exchange a login token to an access token
|
||||||
|
*/
|
||||||
|
fun loginWithToken(loginToken: String,
|
||||||
|
callback: MatrixCallback<Session>): Cancelable
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reset user password
|
* Reset user password
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -21,6 +21,7 @@ import im.vector.matrix.android.api.auth.data.Versions
|
||||||
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
import im.vector.matrix.android.internal.auth.data.LoginFlowResponse
|
||||||
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||||
import im.vector.matrix.android.internal.auth.data.RiotConfig
|
import im.vector.matrix.android.internal.auth.data.RiotConfig
|
||||||
|
import im.vector.matrix.android.internal.auth.data.TokenLoginParams
|
||||||
import im.vector.matrix.android.internal.auth.login.ResetPasswordMailConfirmed
|
import im.vector.matrix.android.internal.auth.login.ResetPasswordMailConfirmed
|
||||||
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
|
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
|
||||||
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
|
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
|
||||||
|
@ -91,6 +92,11 @@ internal interface AuthAPI {
|
||||||
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
|
||||||
fun login(@Body loginParams: PasswordLoginParams): Call<Credentials>
|
fun login(@Body loginParams: PasswordLoginParams): Call<Credentials>
|
||||||
|
|
||||||
|
// Unfortunately we cannot use interface for @Body parameter, so I duplicate the method for the type TokenLoginParams
|
||||||
|
@Headers("CONNECT_TIMEOUT:60000", "READ_TIMEOUT:60000", "WRITE_TIMEOUT:60000")
|
||||||
|
@POST(NetworkConstants.URI_API_PREFIX_PATH_R0 + "login")
|
||||||
|
fun login(@Body loginParams: TokenLoginParams): Call<Credentials>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Ask the homeserver to reset the password associated with the provided email.
|
* Ask the homeserver to reset the password associated with the provided email.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
/*
|
||||||
|
* 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.data
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class TokenLoginParams(
|
||||||
|
@Json(name = "type") override val type: String = LoginFlowTypes.TOKEN,
|
||||||
|
@Json(name = "token") val token: String,
|
||||||
|
// client generated nonce
|
||||||
|
@Json(name = "txn_id") val txId: String = UUID.randomUUID().toString()
|
||||||
|
// Param session is not useful in this case?
|
||||||
|
) : LoginParams
|
|
@ -30,6 +30,7 @@ import im.vector.matrix.android.internal.auth.PendingSessionStore
|
||||||
import im.vector.matrix.android.internal.auth.SessionCreator
|
import im.vector.matrix.android.internal.auth.SessionCreator
|
||||||
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
|
||||||
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
|
||||||
|
import im.vector.matrix.android.internal.auth.data.TokenLoginParams
|
||||||
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.registration.AddThreePidRegistrationParams
|
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationParams
|
||||||
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
|
import im.vector.matrix.android.internal.auth.registration.AddThreePidRegistrationResponse
|
||||||
|
@ -65,6 +66,22 @@ internal class DefaultLoginWizard(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Ref: https://matrix.org/docs/spec/client_server/latest#handling-the-authentication-endpoint
|
||||||
|
*/
|
||||||
|
override fun loginWithToken(loginToken: String, callback: MatrixCallback<Session>): Cancelable {
|
||||||
|
return coroutineScope.launchToCallback(coroutineDispatchers.main, callback) {
|
||||||
|
val loginParams = TokenLoginParams(
|
||||||
|
token = loginToken
|
||||||
|
)
|
||||||
|
val credentials = executeRequest<Credentials>(null) {
|
||||||
|
apiCall = authAPI.login(loginParams)
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionCreator.createSession(credentials, pendingSessionData.homeServerConnectionConfig)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun loginInternal(login: String,
|
private suspend fun loginInternal(login: String,
|
||||||
password: String,
|
password: String,
|
||||||
deviceName: String) = withContext(coroutineDispatchers.computation) {
|
deviceName: String) = withContext(coroutineDispatchers.computation) {
|
||||||
|
|
|
@ -24,6 +24,7 @@ sealed class LoginAction : VectorViewModelAction {
|
||||||
data class UpdateServerType(val serverType: ServerType) : LoginAction()
|
data class UpdateServerType(val serverType: ServerType) : LoginAction()
|
||||||
data class UpdateHomeServer(val homeServerUrl: String) : LoginAction()
|
data class UpdateHomeServer(val homeServerUrl: String) : LoginAction()
|
||||||
data class UpdateSignMode(val signMode: SignMode) : LoginAction()
|
data class UpdateSignMode(val signMode: SignMode) : LoginAction()
|
||||||
|
data class LoginWithToken(val loginToken: String) : LoginAction()
|
||||||
data class WebLoginSuccess(val credentials: Credentials) : LoginAction()
|
data class WebLoginSuccess(val credentials: Credentials) : LoginAction()
|
||||||
data class InitWith(val loginConfig: LoginConfig) : LoginAction()
|
data class InitWith(val loginConfig: LoginConfig) : LoginAction()
|
||||||
data class ResetPassword(val email: String, val newPassword: String) : LoginAction()
|
data class ResetPassword(val email: String, val newPassword: String) : LoginAction()
|
||||||
|
|
|
@ -110,6 +110,7 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
is LoginAction.InitWith -> handleInitWith(action)
|
is LoginAction.InitWith -> handleInitWith(action)
|
||||||
is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action)
|
is LoginAction.UpdateHomeServer -> handleUpdateHomeserver(action)
|
||||||
is LoginAction.LoginOrRegister -> handleLoginOrRegister(action)
|
is LoginAction.LoginOrRegister -> handleLoginOrRegister(action)
|
||||||
|
is LoginAction.LoginWithToken -> handleLoginWithToken(action)
|
||||||
is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
is LoginAction.WebLoginSuccess -> handleWebLoginSuccess(action)
|
||||||
is LoginAction.ResetPassword -> handleResetPassword(action)
|
is LoginAction.ResetPassword -> handleResetPassword(action)
|
||||||
is LoginAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed()
|
is LoginAction.ResetPasswordMailConfirmed -> handleResetPasswordMailConfirmed()
|
||||||
|
@ -120,6 +121,41 @@ class LoginViewModel @AssistedInject constructor(
|
||||||
}.exhaustive
|
}.exhaustive
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleLoginWithToken(action: LoginAction.LoginWithToken) {
|
||||||
|
val safeLoginWizard = loginWizard
|
||||||
|
|
||||||
|
if (safeLoginWizard == null) {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Fail(Throwable("Bad configuration"))
|
||||||
|
)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Loading()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTask = safeLoginWizard.loginWithToken(
|
||||||
|
action.loginToken,
|
||||||
|
object : MatrixCallback<Session> {
|
||||||
|
override fun onSuccess(data: Session) {
|
||||||
|
onSessionCreated(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(failure: Throwable) {
|
||||||
|
_viewEvents.post(LoginViewEvents.Failure(failure))
|
||||||
|
setState {
|
||||||
|
copy(
|
||||||
|
asyncLoginAction = Fail(failure)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun handleSetupSsoForSessionRecovery(action: LoginAction.SetupSsoForSessionRecovery) {
|
private fun handleSetupSsoForSessionRecovery(action: LoginAction.SetupSsoForSessionRecovery) {
|
||||||
setState {
|
setState {
|
||||||
copy(
|
copy(
|
||||||
|
|
|
@ -21,6 +21,7 @@ package im.vector.riotx.features.login
|
||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.content.DialogInterface
|
import android.content.DialogInterface
|
||||||
import android.graphics.Bitmap
|
import android.graphics.Bitmap
|
||||||
|
import android.net.Uri
|
||||||
import android.net.http.SslError
|
import android.net.http.SslError
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
@ -36,6 +37,7 @@ import im.vector.matrix.android.api.auth.REGISTER_FALLBACK_PATH
|
||||||
import im.vector.matrix.android.api.auth.SSO_FALLBACK_PATH
|
import im.vector.matrix.android.api.auth.SSO_FALLBACK_PATH
|
||||||
import im.vector.matrix.android.api.auth.SSO_REDIRECT_URL_PARAM
|
import im.vector.matrix.android.api.auth.SSO_REDIRECT_URL_PARAM
|
||||||
import im.vector.matrix.android.api.auth.data.Credentials
|
import im.vector.matrix.android.api.auth.data.Credentials
|
||||||
|
import im.vector.matrix.android.api.extensions.tryThis
|
||||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||||
import im.vector.riotx.R
|
import im.vector.riotx.R
|
||||||
import im.vector.riotx.core.extensions.appendParamToUrl
|
import im.vector.riotx.core.extensions.appendParamToUrl
|
||||||
|
@ -55,6 +57,11 @@ class LoginWebFragment @Inject constructor(
|
||||||
private val assetReader: AssetReader
|
private val assetReader: AssetReader
|
||||||
) : AbstractLoginFragment() {
|
) : AbstractLoginFragment() {
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
// Note that the domain can be displayed to the user for confirmation that he trusts it. So use a human readable string
|
||||||
|
private const val REDIRECT_URL = "riotx://riotx"
|
||||||
|
}
|
||||||
|
|
||||||
override fun getLayoutResId() = R.layout.fragment_login_web
|
override fun getLayoutResId() = R.layout.fragment_login_web
|
||||||
|
|
||||||
private var isWebViewLoaded = false
|
private var isWebViewLoaded = false
|
||||||
|
@ -130,12 +137,8 @@ class LoginWebFragment @Inject constructor(
|
||||||
if (state.signMode == SignMode.SignIn) {
|
if (state.signMode == SignMode.SignIn) {
|
||||||
if (state.loginMode == LoginMode.Sso) {
|
if (state.loginMode == LoginMode.Sso) {
|
||||||
append(SSO_FALLBACK_PATH)
|
append(SSO_FALLBACK_PATH)
|
||||||
// We do not want to deal with the result, so let the fallback login page to handle it for us
|
// Set a redirect url we will intercept later
|
||||||
appendParamToUrl(SSO_REDIRECT_URL_PARAM,
|
appendParamToUrl(SSO_REDIRECT_URL_PARAM, REDIRECT_URL)
|
||||||
buildString {
|
|
||||||
append(state.homeServerUrl?.trim { it == '/' })
|
|
||||||
append(LOGIN_FALLBACK_PATH)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
append(LOGIN_FALLBACK_PATH)
|
append(LOGIN_FALLBACK_PATH)
|
||||||
}
|
}
|
||||||
|
@ -226,7 +229,9 @@ class LoginWebFragment @Inject constructor(
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean {
|
override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean {
|
||||||
if (null != url && url.startsWith("js:")) {
|
if (url == null) return super.shouldOverrideUrlLoading(view, url as String?)
|
||||||
|
|
||||||
|
if (url.startsWith("js:")) {
|
||||||
var json = url.substring(3)
|
var json = url.substring(3)
|
||||||
var javascriptResponse: JavascriptResponse? = null
|
var javascriptResponse: JavascriptResponse? = null
|
||||||
|
|
||||||
|
@ -256,6 +261,8 @@ class LoginWebFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
} else if (url.startsWith(REDIRECT_URL)) {
|
||||||
|
return handleSsoLoginSuccess(url)
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.shouldOverrideUrlLoading(view, url)
|
return super.shouldOverrideUrlLoading(view, url)
|
||||||
|
@ -263,6 +270,14 @@ class LoginWebFragment @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun handleSsoLoginSuccess(url: String): Boolean {
|
||||||
|
val uri = Uri.parse(url)
|
||||||
|
val loginToken = tryThis { uri.getQueryParameter("loginToken") } ?: return false
|
||||||
|
|
||||||
|
loginViewModel.handle(LoginAction.LoginWithToken(loginToken))
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
private fun notifyViewModel(credentials: Credentials) {
|
private fun notifyViewModel(credentials: Credentials) {
|
||||||
if (isForSessionRecovery) {
|
if (isForSessionRecovery) {
|
||||||
val softLogoutViewModel: SoftLogoutViewModel by activityViewModel()
|
val softLogoutViewModel: SoftLogoutViewModel by activityViewModel()
|
||||||
|
|
Loading…
Reference in New Issue