SoftLogout: Store the info that the token is not valid anymore for a faster startup

This commit is contained in:
Benoit Marty 2019-12-12 15:16:37 +01:00
parent 261b4be287
commit a5af949c15
10 changed files with 77 additions and 10 deletions

View file

@ -22,5 +22,6 @@ package im.vector.matrix.android.api.auth.data
*/ */
data class SessionParams( data class SessionParams(
val credentials: Credentials, val credentials: Credentials,
val homeServerConnectionConfig: HomeServerConnectionConfig val homeServerConnectionConfig: HomeServerConnectionConfig,
val isTokenValid: Boolean
) )

View file

@ -62,6 +62,11 @@ interface Session :
*/ */
val sessionParams: SessionParams val sessionParams: SessionParams
/**
* The session is valid, i.e. it has a valid token so far
*/
val isOpenable: Boolean
/** /**
* Useful shortcut to get access to the userId * Useful shortcut to get access to the userId
*/ */

View file

@ -60,7 +60,8 @@ internal class DefaultSessionCreator @Inject constructor(
?.also { Timber.d("Overriding identity server url to $it") } ?.also { Timber.d("Overriding identity server url to $it") }
?.let { Uri.parse(it) } ?.let { Uri.parse(it) }
?: homeServerConnectionConfig.identityServerUri ?: homeServerConnectionConfig.identityServerUri
)) ),
isTokenValid = true)
sessionParamsStore.save(sessionParams) sessionParamsStore.save(sessionParams)
return sessionManager.getOrCreateSession(sessionParams) return sessionManager.getOrCreateSession(sessionParams)

View file

@ -29,6 +29,8 @@ internal interface SessionParamsStore {
suspend fun save(sessionParams: SessionParams) suspend fun save(sessionParams: SessionParams)
suspend fun setTokenInvalid(userId: String)
suspend fun updateCredentials(newCredentials: Credentials) suspend fun updateCredentials(newCredentials: Credentials)
suspend fun delete(userId: String) suspend fun delete(userId: String)

View file

@ -24,7 +24,7 @@ internal class AuthRealmMigration : RealmMigration {
companion object { companion object {
// Current schema version // Current schema version
const val SCHEMA_VERSION = 1L const val SCHEMA_VERSION = 2L
} }
override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) { override fun migrate(realm: DynamicRealm, oldVersion: Long, newVersion: Long) {
@ -46,5 +46,14 @@ internal class AuthRealmMigration : RealmMigration {
.addField(PendingSessionEntityFields.IS_REGISTRATION_STARTED, Boolean::class.java) .addField(PendingSessionEntityFields.IS_REGISTRATION_STARTED, Boolean::class.java)
.addField(PendingSessionEntityFields.CURRENT_THREE_PID_DATA_JSON, String::class.java) .addField(PendingSessionEntityFields.CURRENT_THREE_PID_DATA_JSON, String::class.java)
} }
if (oldVersion <= 1) {
Timber.d("Step 1 -> 2")
Timber.d("Add boolean isTokenValid in SessionParamsEntity, with value true")
realm.schema.get("SessionParamsEntity")
?.addField(SessionParamsEntityFields.IS_TOKEN_VALID, Boolean::class.java)
?.transform { it.set(SessionParamsEntityFields.IS_TOKEN_VALID, true) }
}
} }
} }

View file

@ -76,6 +76,26 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
} }
} }
override suspend fun setTokenInvalid(userId: String) {
awaitTransaction(realmConfiguration) { realm ->
val currentSessionParams = realm
.where(SessionParamsEntity::class.java)
.equalTo(SessionParamsEntityFields.USER_ID, userId)
.findAll()
.firstOrNull()
if (currentSessionParams == null) {
// Should not happen
"Session param not found for user $userId"
.let { Timber.w(it) }
.also { error(it) }
} else {
currentSessionParams.isTokenValid = false
}
}
}
override suspend fun updateCredentials(newCredentials: Credentials) { override suspend fun updateCredentials(newCredentials: Credentials) {
awaitTransaction(realmConfiguration) { realm -> awaitTransaction(realmConfiguration) { realm ->
val currentSessionParams = realm val currentSessionParams = realm
@ -92,7 +112,8 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
.also { error(it) } .also { error(it) }
} else { } else {
val newSessionParams = currentSessionParams.copy( val newSessionParams = currentSessionParams.copy(
credentials = newCredentials credentials = newCredentials,
isTokenValid = true
) )
val entity = mapper.map(newSessionParams) val entity = mapper.map(newSessionParams)

View file

@ -22,5 +22,8 @@ import io.realm.annotations.PrimaryKey
internal open class SessionParamsEntity( internal open class SessionParamsEntity(
@PrimaryKey var userId: String = "", @PrimaryKey var userId: String = "",
var credentialsJson: String = "", var credentialsJson: String = "",
var homeServerConnectionConfigJson: String = "" var homeServerConnectionConfigJson: String = "",
// Set to false when the token is invalid and the user has been soft logged out
// In case of hard logout, this object in deleted from DB
var isTokenValid: Boolean = true
) : RealmObject() ) : RealmObject()

View file

@ -36,7 +36,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
if (credentials == null || homeServerConnectionConfig == null) { if (credentials == null || homeServerConnectionConfig == null) {
return null return null
} }
return SessionParams(credentials, homeServerConnectionConfig) return SessionParams(credentials, homeServerConnectionConfig, entity.isTokenValid)
} }
fun map(sessionParams: SessionParams?): SessionParamsEntity? { fun map(sessionParams: SessionParams?): SessionParamsEntity? {
@ -48,6 +48,10 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
if (credentialsJson == null || homeServerConnectionConfigJson == null) { if (credentialsJson == null || homeServerConnectionConfigJson == null) {
return null return null
} }
return SessionParamsEntity(sessionParams.credentials.userId, credentialsJson, homeServerConnectionConfigJson) return SessionParamsEntity(
sessionParams.credentials.userId,
credentialsJson,
homeServerConnectionConfigJson,
sessionParams.isTokenValid)
} }
} }

View file

@ -42,10 +42,14 @@ import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.session.sync.FilterService import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.api.session.sync.SyncState import im.vector.matrix.android.api.session.sync.SyncState
import im.vector.matrix.android.api.session.user.UserService import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.crypto.DefaultCryptoService import im.vector.matrix.android.internal.crypto.DefaultCryptoService
import im.vector.matrix.android.internal.database.LiveEntityObserver import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.session.sync.job.SyncThread import im.vector.matrix.android.internal.session.sync.job.SyncThread
import im.vector.matrix.android.internal.session.sync.job.SyncWorker import im.vector.matrix.android.internal.session.sync.job.SyncWorker
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
@ -72,6 +76,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
private val secureStorageService: Lazy<SecureStorageService>, private val secureStorageService: Lazy<SecureStorageService>,
private val syncThreadProvider: Provider<SyncThread>, private val syncThreadProvider: Provider<SyncThread>,
private val contentUrlResolver: ContentUrlResolver, private val contentUrlResolver: ContentUrlResolver,
private val sessionParamsStore: SessionParamsStore,
private val contentUploadProgressTracker: ContentUploadStateTracker, private val contentUploadProgressTracker: ContentUploadStateTracker,
private val initialSyncProgressService: Lazy<InitialSyncProgressService>, private val initialSyncProgressService: Lazy<InitialSyncProgressService>,
private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>) private val homeServerCapabilitiesService: Lazy<HomeServerCapabilitiesService>)
@ -94,6 +99,9 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
private var syncThread: SyncThread? = null private var syncThread: SyncThread? = null
override val isOpenable: Boolean
get() = sessionParamsStore.get(myUserId)?.isTokenValid ?: false
@MainThread @MainThread
override fun open() { override fun open() {
assertMainThread() assertMainThread()
@ -171,6 +179,14 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onGlobalError(globalError: GlobalError) { fun onGlobalError(globalError: GlobalError) {
if (globalError is GlobalError.InvalidToken
&& globalError.softLogout) {
// Mark the token has invalid
GlobalScope.launch(Dispatchers.IO) {
sessionParamsStore.setTokenInvalid(myUserId)
}
}
sessionListeners.dispatchGlobalError(globalError) sessionListeners.dispatchGlobalError(globalError)
} }

View file

@ -169,9 +169,14 @@ class MainActivity : VectorBaseActivity() {
// the homeserver has invalidated the token (password changed, device deleted, other security reason // the homeserver has invalidated the token (password changed, device deleted, other security reason
SignedOutActivity.newIntent(this) SignedOutActivity.newIntent(this)
sessionHolder.hasActiveSession() -> sessionHolder.hasActiveSession() ->
// We have a session. In case of soft logout (i.e. restart of the app after a soft logout) // We have a session.
// the app will try to sync and will reenter the soft logout use case // Check it can be opened
HomeActivity.newIntent(this) if(sessionHolder.getActiveSession().isOpenable) {
HomeActivity.newIntent(this)
} else {
// The token is still invalid
SoftLogoutActivity.newIntent(this)
}
else -> else ->
// First start, or no active session // First start, or no active session
LoginActivity.newIntent(this, null) LoginActivity.newIntent(this, null)