InvalidToken: Regular Signed out screen

This commit is contained in:
Benoit Marty 2019-12-10 18:00:51 +01:00
parent 29087d4a87
commit 284dc8602f
11 changed files with 212 additions and 55 deletions

View File

@ -17,6 +17,7 @@
package im.vector.matrix.android.api.session.signout package im.vector.matrix.android.api.session.signout
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
/** /**
* This interface defines a method to sign out. It's implemented at the session level. * This interface defines a method to sign out. It's implemented at the session level.
@ -24,7 +25,9 @@ import im.vector.matrix.android.api.MatrixCallback
interface SignOutService { interface SignOutService {
/** /**
* Sign out * Sign out, and release the session, clear all the session data, including crypto data
* @param sigOutFromHomeserver true if the sign out request has to be done
*/ */
fun signOut(callback: MatrixCallback<Unit>) fun signOut(sigOutFromHomeserver: Boolean,
callback: MatrixCallback<Unit>): Cancelable
} }

View File

@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.signout
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.signout.SignOutService import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import javax.inject.Inject import javax.inject.Inject
@ -25,9 +26,10 @@ import javax.inject.Inject
internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask, internal class DefaultSignOutService @Inject constructor(private val signOutTask: SignOutTask,
private val taskExecutor: TaskExecutor) : SignOutService { private val taskExecutor: TaskExecutor) : SignOutService {
override fun signOut(callback: MatrixCallback<Unit>) { override fun signOut(sigOutFromHomeserver: Boolean,
signOutTask callback: MatrixCallback<Unit>): Cancelable {
.configureWith { return signOutTask
.configureWith(SignOutTask.Params(sigOutFromHomeserver)) {
this.callback = callback this.callback = callback
} }
.executeBy(taskExecutor) .executeBy(taskExecutor)

View File

@ -34,7 +34,11 @@ import timber.log.Timber
import java.io.File import java.io.File
import javax.inject.Inject import javax.inject.Inject
internal interface SignOutTask : Task<Unit, Unit> internal interface SignOutTask : Task<SignOutTask.Params, Unit> {
data class Params(
val sigOutFromHomeserver: Boolean
)
}
internal class DefaultSignOutTask @Inject constructor(private val context: Context, internal class DefaultSignOutTask @Inject constructor(private val context: Context,
@UserId private val userId: String, @UserId private val userId: String,
@ -49,10 +53,13 @@ internal class DefaultSignOutTask @Inject constructor(private val context: Conte
@CryptoDatabase private val realmCryptoConfiguration: RealmConfiguration, @CryptoDatabase private val realmCryptoConfiguration: RealmConfiguration,
@UserMd5 private val userMd5: String) : SignOutTask { @UserMd5 private val userMd5: String) : SignOutTask {
override suspend fun execute(params: Unit) { override suspend fun execute(params: SignOutTask.Params) {
Timber.d("SignOut: send request...") Timber.d("SignOut: send request...")
executeRequest<Unit> {
apiCall = signOutAPI.signOut() if (params.sigOutFromHomeserver) {
executeRequest<Unit> {
apiCall = signOutAPI.signOut()
}
} }
Timber.d("SignOut: release session...") Timber.d("SignOut: release session...")

View File

@ -17,8 +17,6 @@
package im.vector.matrix.android.internal.session.sync package im.vector.matrix.android.internal.session.sync
import im.vector.matrix.android.R import im.vector.matrix.android.R
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.di.UserId import im.vector.matrix.android.internal.di.UserId
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
@ -67,19 +65,8 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI,
initialSyncProgressService.endAll() initialSyncProgressService.endAll()
initialSyncProgressService.startTask(R.string.initial_sync_start_importing_account, 100) initialSyncProgressService.startTask(R.string.initial_sync_start_importing_account, 100)
} }
val syncResponse = try { val syncResponse = executeRequest<SyncResponse> {
executeRequest<SyncResponse> { apiCall = syncAPI.sync(requestParams)
apiCall = syncAPI.sync(requestParams)
}
} catch (throwable: Throwable) {
// Intercept 401
// TODO Remove?
//if (throwable is Failure.ServerError
// && throwable.error.code == MatrixError.M_UNKNOWN_TOKEN
// && !throwable.error.isSoftLogout) {
// sessionParamsStore.delete(userId)
//}
throw throwable
} }
syncResponseHandler.handleResponse(syncResponse, token) syncResponseHandler.handleResponse(syncResponse, token)
syncTokenStore.saveToken(syncResponse.nextBatch) syncTokenStore.saveToken(syncResponse.nextBatch)

View File

@ -98,6 +98,7 @@
<category android:name="android.intent.category.OPENABLE" /> <category android:name="android.intent.category.OPENABLE" />
</intent-filter> </intent-filter>
</activity> </activity>
<activity android:name=".features.main.SignedOutActivity" />
<!-- Services --> <!-- Services -->
<service <service

View File

@ -45,6 +45,8 @@ import im.vector.riotx.core.di.*
import im.vector.riotx.core.dialogs.DialogLocker import im.vector.riotx.core.dialogs.DialogLocker
import im.vector.riotx.core.extensions.observeEvent import im.vector.riotx.core.extensions.observeEvent
import im.vector.riotx.core.utils.toast import im.vector.riotx.core.utils.toast
import im.vector.riotx.features.MainActivity
import im.vector.riotx.features.MainActivityArgs
import im.vector.riotx.features.configuration.VectorConfiguration import im.vector.riotx.features.configuration.VectorConfiguration
import im.vector.riotx.features.consent.ConsentNotGivenHelper import im.vector.riotx.features.consent.ConsentNotGivenHelper
import im.vector.riotx.features.navigation.Navigator import im.vector.riotx.features.navigation.Navigator
@ -90,6 +92,9 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
protected lateinit var navigator: Navigator protected lateinit var navigator: Navigator
private lateinit var activeSessionHolder: ActiveSessionHolder private lateinit var activeSessionHolder: ActiveSessionHolder
// Filter for multiple invalid token error
private var mainActivityStarted = false
private var unBinder: Unbinder? = null private var unBinder: Unbinder? = null
private var savedInstanceState: Bundle? = null private var savedInstanceState: Bundle? = null
@ -182,13 +187,32 @@ abstract class VectorBaseActivity : AppCompatActivity(), HasScreenInjector {
private fun handleGlobalError(globalError: GlobalError) { private fun handleGlobalError(globalError: GlobalError) {
when (globalError) { when (globalError) {
is GlobalError.InvalidToken -> TODO() is GlobalError.InvalidToken ->
handleInvalidToken(globalError)
is GlobalError.ConsentNotGivenError -> is GlobalError.ConsentNotGivenError ->
consentNotGivenHelper.displayDialog(globalError.consentUri, consentNotGivenHelper.displayDialog(globalError.consentUri,
activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host ?: "") activeSessionHolder.getActiveSession().sessionParams.homeServerConnectionConfig.homeServerUri.host ?: "")
} }
} }
protected open fun handleInvalidToken(globalError: GlobalError.InvalidToken) {
Timber.w("Invalid token event received")
if(mainActivityStarted) {
return
}
mainActivityStarted = true
MainActivity.restartApp(this,
MainActivityArgs(
clearCache = true,
clearCredentials = !globalError.softLogout,
isUserLoggedOut = true,
isSoftLogout = globalError.softLogout
)
)
}
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
unBinder?.unbind() unBinder?.unbind()

View File

@ -23,6 +23,7 @@ import android.os.Parcelable
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import com.bumptech.glide.Glide import com.bumptech.glide.Glide
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.GlobalError
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.di.ActiveSessionHolder import im.vector.riotx.core.di.ActiveSessionHolder
import im.vector.riotx.core.di.ScreenComponent import im.vector.riotx.core.di.ScreenComponent
@ -31,6 +32,7 @@ import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.core.utils.deleteAllFiles import im.vector.riotx.core.utils.deleteAllFiles
import im.vector.riotx.features.home.HomeActivity import im.vector.riotx.features.home.HomeActivity
import im.vector.riotx.features.login.LoginActivity import im.vector.riotx.features.login.LoginActivity
import im.vector.riotx.features.main.SignedOutActivity
import kotlinx.android.parcel.Parcelize import kotlinx.android.parcel.Parcelize
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -62,7 +64,8 @@ class MainActivity : VectorBaseActivity() {
} }
} }
private var args: MainActivityArgs? = null private lateinit var args: MainActivityArgs
@Inject lateinit var sessionHolder: ActiveSessionHolder @Inject lateinit var sessionHolder: ActiveSessionHolder
@Inject lateinit var errorFormatter: ErrorFormatter @Inject lateinit var errorFormatter: ErrorFormatter
@ -72,44 +75,61 @@ class MainActivity : VectorBaseActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
args = intent.getParcelableExtra(EXTRA_ARGS) args = parseArgs()
val clearCache = args?.clearCache ?: false
val clearCredentials = args?.clearCredentials ?: false
// Handle some wanted cleanup // Handle some wanted cleanup
if (clearCache || clearCredentials) { if (args.clearCache || args.clearCredentials) {
doCleanUp(clearCache, clearCredentials) doCleanUp()
} else { } else {
start() start()
} }
} }
private fun doCleanUp(clearCache: Boolean, clearCredentials: Boolean) { private fun parseArgs(): MainActivityArgs {
val argsFromIntent: MainActivityArgs? = intent.getParcelableExtra(EXTRA_ARGS)
Timber.w("Starting MainActivity with $argsFromIntent")
return MainActivityArgs(
clearCache = argsFromIntent?.clearCache ?: false,
clearCredentials = argsFromIntent?.clearCredentials ?: false,
isUserLoggedOut = argsFromIntent?.isUserLoggedOut ?: false,
isSoftLogout = argsFromIntent?.isSoftLogout ?: false
)
}
private fun doCleanUp() {
when { when {
clearCredentials -> sessionHolder.getActiveSession().signOut(object : MatrixCallback<Unit> { args.clearCredentials -> sessionHolder.getActiveSession().signOut(
override fun onSuccess(data: Unit) { !args.isUserLoggedOut,
Timber.w("SIGN_OUT: success, start app") object : MatrixCallback<Unit> {
sessionHolder.clearActiveSession() override fun onSuccess(data: Unit) {
doLocalCleanupAndStart() Timber.w("SIGN_OUT: success, start app")
} sessionHolder.clearActiveSession()
doLocalCleanupAndStart()
}
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
displayError(failure, clearCache, clearCredentials) displayError(failure)
} }
}) })
clearCache -> sessionHolder.getActiveSession().clearCache(object : MatrixCallback<Unit> { args.clearCache -> sessionHolder.getActiveSession().clearCache(
override fun onSuccess(data: Unit) { object : MatrixCallback<Unit> {
doLocalCleanupAndStart() override fun onSuccess(data: Unit) {
} doLocalCleanupAndStart()
}
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
displayError(failure, clearCache, clearCredentials) displayError(failure)
} }
}) })
} }
} }
override fun handleInvalidToken(globalError: GlobalError.InvalidToken) {
// No op here
Timber.w("Ignoring invalid token global error")
}
private fun doLocalCleanupAndStart() { private fun doLocalCleanupAndStart() {
GlobalScope.launch(Dispatchers.Main) { GlobalScope.launch(Dispatchers.Main) {
// On UI Thread // On UI Thread
@ -126,11 +146,11 @@ class MainActivity : VectorBaseActivity() {
start() start()
} }
private fun displayError(failure: Throwable, clearCache: Boolean, clearCredentials: Boolean) { private fun displayError(failure: Throwable) {
AlertDialog.Builder(this) AlertDialog.Builder(this)
.setTitle(R.string.dialog_title_error) .setTitle(R.string.dialog_title_error)
.setMessage(errorFormatter.toHumanReadable(failure)) .setMessage(errorFormatter.toHumanReadable(failure))
.setPositiveButton(R.string.global_retry) { _, _ -> doCleanUp(clearCache, clearCredentials) } .setPositiveButton(R.string.global_retry) { _, _ -> doCleanUp() }
.setNegativeButton(R.string.cancel) { _, _ -> start() } .setNegativeButton(R.string.cancel) { _, _ -> start() }
.setCancelable(false) .setCancelable(false)
.show() .show()
@ -140,7 +160,13 @@ class MainActivity : VectorBaseActivity() {
val intent = if (sessionHolder.hasActiveSession()) { val intent = if (sessionHolder.hasActiveSession()) {
HomeActivity.newIntent(this) HomeActivity.newIntent(this)
} else { } else {
LoginActivity.newIntent(this, null) // Check if we've been signed out
if (args.isUserLoggedOut) {
// TODO Soft logout
SignedOutActivity.newIntent(this)
} else {
LoginActivity.newIntent(this, null)
}
} }
startActivity(intent) startActivity(intent)
finish() finish()

View File

@ -87,7 +87,7 @@ class LinkHandlerActivity : VectorBaseActivity() {
.setMessage(R.string.error_user_already_logged_in) .setMessage(R.string.error_user_already_logged_in)
.setCancelable(false) .setCancelable(false)
.setPositiveButton(R.string.logout) { _, _ -> .setPositiveButton(R.string.logout) { _, _ ->
sessionHolder.getSafeActiveSession()?.signOut(object : MatrixCallback<Unit> { sessionHolder.getSafeActiveSession()?.signOut(true, object : MatrixCallback<Unit> {
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
displayError(failure) displayError(failure)
} }

View File

@ -0,0 +1,52 @@
/*
* 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.main
import android.content.Context
import android.content.Intent
import butterknife.OnClick
import im.vector.matrix.android.api.failure.GlobalError
import im.vector.riotx.R
import im.vector.riotx.core.platform.VectorBaseActivity
import im.vector.riotx.features.MainActivity
import im.vector.riotx.features.MainActivityArgs
import timber.log.Timber
/**
* In this screen, the user is viewing a message informing that he has been logged out
*/
class SignedOutActivity : VectorBaseActivity() {
override fun getLayoutRes() = R.layout.activity_signed_out
@OnClick(R.id.signedOutSubmit)
fun submit() {
// All is already cleared when we are here
MainActivity.restartApp(this, MainActivityArgs())
}
companion object {
fun newIntent(context: Context): Intent {
return Intent(context, SignedOutActivity::class.java)
}
}
override fun handleInvalidToken(globalError: GlobalError.InvalidToken) {
// No op here
Timber.w("Ignoring invalid token global error")
}
}

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/signedOut"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="?riotx_background">
<!-- Missing attributes are in the style -->
<ImageView
style="@style/LoginLogo"
tools:ignore="ContentDescription,MissingConstraints" />
<!-- Missing attributes are in the style -->
<androidx.core.widget.NestedScrollView
style="@style/LoginFormScrollView"
tools:ignore="MissingConstraints">
<LinearLayout
style="@style/LoginFormContainer"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/signed_out_title"
android:textAppearance="@style/TextAppearance.Vector.Login.Title" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="@dimen/layout_vertical_margin"
android:gravity="start"
android:text="@string/signed_out_notice"
android:textAppearance="@style/TextAppearance.Vector.Login.Text" />
<com.google.android.material.button.MaterialButton
android:id="@+id/signedOutSubmit"
style="@style/Style.Vector.Login.Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="end"
android:layout_marginTop="22dp"
android:text="@string/signed_out_submit" />
</LinearLayout>
</androidx.core.widget.NestedScrollView>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -140,4 +140,8 @@
<string name="seen_by">Seen by</string> <string name="seen_by">Seen by</string>
<string name="signed_out_title">Youre signed out</string>
<string name="signed_out_notice">It can be due to various reasons:\n\n• Youve changed your password on another device.\n\n• You have deleted this device from another device.\n\n• The administrator of your server has invalidated your access for security reason.</string>
<string name="signed_out_submit">Sign in again</string>
</resources> </resources>