Let the Matrix SDK compute the SSO url

This commit is contained in:
Benoit Marty 2020-12-21 11:18:25 +01:00
parent 4a5dbde8d3
commit 13938f2ab3
12 changed files with 103 additions and 56 deletions

View file

@ -41,6 +41,11 @@ interface AuthenticationService {
*/
fun getLoginFlowOfSession(sessionId: String, callback: MatrixCallback<LoginFlowResult>): Cancelable
/**
* Get a SSO url
*/
fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String?
/**
* Return a LoginWizard, to login to the homeserver. The login flow has to be retrieved first.
*/

View file

@ -32,7 +32,7 @@ const val REGISTER_FALLBACK_PATH = "/_matrix/static/client/register/"
* Path to use when the client want to connect using SSO
* Ref: https://matrix.org/docs/spec/client_server/latest#sso-client-login
*/
const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect"
internal const val SSO_REDIRECT_PATH = "/_matrix/client/r0/login/sso/redirect"
internal const val MSC2858_SSO_REDIRECT_PATH = "/_matrix/client/unstable/org.matrix.msc2858/login/sso/redirect"
const val SSO_REDIRECT_URL_PARAM = "redirectUrl"
internal const val SSO_REDIRECT_URL_PARAM = "redirectUrl"

View file

@ -0,0 +1,37 @@
/*
* 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 org.matrix.android.sdk.api.util
import java.net.URLEncoder
/**
* Append param and value to a Url, using "?" or "&". Value parameter will be encoded
* Return this for chaining purpose
*/
fun StringBuilder.appendParamToUrl(param: String, value: String): StringBuilder {
if (contains("?")) {
append("&")
} else {
append("?")
}
append(param)
append("=")
append(URLEncoder.encode(value, "utf-8"))
return this
}

View file

@ -23,6 +23,9 @@ import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.auth.AuthenticationService
import org.matrix.android.sdk.api.auth.MSC2858_SSO_REDIRECT_PATH
import org.matrix.android.sdk.api.auth.SSO_REDIRECT_PATH
import org.matrix.android.sdk.api.auth.SSO_REDIRECT_URL_PARAM
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.auth.data.HomeServerConnectionConfig
import org.matrix.android.sdk.api.auth.data.LoginFlowResult
@ -34,6 +37,7 @@ import org.matrix.android.sdk.api.failure.Failure
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.NoOpCancellable
import org.matrix.android.sdk.api.util.appendParamToUrl
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.auth.data.LoginFlowResponse
import org.matrix.android.sdk.internal.auth.data.RiotConfig
@ -99,6 +103,28 @@ internal class DefaultAuthenticationService @Inject constructor(
}
}
override fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
return pendingSessionData?.let { safePendingSessionData ->
val homeServerUrl = safePendingSessionData.homeServerConnectionConfig.homeServerUri.toString()
buildString {
append(homeServerUrl.trim { it == '/' })
if (providerId != null) {
append(MSC2858_SSO_REDIRECT_PATH)
append("/$providerId")
} else {
append(SSO_REDIRECT_PATH)
}
// Set a redirect url we will intercept later
appendParamToUrl(SSO_REDIRECT_URL_PARAM, redirectUrl)
deviceId?.takeIf { it.isNotBlank() }?.let {
// But https://github.com/matrix-org/synapse/issues/5755
appendParamToUrl("device_id", it)
}
}
}
}
override fun getLoginFlow(homeServerConnectionConfig: HomeServerConnectionConfig, callback: MatrixCallback<LoginFlowResult>): Cancelable {
pendingSessionData = null

View file

@ -16,26 +16,6 @@
package im.vector.app.core.extensions
import java.net.URLEncoder
/**
* Append param and value to a Url, using "?" or "&". Value parameter will be encoded
* Return this for chaining purpose
*/
fun StringBuilder.appendParamToUrl(param: String, value: String): StringBuilder {
if (contains("?")) {
append("&")
} else {
append("?")
}
append(param)
append("=")
append(URLEncoder.encode(value, "utf-8"))
return this
}
/**
* Ex: "https://matrix.org/" -> "matrix.org"
*/

View file

@ -87,7 +87,12 @@ abstract class AbstractSSOLoginFragment<VB: ViewBinding> : AbstractLoginFragment
withState(loginViewModel) { state ->
if (state.loginMode.hasSso() && state.loginMode.ssoIdentityProviders().isNullOrEmpty()) {
// in this case we can prefetch (not other cases for privacy concerns)
prefetchUrl(state.getSsoUrl(null))
loginViewModel.getSsoUrl(
redirectUrl = LoginActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = null
)
?.let { prefetchUrl(it) }
}
}
}

View file

@ -360,6 +360,9 @@ open class LoginActivity : VectorBaseActivity<ActivityLoginBinding>(), ToolbarCo
private const val EXTRA_CONFIG = "EXTRA_CONFIG"
// Note that the domain can be displayed to the user for confirmation that he trusts it. So use a human readable string
const val VECTOR_REDIRECT_URL = "element://connect"
fun newIntent(context: Context, loginConfig: LoginConfig?): Intent {
return Intent(context, LoginActivity::class.java).apply {
putExtra(EXTRA_CONFIG, loginConfig)

View file

@ -193,7 +193,12 @@ class LoginFragment @Inject constructor() : AbstractSSOLoginFragment<FragmentLog
views.loginSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders
views.loginSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(id: String?) {
openInCustomTab(state.getSsoUrl(id))
loginViewModel.getSsoUrl(
redirectUrl = LoginActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = id
)
?.let { openInCustomTab(it) }
}
}
} else {

View file

@ -76,8 +76,12 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi
views.loginSignupSigninSocialLoginButtons.ssoIdentityProviders = state.loginMode.ssoIdentityProviders()
views.loginSignupSigninSocialLoginButtons.listener = object : SocialLoginButtonsView.InteractionListener {
override fun onProviderSelected(id: String?) {
val url = withState(loginViewModel) { it.getSsoUrl(id) }
openInCustomTab(url)
loginViewModel.getSsoUrl(
redirectUrl = LoginActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = id
)
?.let { openInCustomTab(it) }
}
}
}
@ -105,7 +109,12 @@ class LoginSignUpSignInSelectionFragment @Inject constructor() : AbstractSSOLogi
private fun submit() = withState(loginViewModel) { state ->
if (state.loginMode is LoginMode.Sso) {
openInCustomTab(state.getSsoUrl(null))
loginViewModel.getSsoUrl(
redirectUrl = LoginActivity.VECTOR_REDIRECT_URL,
deviceId = state.deviceId,
providerId = null
)
?.let { openInCustomTab(it) }
} else {
loginViewModel.handle(LoginAction.UpdateSignMode(SignMode.SignUp))
}

View file

@ -818,4 +818,8 @@ class LoginViewModel @AssistedInject constructor(
fun getInitialHomeServerUrl(): String? {
return loginConfig?.homeServerUrl
}
fun getSsoUrl(redirectUrl: String, deviceId: String?, providerId: String?): String? {
return authenticationService.getSsoUrl(redirectUrl, deviceId, providerId)
}
}

View file

@ -22,10 +22,6 @@ import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.PersistState
import com.airbnb.mvrx.Success
import com.airbnb.mvrx.Uninitialized
import im.vector.app.core.extensions.appendParamToUrl
import org.matrix.android.sdk.api.auth.MSC2858_SSO_REDIRECT_PATH
import org.matrix.android.sdk.api.auth.SSO_REDIRECT_PATH
import org.matrix.android.sdk.api.auth.SSO_REDIRECT_URL_PARAM
data class LoginViewState(
val asyncLoginAction: Async<Unit> = Uninitialized,
@ -69,27 +65,4 @@ data class LoginViewState(
fun isUserLogged(): Boolean {
return asyncLoginAction is Success
}
fun getSsoUrl(providerId: String?): String {
return buildString {
append(homeServerUrl?.trim { it == '/' })
if (providerId != null) {
append(MSC2858_SSO_REDIRECT_PATH)
append("/$providerId")
} else {
append(SSO_REDIRECT_PATH)
}
// Set a redirect url we will intercept later
appendParamToUrl(SSO_REDIRECT_URL_PARAM, VECTOR_REDIRECT_URL)
deviceId?.takeIf { it.isNotBlank() }?.let {
// But https://github.com/matrix-org/synapse/issues/5755
appendParamToUrl("device_id", it)
}
}
}
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 VECTOR_REDIRECT_URL = "element://connect"
}
}

View file

@ -33,7 +33,6 @@ import android.webkit.WebViewClient
import androidx.appcompat.app.AlertDialog
import com.airbnb.mvrx.activityViewModel
import im.vector.app.R
import im.vector.app.core.extensions.appendParamToUrl
import im.vector.app.core.utils.AssetReader
import im.vector.app.databinding.FragmentLoginWebBinding
import im.vector.app.features.signout.soft.SoftLogoutAction
@ -42,6 +41,7 @@ import im.vector.app.features.signout.soft.SoftLogoutViewModel
import org.matrix.android.sdk.api.auth.LOGIN_FALLBACK_PATH
import org.matrix.android.sdk.api.auth.REGISTER_FALLBACK_PATH
import org.matrix.android.sdk.api.auth.data.Credentials
import org.matrix.android.sdk.api.util.appendParamToUrl
import org.matrix.android.sdk.internal.di.MoshiProvider
import timber.log.Timber
import java.net.URLDecoder