mirror of
https://github.com/element-hq/element-android
synced 2024-11-27 20:06:51 +03:00
Login screens: registration fallback
This commit is contained in:
parent
1c03163a33
commit
1dc7dfc896
10 changed files with 105 additions and 77 deletions
|
@ -31,4 +31,5 @@ data class Credentials(
|
|||
@Json(name = "access_token") val accessToken: String,
|
||||
@Json(name = "refresh_token") val refreshToken: String?,
|
||||
@Json(name = "device_id") val deviceId: String?
|
||||
// TODO Add Wellknown
|
||||
)
|
||||
|
|
1
vector/src/main/assets/onLogin.js
Normal file
1
vector/src/main/assets/onLogin.js
Normal file
|
@ -0,0 +1 @@
|
|||
javascript:window.matrixLogin.onLogin = function(response) { sendObjectMessage({ 'action': 'onLogin', 'credentials': response }); };
|
1
vector/src/main/assets/onRegistered.js
Normal file
1
vector/src/main/assets/onRegistered.js
Normal file
|
@ -0,0 +1 @@
|
|||
javascript:window.matrixRegistration.onRegistered = function(homeserverUrl, userId, accessToken) { sendObjectMessage({ 'action': 'onRegistered', 'homeServer': homeserverUrl, 'userId': userId, 'accessToken': accessToken }); }
|
1
vector/src/main/assets/sendObject.js
Normal file
1
vector/src/main/assets/sendObject.js
Normal file
|
@ -0,0 +1 @@
|
|||
javascript:window.sendObjectMessage = function(parameters) { var iframe = document.createElement('iframe'); iframe.setAttribute('src', 'js:' + JSON.stringify(parameters)); document.documentElement.appendChild(iframe); iframe.parentNode.removeChild(iframe); iframe = null;};
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* Copyright 2019 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.features.login
|
||||
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class JavascriptResponse(
|
||||
@Json(name = "action")
|
||||
val action: String? = null,
|
||||
|
||||
/**
|
||||
* Use for captcha result
|
||||
*/
|
||||
@Json(name = "response")
|
||||
val response: String? = null,
|
||||
|
||||
/**
|
||||
* Used for login/registration result
|
||||
*/
|
||||
@Json(name = "credentials")
|
||||
val credentials: Credentials? = null
|
||||
)
|
|
@ -103,9 +103,9 @@ class LoginActivity : VectorBaseActivity() {
|
|||
when (loginViewEvents) {
|
||||
is LoginViewEvents.RegistrationFlowResult -> {
|
||||
// Check that all flows are supported by the application
|
||||
if (loginViewEvents.flowResult.missingStages.any { it is Stage.Other }) {
|
||||
if (loginViewEvents.flowResult.missingStages.any { !it.isSupported() }) {
|
||||
// Display a popup to propose use web fallback
|
||||
// TODO
|
||||
onRegistrationStageNotSupported()
|
||||
} else {
|
||||
// Go on with registration flow
|
||||
// loginSharedActionViewModel.post(LoginNavigation.OnSignModeSelected)
|
||||
|
@ -159,7 +159,9 @@ class LoginActivity : VectorBaseActivity() {
|
|||
private fun onSignModeSelected() {
|
||||
when (loginViewModel.signMode) {
|
||||
SignMode.Unknown -> error("Sign mode has to be set before calling this method")
|
||||
SignMode.SignUp -> addFragmentToBackstack(R.id.loginFragmentContainer, LoginFragment::class.java)
|
||||
SignMode.SignUp -> {
|
||||
// This is managed by the LoginViewEvents
|
||||
}
|
||||
SignMode.SignIn -> {
|
||||
// It depends on the LoginMode
|
||||
withState(loginViewModel) {
|
||||
|
@ -174,6 +176,17 @@ class LoginActivity : VectorBaseActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
private fun onRegistrationStageNotSupported() {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.app_name)
|
||||
.setMessage(getString(R.string.login_registration_not_supported))
|
||||
.setPositiveButton(R.string.yes) { _, _ ->
|
||||
addFragmentToBackstack(R.id.loginFragmentContainer, LoginWebFragment::class.java)
|
||||
}
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun onLoginModeNotSupported(unsupportedLoginMode: LoginMode.Unsupported) {
|
||||
AlertDialog.Builder(this)
|
||||
.setTitle(R.string.app_name)
|
||||
|
|
|
@ -27,8 +27,6 @@ import android.view.View
|
|||
import android.webkit.*
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import com.airbnb.mvrx.args
|
||||
import com.squareup.moshi.Json
|
||||
import com.squareup.moshi.JsonClass
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.utils.AssetReader
|
||||
|
@ -44,14 +42,6 @@ data class LoginCaptchaFragmentArgument(
|
|||
val siteKey: String
|
||||
) : Parcelable
|
||||
|
||||
@JsonClass(generateAdapter = true)
|
||||
data class JavascriptResponse(
|
||||
@Json(name = "action")
|
||||
val action: String? = null,
|
||||
@Json(name = "response")
|
||||
val response: String? = null
|
||||
)
|
||||
|
||||
/**
|
||||
* In this screen, the user is asked to confirm he is not a robot
|
||||
*/
|
||||
|
|
|
@ -30,10 +30,9 @@ import android.webkit.SslErrorHandler
|
|||
import android.webkit.WebView
|
||||
import android.webkit.WebViewClient
|
||||
import androidx.appcompat.app.AlertDialog
|
||||
import im.vector.matrix.android.api.auth.data.Credentials
|
||||
import im.vector.matrix.android.api.util.JsonDict
|
||||
import im.vector.matrix.android.internal.di.MoshiProvider
|
||||
import im.vector.riotx.R
|
||||
import im.vector.riotx.core.utils.AssetReader
|
||||
import kotlinx.android.synthetic.main.fragment_login_web.*
|
||||
import timber.log.Timber
|
||||
import java.net.URLDecoder
|
||||
|
@ -43,7 +42,7 @@ import javax.inject.Inject
|
|||
* This screen is displayed for SSO login and also when the application does not support login flow or registration flow
|
||||
* of the homeserfver, as a fallback to login or to create an account
|
||||
*/
|
||||
class LoginWebFragment @Inject constructor() : AbstractLoginFragment() {
|
||||
class LoginWebFragment @Inject constructor(private val assetReader: AssetReader) : AbstractLoginFragment() {
|
||||
|
||||
private lateinit var homeServerUrl: String
|
||||
private lateinit var signMode: SignMode
|
||||
|
@ -151,33 +150,17 @@ class LoginWebFragment @Inject constructor() : AbstractLoginFragment() {
|
|||
// avoid infinite onPageFinished call
|
||||
if (url.startsWith("http")) {
|
||||
// Generic method to make a bridge between JS and the UIWebView
|
||||
val mxcJavascriptSendObjectMessage = "javascript:window.sendObjectMessage = function(parameters) {" +
|
||||
" var iframe = document.createElement('iframe');" +
|
||||
" iframe.setAttribute('src', 'js:' + JSON.stringify(parameters));" +
|
||||
" document.documentElement.appendChild(iframe);" +
|
||||
" iframe.parentNode.removeChild(iframe); iframe = null;" +
|
||||
" };"
|
||||
|
||||
val mxcJavascriptSendObjectMessage = assetReader.readAssetFile("sendObject.js")
|
||||
view.loadUrl(mxcJavascriptSendObjectMessage)
|
||||
|
||||
if (signMode == SignMode.SignIn) {
|
||||
// The function the fallback page calls when the login is complete
|
||||
val mxcJavascriptOnRegistered = "javascript:window.matrixLogin.onLogin = function(response) {" +
|
||||
" sendObjectMessage({ 'action': 'onLogin', 'credentials': response });" +
|
||||
" };"
|
||||
|
||||
view.loadUrl(mxcJavascriptOnRegistered)
|
||||
val mxcJavascriptOnLogin = assetReader.readAssetFile("onLogin.js")
|
||||
view.loadUrl(mxcJavascriptOnLogin)
|
||||
} else {
|
||||
// MODE_REGISTER
|
||||
// The function the fallback page calls when the registration is complete
|
||||
val mxcJavascriptOnRegistered = "javascript:window.matrixRegistration.onRegistered" +
|
||||
" = function(homeserverUrl, userId, accessToken) {" +
|
||||
" sendObjectMessage({ 'action': 'onRegistered'," +
|
||||
" 'homeServer': homeserverUrl," +
|
||||
" 'userId': userId," +
|
||||
" 'accessToken': accessToken });" +
|
||||
" };"
|
||||
|
||||
val mxcJavascriptOnRegistered = assetReader.readAssetFile("onRegistered.js")
|
||||
view.loadUrl(mxcJavascriptOnRegistered)
|
||||
}
|
||||
}
|
||||
|
@ -209,46 +192,27 @@ class LoginWebFragment @Inject constructor() : AbstractLoginFragment() {
|
|||
override fun shouldOverrideUrlLoading(view: WebView, url: String?): Boolean {
|
||||
if (null != url && url.startsWith("js:")) {
|
||||
var json = url.substring(3)
|
||||
var parameters: Map<String, Any>? = null
|
||||
var parameters: JavascriptResponse? = null
|
||||
|
||||
try {
|
||||
// URL decode
|
||||
json = URLDecoder.decode(json, "UTF-8")
|
||||
|
||||
val adapter = MoshiProvider.providesMoshi().adapter(Map::class.java)
|
||||
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
parameters = adapter.fromJson(json) as JsonDict?
|
||||
val adapter = MoshiProvider.providesMoshi().adapter(JavascriptResponse::class.java)
|
||||
parameters = adapter.fromJson(json)
|
||||
} catch (e: Exception) {
|
||||
Timber.e(e, "## shouldOverrideUrlLoading() : fromJson failed")
|
||||
}
|
||||
|
||||
// succeeds to parse parameters
|
||||
if (parameters != null) {
|
||||
val action = parameters["action"] as String
|
||||
val action = parameters.action
|
||||
|
||||
if (signMode == SignMode.SignIn) {
|
||||
try {
|
||||
if (action == "onLogin") {
|
||||
@Suppress("UNCHECKED_CAST")
|
||||
val credentials = parameters["credentials"] as Map<String, String>
|
||||
|
||||
val userId = credentials["user_id"]
|
||||
val accessToken = credentials["access_token"]
|
||||
val homeServer = credentials["home_server"]
|
||||
val deviceId = credentials["device_id"]
|
||||
|
||||
// check if the parameters are defined
|
||||
if (null != homeServer && null != userId && null != accessToken) {
|
||||
val safeCredentials = Credentials(
|
||||
userId = userId,
|
||||
accessToken = accessToken,
|
||||
homeServer = homeServer,
|
||||
deviceId = deviceId,
|
||||
refreshToken = null
|
||||
)
|
||||
|
||||
loginViewModel.handle(LoginAction.WebLoginSuccess(safeCredentials))
|
||||
val credentials = parameters.credentials
|
||||
if (credentials != null) {
|
||||
loginViewModel.handle(LoginAction.WebLoginSuccess(credentials))
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
|
@ -258,21 +222,8 @@ class LoginWebFragment @Inject constructor() : AbstractLoginFragment() {
|
|||
// MODE_REGISTER
|
||||
// check the required parameters
|
||||
if (action == "onRegistered") {
|
||||
// TODO The keys are very strange, this code comes from Riot-Android...
|
||||
if (parameters.containsKey("homeServer")
|
||||
&& parameters.containsKey("userId")
|
||||
&& parameters.containsKey("accessToken")) {
|
||||
// We cannot parse Credentials here because of https://github.com/matrix-org/synapse/issues/4756
|
||||
// Build on object manually
|
||||
val credentials = Credentials(
|
||||
userId = parameters["userId"] as String,
|
||||
accessToken = parameters["accessToken"] as String,
|
||||
homeServer = parameters["homeServer"] as String,
|
||||
// TODO We need deviceId on RiotX...
|
||||
deviceId = "TODO",
|
||||
refreshToken = null
|
||||
)
|
||||
|
||||
val credentials = parameters.credentials
|
||||
if (credentials != null) {
|
||||
loginViewModel.handle(LoginAction.WebLoginSuccess(credentials))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,30 @@
|
|||
/*
|
||||
* Copyright 2019 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.features.login
|
||||
|
||||
import im.vector.matrix.android.api.auth.registration.Stage
|
||||
|
||||
/**
|
||||
* Stage.Other is not supported, as well as any other new stage added to the SDK before it is added to the list below
|
||||
*/
|
||||
fun Stage.isSupported(): Boolean {
|
||||
return this is Stage.ReCaptcha
|
||||
|| this is Stage.Dummy
|
||||
|| this is Stage.Msisdn
|
||||
|| this is Stage.Terms
|
||||
|| this is Stage.Email
|
||||
}
|
|
@ -56,6 +56,7 @@
|
|||
|
||||
<string name="login_sso_error_message">An error occurred when loading the page: %1$s (%2$d)</string>
|
||||
<string name="login_mode_not_supported">The application is not able to signin to this homeserver. The homeserver supports the following signin type(s): %1$s.\n\nDo you want to signin using a web client?</string>
|
||||
<string name="login_registration_not_supported">The application is not able to create an account on this homeserver.\n\nDo you want to signup using a web client?</string>
|
||||
|
||||
<!-- Replaced string is the homeserver url -->
|
||||
<string name="login_reset_password_on">Reset password on %1$s</string>
|
||||
|
|
Loading…
Reference in a new issue