Skip to content

Commit

Permalink
Always check server id when retrieving user data from AccountManagerS…
Browse files Browse the repository at this point in the history
…tore

(cherry picked from commit 6712eeb)
  • Loading branch information
nielsvanvelzen committed Mar 12, 2023
1 parent d0365ce commit 6191044
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 32 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand All @@ -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)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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)
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ interface SessionRepository {
val state: StateFlow<SessionRepositoryState>

suspend fun restoreSession()
suspend fun switchCurrentSession(userId: UUID): Boolean
suspend fun switchCurrentSession(serverId: UUID, userId: UUID): Boolean
fun destroyCurrentSession()
}

Expand Down Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -22,6 +23,13 @@ class AccountManagerStore(
const val ACCOUNT_ACCESS_TOKEN_TYPE = "$ACCOUNT_TYPE.access_token"
}

private fun Array<Account>.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(),
Expand All @@ -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) {
Expand Down Expand Up @@ -59,24 +68,26 @@ 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)
}

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)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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", "")
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,16 @@ class OptionsItemUserPicker(

private fun MutableList<RichListItem<UserSelection>>.add(
behavior: UserSelectBehavior,
serverId: UUID? = null,
userId: UUID? = null,
title: String,
summary: String
) = add(
RichListOption(
UserSelection(
behavior = behavior,
userId = userId
serverId = serverId,
userId = userId,
),
title,
summary
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -111,7 +114,8 @@ class OptionsItemUserPicker(

data class UserSelection(
val behavior: UserSelectBehavior,
val userId: UUID?
val serverId: UUID?,
val userId: UUID?,
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,8 @@ class AuthPreferencesScreen : OptionsFragment() {
from(
authenticationPreferences,
AuthenticationPreferences.autoLoginUserBehavior,
AuthenticationPreferences.autoLoginUserId
AuthenticationPreferences.autoLoginServerId,
AuthenticationPreferences.autoLoginUserId,
)
}

Expand Down Expand Up @@ -117,25 +118,28 @@ class AuthPreferencesScreen : OptionsFragment() {
private fun OptionsBinder.Builder<OptionsItemUserPicker.UserSelection>.from(
authenticationPreferences: AuthenticationPreferences,
userBehaviorPreference: Preference<UserSelectBehavior>,
serverIdPreference: Preference<String>,
userIdPreference: Preference<String>,
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)
}
}

Expand Down

0 comments on commit 6191044

Please sign in to comment.