From 61910440575a8e85100759766a908afb5906fdd1 Mon Sep 17 00:00:00 2001 From: Niels van Velzen Date: Sat, 11 Mar 2023 18:19:45 +0100 Subject: [PATCH] Always check server id when retrieving user data from AccountManagerStore (cherry picked from commit 6712eeb78194dc5e9652875b206cd3bb44d58238) --- .../repository/AuthenticationRepository.kt | 6 ++-- .../auth/repository/ServerUserRepository.kt | 4 +-- .../auth/repository/SessionRepository.kt | 29 ++++++++++--------- .../auth/store/AccountManagerStore.kt | 29 +++++++++++++------ .../auth/store/AuthenticationPreferences.kt | 18 ++++++++++++ .../preference/dsl/OptionsItemUserPicker.kt | 8 +++-- .../screen/AuthPreferencesScreen.kt | 10 +++++-- 7 files changed, 72 insertions(+), 32 deletions(-) diff --git a/app/src/main/java/org/jellyfin/androidtv/auth/repository/AuthenticationRepository.kt b/app/src/main/java/org/jellyfin/androidtv/auth/repository/AuthenticationRepository.kt index ab26c049d2..2499e7ec8d 100644 --- a/app/src/main/java/org/jellyfin/androidtv/auth/repository/AuthenticationRepository.kt +++ b/app/src/main/java/org/jellyfin/androidtv/auth/repository/AuthenticationRepository.kt @@ -75,7 +75,7 @@ class AuthenticationRepositoryImpl( // Automatic logic is disabled when the always authenticate preference is enabled if (authenticationPreferences[AuthenticationPreferences.alwaysAuthenticate]) return flowOf(RequireSignInState) - val account = accountManagerStore.getAccount(user.id) + val account = accountManagerStore.getAccount(server.id, user.id) // Try login with access token return if (account?.accessToken != null) authenticateToken(server, user.withToken(account.accessToken)) // Try login without password @@ -180,7 +180,7 @@ class AuthenticationRepositoryImpl( } private suspend fun setActiveSession(user: User, server: Server): Boolean { - val authenticated = sessionRepository.switchCurrentSession(user.id) + val authenticated = sessionRepository.switchCurrentSession(server.id, user.id) if (authenticated) { // Update last use in store @@ -197,7 +197,7 @@ class AuthenticationRepositoryImpl( } override fun logout(user: User): Boolean { - val authInfo = accountManagerStore.getAccount(user.id) ?: return false + val authInfo = accountManagerStore.getAccount(user.serverId, user.id) ?: return false return accountManagerStore.removeAccount(authInfo) } diff --git a/app/src/main/java/org/jellyfin/androidtv/auth/repository/ServerUserRepository.kt b/app/src/main/java/org/jellyfin/androidtv/auth/repository/ServerUserRepository.kt index edc5b14178..e1d43de597 100644 --- a/app/src/main/java/org/jellyfin/androidtv/auth/repository/ServerUserRepository.kt +++ b/app/src/main/java/org/jellyfin/androidtv/auth/repository/ServerUserRepository.kt @@ -31,7 +31,7 @@ class ServerUserRepositoryImpl( ) : ServerUserRepository { override fun getStoredServerUsers(server: Server) = authenticationStore.getUsers(server.id) ?.mapNotNull { (userId, userInfo) -> - val authInfo = accountManagerStore.getAccount(userId) + val authInfo = accountManagerStore.getAccount(server.id, userId) PrivateUser( id = authInfo?.id ?: userId, serverId = authInfo?.server ?: server.id, @@ -64,7 +64,7 @@ class ServerUserRepositoryImpl( authenticationStore.removeUser(user.serverId, user.id) // Remove authentication info from system account manager - accountManagerStore.getAccount(user.id)?.let { accountManagerAccount -> + accountManagerStore.getAccount(user.serverId, user.id)?.let { accountManagerAccount -> accountManagerStore.removeAccount(accountManagerAccount) } } diff --git a/app/src/main/java/org/jellyfin/androidtv/auth/repository/SessionRepository.kt b/app/src/main/java/org/jellyfin/androidtv/auth/repository/SessionRepository.kt index 201f212728..faf3fcfb29 100644 --- a/app/src/main/java/org/jellyfin/androidtv/auth/repository/SessionRepository.kt +++ b/app/src/main/java/org/jellyfin/androidtv/auth/repository/SessionRepository.kt @@ -41,7 +41,7 @@ interface SessionRepository { val state: StateFlow suspend fun restoreSession() - suspend fun switchCurrentSession(userId: UUID): Boolean + suspend fun switchCurrentSession(serverId: UUID, userId: UUID): Boolean fun destroyCurrentSession() } @@ -69,26 +69,27 @@ class SessionRepositoryImpl( if (authenticationPreferences[AuthenticationPreferences.alwaysAuthenticate]) return destroyCurrentSession() - val behavior = authenticationPreferences[AuthenticationPreferences.autoLoginUserBehavior] - val userId = authenticationPreferences[AuthenticationPreferences.autoLoginUserId].toUUIDOrNull() - - when (behavior) { + when (authenticationPreferences[AuthenticationPreferences.autoLoginUserBehavior]) { DISABLED -> destroyCurrentSession() LAST_USER -> setCurrentSession(createLastUserSession()) - SPECIFIC_USER -> setCurrentSession(createUserSession(userId)) + SPECIFIC_USER -> { + val serverId = authenticationPreferences[AuthenticationPreferences.autoLoginServerId].toUUIDOrNull() + val userId = authenticationPreferences[AuthenticationPreferences.autoLoginUserId].toUUIDOrNull() + if (serverId != null && userId != null) setCurrentSession(createUserSession(serverId, userId)) + } } _state.value = SessionRepositoryState.READY } - override suspend fun switchCurrentSession(userId: UUID): Boolean { + override suspend fun switchCurrentSession(serverId: UUID, userId: UUID): Boolean { // No change in user - don't switch if (currentSession.value?.userId == userId) return false _state.value = SessionRepositoryState.SWITCHING_SESSION Timber.d("Switching current session to user $userId") - val session = createUserSession(userId) + val session = createUserSession(serverId, userId) if (session == null) { Timber.d("Could not switch to non-existing session for user $userId") return false @@ -115,6 +116,7 @@ class SessionRepositoryImpl( if (currentSession.value?.userId == session.userId) return true // Update last active user + authenticationPreferences[AuthenticationPreferences.lastServerId] = session.serverId.toString() authenticationPreferences[AuthenticationPreferences.lastUserId] = session.userId.toString() // Check if server version is supported @@ -156,13 +158,14 @@ class SessionRepositoryImpl( private fun createLastUserSession(): Session? { val lastUserId = authenticationPreferences[AuthenticationPreferences.lastUserId].toUUIDOrNull() - return createUserSession(lastUserId) - } + val lastServerId = authenticationPreferences[AuthenticationPreferences.lastServerId].toUUIDOrNull() - private fun createUserSession(userId: UUID?): Session? { - if (userId == null) return null + return if (lastUserId != null && lastServerId != null) createUserSession(lastServerId, lastUserId) + else null + } - val account = accountManagerStore.getAccount(userId) + private fun createUserSession(serverId: UUID, userId: UUID): Session? { + val account = accountManagerStore.getAccount(serverId, userId) if (account?.accessToken == null) return null return Session( diff --git a/app/src/main/java/org/jellyfin/androidtv/auth/store/AccountManagerStore.kt b/app/src/main/java/org/jellyfin/androidtv/auth/store/AccountManagerStore.kt index 8e920f1b60..feee95b77b 100644 --- a/app/src/main/java/org/jellyfin/androidtv/auth/store/AccountManagerStore.kt +++ b/app/src/main/java/org/jellyfin/androidtv/auth/store/AccountManagerStore.kt @@ -2,6 +2,7 @@ package org.jellyfin.androidtv.auth.store import android.accounts.Account import android.accounts.AccountManager +import android.os.Build import android.os.Bundle import org.jellyfin.androidtv.BuildConfig import org.jellyfin.androidtv.auth.model.AccountManagerAccount @@ -22,6 +23,13 @@ class AccountManagerStore( const val ACCOUNT_ACCESS_TOKEN_TYPE = "$ACCOUNT_TYPE.access_token" } + private fun Array.filterServerAccount(server: UUID, account: UUID? = null) = filter { + val validServerId = accountManager.getUserData(it, ACCOUNT_DATA_SERVER)?.toUUIDOrNull() == server + val validUserId = account == null || accountManager.getUserData(it, ACCOUNT_DATA_ID)?.toUUIDOrNull() == account + + validServerId && validUserId + } + private fun getAccountData(account: Account): AccountManagerAccount = AccountManagerAccount( id = accountManager.getUserData(account, ACCOUNT_DATA_ID).toUUID(), server = accountManager.getUserData(account, ACCOUNT_DATA_SERVER).toUUID(), @@ -31,7 +39,8 @@ class AccountManagerStore( suspend fun putAccount(accountManagerAccount: AccountManagerAccount) { var androidAccount = accountManager.getAccountsByType(ACCOUNT_TYPE) - .firstOrNull { accountManager.getUserData(it, ACCOUNT_DATA_ID)?.toUUIDOrNull() == accountManagerAccount.id } + .filterServerAccount(accountManagerAccount.server, accountManagerAccount.id) + .firstOrNull() // Update credentials if (androidAccount == null) { @@ -59,12 +68,13 @@ class AccountManagerStore( fun removeAccount(accountManagerAccount: AccountManagerAccount): Boolean { val androidAccount = accountManager.getAccountsByType(ACCOUNT_TYPE) - .firstOrNull { accountManager.getUserData(it, ACCOUNT_DATA_ID)?.toUUIDOrNull() == accountManagerAccount.id } + .filterServerAccount(accountManagerAccount.server, accountManagerAccount.id) + .firstOrNull() ?: return false // Remove current account info @Suppress("DEPRECATION") - return if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.LOLLIPOP_MR1) { + return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP_MR1) { accountManager.removeAccount(androidAccount, null, null) true } else accountManager.removeAccountExplicitly(androidAccount) @@ -72,11 +82,12 @@ class AccountManagerStore( fun getAccounts() = accountManager.getAccountsByType(ACCOUNT_TYPE).map(::getAccountData) - fun getAccountsByServer(server: UUID) = accountManager.getAccountsByType(ACCOUNT_TYPE).filter { account -> - accountManager.getUserData(account, ACCOUNT_DATA_SERVER)?.toUUIDOrNull() == server - }.map(::getAccountData) + fun getAccountsByServer(server: UUID) = accountManager.getAccountsByType(ACCOUNT_TYPE) + .filterServerAccount(server) + .map(::getAccountData) - fun getAccount(id: UUID) = accountManager.getAccountsByType(ACCOUNT_TYPE).firstOrNull { account -> - accountManager.getUserData(account, ACCOUNT_DATA_ID)?.toUUIDOrNull() == id - }?.let(::getAccountData) + fun getAccount(server: UUID, account: UUID) = accountManager.getAccountsByType(ACCOUNT_TYPE) + .filterServerAccount(server, account) + .firstOrNull() + ?.let(::getAccountData) } diff --git a/app/src/main/java/org/jellyfin/androidtv/auth/store/AuthenticationPreferences.kt b/app/src/main/java/org/jellyfin/androidtv/auth/store/AuthenticationPreferences.kt index 4d26fd2009..54fdb92ec9 100644 --- a/app/src/main/java/org/jellyfin/androidtv/auth/store/AuthenticationPreferences.kt +++ b/app/src/main/java/org/jellyfin/androidtv/auth/store/AuthenticationPreferences.kt @@ -14,12 +14,30 @@ class AuthenticationPreferences(context: Context) : SharedPreferenceStore( companion object { // Preferences val autoLoginUserBehavior = enumPreference("auto_login_user_behavior", UserSelectBehavior.LAST_USER) + val autoLoginServerId = stringPreference("auto_login_server_id", "") val autoLoginUserId = stringPreference("auto_login_user_id", "") val sortBy = enumPreference("sort_by", AuthenticationSortBy.LAST_USE) val alwaysAuthenticate = booleanPreference("always_authenticate", false) // Persistent state + val lastServerId = stringPreference("last_server_id", "") val lastUserId = stringPreference("last_user_id", "") } + + init { + runMigrations { + // v0.15.4 to v0.15.5 + migration(toVersion = 2) { + // Unfortunately we cannot migrate the "specific user" login option + // so we'll reset the preference to disabled if it was used + if (it.getString("auto_login_user_behavior", null) === UserSelectBehavior.SPECIFIC_USER.name) { + putString("auto_login_user_id", "") + putString("auto_login_user_behavior", UserSelectBehavior.DISABLED.name) + } + + putString("last_user_id", "") + } + } + } } diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsItemUserPicker.kt b/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsItemUserPicker.kt index 65cd0854a7..20b355e5ff 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsItemUserPicker.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/preference/dsl/OptionsItemUserPicker.kt @@ -33,6 +33,7 @@ class OptionsItemUserPicker( private fun MutableList>.add( behavior: UserSelectBehavior, + serverId: UUID? = null, userId: UUID? = null, title: String, summary: String @@ -40,7 +41,8 @@ class OptionsItemUserPicker( RichListOption( UserSelection( behavior = behavior, - userId = userId + serverId = serverId, + userId = userId, ), title, summary @@ -70,6 +72,7 @@ class OptionsItemUserPicker( for (user in users) add( behavior = UserSelectBehavior.SPECIFIC_USER, + serverId = user.serverId, userId = user.id, title = user.name, summary = context.getString( @@ -111,7 +114,8 @@ class OptionsItemUserPicker( data class UserSelection( val behavior: UserSelectBehavior, - val userId: UUID? + val serverId: UUID?, + val userId: UUID?, ) } diff --git a/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/AuthPreferencesScreen.kt b/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/AuthPreferencesScreen.kt index 16e171b85a..b2eb2c9b18 100644 --- a/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/AuthPreferencesScreen.kt +++ b/app/src/main/java/org/jellyfin/androidtv/ui/preference/screen/AuthPreferencesScreen.kt @@ -61,7 +61,8 @@ class AuthPreferencesScreen : OptionsFragment() { from( authenticationPreferences, AuthenticationPreferences.autoLoginUserBehavior, - AuthenticationPreferences.autoLoginUserId + AuthenticationPreferences.autoLoginServerId, + AuthenticationPreferences.autoLoginUserId, ) } @@ -117,25 +118,28 @@ class AuthPreferencesScreen : OptionsFragment() { private fun OptionsBinder.Builder.from( authenticationPreferences: AuthenticationPreferences, userBehaviorPreference: Preference, + serverIdPreference: Preference, userIdPreference: Preference, onSet: ((OptionsItemUserPicker.UserSelection) -> Unit)? = null, ) { get { OptionsItemUserPicker.UserSelection( authenticationPreferences[userBehaviorPreference], - authenticationPreferences[userIdPreference].toUUIDOrNull() + authenticationPreferences[serverIdPreference].toUUIDOrNull(), + authenticationPreferences[userIdPreference].toUUIDOrNull(), ) } set { authenticationPreferences[userBehaviorPreference] = it.behavior + authenticationPreferences[serverIdPreference] = it.serverId?.toString().orEmpty() authenticationPreferences[userIdPreference] = it.userId?.toString().orEmpty() onSet?.invoke(it) } default { - OptionsItemUserPicker.UserSelection(UserSelectBehavior.LAST_USER, null) + OptionsItemUserPicker.UserSelection(UserSelectBehavior.LAST_USER, null, null) } }