Logs the user out when username not found for user id

If we detect an inconsistent state in the app, in which a username
for the primary id is not present in the shared prefs, log the
user out and log the error. The correct username for that user id
will be put there on the next log in.

Additionally, removes the code setting up alarm and fetching events from
the navigation activity when a new id arrives- it's no longer needed
there and was a leftover from a merge conflict from a previous MR.

MAILAND-2644
This commit is contained in:
Maciej Surmacz 2021-12-09 12:38:58 +01:00 committed by Zorica Stojchevska
parent 60544120f1
commit 497ccd0709
7 changed files with 162 additions and 24 deletions

View File

@ -18,7 +18,6 @@
*/
package ch.protonmail.android.activities.dialogs;
import android.content.Context;
import android.content.Intent;
import android.view.View;
import android.widget.AdapterView;
@ -44,7 +43,7 @@ import ch.protonmail.android.settings.presentation.SnoozeNotificationsActivity;
import ch.protonmail.android.views.CustomQuickSnoozeDialog;
import dagger.hilt.android.AndroidEntryPoint;
import static ch.protonmail.android.activities.NavigationActivityKt.REQUEST_CODE_SNOOZED_NOTIFICATIONS;
import static ch.protonmail.android.navigation.presentation.NavigationActivityKt.REQUEST_CODE_SNOOZED_NOTIFICATIONS;
@AndroidEntryPoint
public class QuickSnoozeDialogFragment

View File

@ -44,9 +44,9 @@ open class DatabaseFactory<T : RoomDatabase>(
@Synchronized
fun deleteDatabase(context: Context, userId: UserId) {
val username = CounterDatabase.usernameForUserId(context, userId)
val username = usernameForUserId(context, userId)
val baseFileName = CounterDatabase.databaseName(username)
val baseFileName = databaseName(username)
val databaseFile = context.getDatabasePath(baseFileName)
val databaseFileShm = context.getDatabasePath("$baseFileName-shm")
val databaseFileWal = context.getDatabasePath("$baseFileName-wal")
@ -72,12 +72,12 @@ open class DatabaseFactory<T : RoomDatabase>(
.build()
}
protected fun usernameForUserId(context: Context, userId: UserId): String {
private fun usernameForUserId(context: Context, userId: UserId): String {
val prefs = SecureSharedPreferences.getPrefsForUser(context, userId)
return checkNotNull(prefs.getString(Constants.Prefs.PREF_USER_NAME, null))
}
protected fun databaseName(username: String) =
private fun databaseName(username: String) =
"${Base64.encodeToString(username.toByteArray(), Base64.NO_WRAP)}-$baseDatabaseName"
@VisibleForTesting

View File

@ -57,11 +57,11 @@ import androidx.recyclerview.widget.RecyclerView
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
import ch.protonmail.android.R
import ch.protonmail.android.activities.EXTRA_FIRST_LOGIN
import ch.protonmail.android.navigation.presentation.EXTRA_FIRST_LOGIN
import ch.protonmail.android.activities.EXTRA_SETTINGS_ITEM_TYPE
import ch.protonmail.android.activities.EditSettingsItemActivity
import ch.protonmail.android.activities.EngagementActivity
import ch.protonmail.android.activities.NavigationActivity
import ch.protonmail.android.navigation.presentation.NavigationActivity
import ch.protonmail.android.activities.SettingsItem
import ch.protonmail.android.activities.StartCompose
import ch.protonmail.android.activities.StartMessageDetails
@ -86,8 +86,6 @@ import ch.protonmail.android.core.Constants.Prefs.PREF_DONT_SHOW_PLAY_SERVICES
import ch.protonmail.android.core.Constants.Prefs.PREF_SWIPE_GESTURES_DIALOG_SHOWN
import ch.protonmail.android.core.Constants.Prefs.PREF_USED_SPACE
import ch.protonmail.android.core.Constants.SWIPE_GESTURES_CHANGED_VERSION
import ch.protonmail.android.data.local.CounterDao
import ch.protonmail.android.data.local.CounterDatabase
import ch.protonmail.android.data.local.PendingActionDao
import ch.protonmail.android.data.local.PendingActionDatabase
import ch.protonmail.android.data.local.model.Message
@ -130,7 +128,6 @@ import com.squareup.otto.Subscribe
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.android.synthetic.main.activity_mailbox.*
import kotlinx.android.synthetic.main.activity_mailbox.screenShotPreventerView
import kotlinx.android.synthetic.main.activity_message_details.*
import kotlinx.android.synthetic.main.navigation_drawer.*
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flatMapLatest
@ -167,7 +164,6 @@ internal class MailboxActivity :
ActionMode.Callback,
OnRefreshListener {
private lateinit var counterDao: CounterDao
private lateinit var pendingActionDao: PendingActionDao
@Inject
@ -521,10 +517,7 @@ internal class MailboxActivity :
private var firstLogin: Boolean? = null
override fun onPrimaryUserId(userId: UserId) {
super.onPrimaryUserId(userId)
mJobManager.start()
counterDao = CounterDatabase.getInstance(this, userId).getDao()
pendingActionDao = PendingActionDatabase.getInstance(this, userId).getDao()
setUpDrawer()

View File

@ -16,7 +16,7 @@
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.activities
package ch.protonmail.android.navigation.presentation
import android.content.Intent
import android.content.res.Configuration.UI_MODE_NIGHT_MASK
@ -35,10 +35,13 @@ import androidx.lifecycle.flowWithLifecycle
import androidx.lifecycle.lifecycleScope
import ch.protonmail.android.BuildConfig
import ch.protonmail.android.R
import ch.protonmail.android.activities.BaseActivity
import ch.protonmail.android.activities.StartContacts
import ch.protonmail.android.activities.StartReportBugs
import ch.protonmail.android.activities.StartSettings
import ch.protonmail.android.api.AccountManager
import ch.protonmail.android.api.models.DatabaseProvider
import ch.protonmail.android.api.segments.event.AlarmReceiver
import ch.protonmail.android.api.segments.event.FetchUpdatesJob
import ch.protonmail.android.core.Constants
import ch.protonmail.android.core.UserManager
import ch.protonmail.android.drawer.presentation.mapper.DrawerLabelItemUiModelMapper
@ -61,6 +64,7 @@ import ch.protonmail.android.utils.extensions.setDrawBehindSystemBars
import ch.protonmail.android.utils.ui.dialogs.DialogUtils.Companion.showTwoButtonInfoDialog
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@ -115,6 +119,7 @@ internal abstract class NavigationActivity : BaseActivity() {
lateinit var drawerLabelMapper: DrawerLabelItemUiModelMapper
private val accountSwitcherViewModel by viewModels<AccountSwitcherViewModel>()
private val navigationViewModel by viewModels<NavigationViewModel>()
private val startSettingsLauncher = registerForActivityResult(StartSettings()) {}
private val startContactsLauncher = registerForActivityResult(StartContacts()) {}
@ -143,12 +148,7 @@ internal abstract class NavigationActivity : BaseActivity() {
}
}
protected open fun onPrimaryUserId(userId: UserId) {
app.startJobManager()
mJobManager.addJobInBackground(FetchUpdatesJob())
val alarmReceiver = AlarmReceiver()
alarmReceiver.setAlarm(this)
}
protected abstract fun onPrimaryUserId(userId: UserId)
protected abstract fun onInbox(type: Constants.DrawerOptionType)
@ -200,6 +200,7 @@ internal abstract class NavigationActivity : BaseActivity() {
getPrimaryUserId().filterNotNull()
.flowWithLifecycle(lifecycle, Lifecycle.State.CREATED)
.filter { userId -> navigationViewModel.verifyPrimaryUserId(userId) }
.onEach { userId -> onPrimaryUserId(userId) }
.launchIn(lifecycleScope)
}

View File

@ -0,0 +1,54 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.navigation.presentation
import androidx.lifecycle.ViewModel
import ch.protonmail.android.R
import ch.protonmail.android.core.Constants
import ch.protonmail.android.feature.account.AccountStateManager
import ch.protonmail.android.prefs.SecureSharedPreferences
import ch.protonmail.android.utils.notifier.UserNotifier
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.withContext
import me.proton.core.domain.entity.UserId
import me.proton.core.util.kotlin.DispatcherProvider
import timber.log.Timber
import javax.inject.Inject
@HiltViewModel
internal class NavigationViewModel @Inject constructor(
private val secureSharedPreferencesFactory: SecureSharedPreferences.Factory,
private val accountStateManager: AccountStateManager,
private val userNotifier: UserNotifier,
private val dispatchers: DispatcherProvider
) : ViewModel() {
suspend fun verifyPrimaryUserId(userId: UserId): Boolean = withContext(dispatchers.Io) {
val prefs = secureSharedPreferencesFactory.userPreferences(userId)
return@withContext if (prefs.getString(Constants.Prefs.PREF_USER_NAME, null) == null) {
Timber.e("Did not find username for the current primary user id. Logging the user out.")
accountStateManager.signOut(userId)
userNotifier.showError(R.string.logged_out_description)
false
} else {
true
}
}
}

View File

@ -20,7 +20,7 @@ package ch.protonmail.android.utils
import android.content.Context
import android.content.Intent
import ch.protonmail.android.activities.EXTRA_FIRST_LOGIN
import ch.protonmail.android.navigation.presentation.EXTRA_FIRST_LOGIN
import ch.protonmail.android.core.Constants
import ch.protonmail.android.core.ProtonMailApplication
import ch.protonmail.android.mailbox.presentation.MailboxActivity

View File

@ -0,0 +1,91 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.navigation.presentation
import android.content.SharedPreferences
import ch.protonmail.android.R
import ch.protonmail.android.core.Constants
import ch.protonmail.android.feature.account.AccountStateManager
import ch.protonmail.android.prefs.SecureSharedPreferences
import ch.protonmail.android.testdata.UserIdTestData
import ch.protonmail.android.utils.notifier.UserNotifier
import io.mockk.called
import io.mockk.every
import io.mockk.just
import io.mockk.mockk
import io.mockk.runs
import io.mockk.verify
import kotlinx.coroutines.test.runBlockingTest
import me.proton.core.test.android.ArchTest
import me.proton.core.test.kotlin.CoroutinesTest
import org.junit.Test
import kotlin.test.assertFalse
import kotlin.test.assertTrue
class NavigationViewModelTest : ArchTest, CoroutinesTest {
private val sharedPrefsMock = mockk<SharedPreferences>()
private val sharedPreferencesFactoryMock = mockk<SecureSharedPreferences.Factory> {
every { userPreferences(UserIdTestData.userId) } returns sharedPrefsMock
}
private val accountStateManagerMock = mockk<AccountStateManager> {
every { signOut(UserIdTestData.userId) } returns mockk()
}
private val userNotifierMock = mockk<UserNotifier> {
every { showError(R.string.logged_out_description) } just runs
}
private val navigationViewModel = NavigationViewModel(
sharedPreferencesFactoryMock,
accountStateManagerMock,
userNotifierMock,
dispatchers
)
@Test
fun `should not log out and return true if user id verified correctly`() = runBlockingTest {
// given
every { sharedPrefsMock.getString(Constants.Prefs.PREF_USER_NAME, null) } returns USERNAME
// when
val userIdVerified = navigationViewModel.verifyPrimaryUserId(UserIdTestData.userId)
// then
assertTrue(userIdVerified)
verify { accountStateManagerMock wasNot called }
}
@Test
fun `should log out, show error, and return false if user id not verified correctly`() = runBlockingTest {
// given
every { sharedPrefsMock.getString(Constants.Prefs.PREF_USER_NAME, null) } returns null
// when
val userIdVerified = navigationViewModel.verifyPrimaryUserId(UserIdTestData.userId)
// then
assertFalse(userIdVerified)
verify { accountStateManagerMock.signOut(UserIdTestData.userId) }
verify { userNotifierMock.showError(R.string.logged_out_description) }
}
private companion object TestData {
const val USERNAME = "username"
}
}