Loading app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionActivity.kt +46 −19 Original line number Diff line number Diff line Loading @@ -26,10 +26,17 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import at.bitfire.davdroid.R import com.nextcloud.android.sso.Constants import at.bitfire.davdroid.log.Logger import com.nextcloud.android.sso.Constants.EXCEPTION_ACCOUNT_ACCESS_DECLINED import com.nextcloud.android.sso.Constants.NEXTCLOUD_FILES_ACCOUNT import com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO import com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO_EXCEPTION import com.owncloud.android.ui.activity.SsoPermissionGrantResult.PermissionDenied import com.owncloud.android.ui.activity.SsoPermissionGrantResult.PermissionGranted import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import java.util.logging.Level @AndroidEntryPoint class SsoGrantPermissionActivity : AppCompatActivity() { Loading @@ -41,46 +48,66 @@ class SsoGrantPermissionActivity : AppCompatActivity() { setContentView(R.layout.activity_sso_grant_permission) lifecycleScope.launch { viewModel.event viewModel.permissionGrantResult .flowWithLifecycle( lifecycle = lifecycle, minActiveState = Lifecycle.State.CREATED ).collectLatest { when (it) { is SsoGrantPermissionEvent.PermissionGranted -> setSuccessResult(it.bundle) is SsoGrantPermissionEvent.PermissionDenied -> setCanceledResult(it.errorMessage) is PermissionGranted -> setSuccessResult(it.extraData) is PermissionDenied -> setCanceledResult(it.errorMessage) } } } initiateValidation() processNextCloudAccount() } private fun initiateValidation() { val account: Account? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { intent.getParcelableExtra(Constants.NEXTCLOUD_FILES_ACCOUNT, Account::class.java) } else { intent.getParcelableExtra(Constants.NEXTCLOUD_FILES_ACCOUNT) private fun processNextCloudAccount() { val account = getNextCloudFilesAccount() if (account == null) { Logger.log.log(Level.SEVERE, "SsoGrantPermissionViewModel: Invalid request") setCanceledResult(EXCEPTION_ACCOUNT_ACCESS_DECLINED) return } viewModel.initValidation( callingActivity = callingActivity, account = account if (callingActivity == null) { Logger.log.log(Level.SEVERE, "SsoGrantPermissionViewModel: Calling Package is null") setCanceledResult(EXCEPTION_ACCOUNT_ACCESS_DECLINED) return } viewModel.processNextCloudAccount( account = account, packageName = callingActivity!!.packageName ) } private fun getNextCloudFilesAccount(): Account? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { intent.getParcelableExtra(NEXTCLOUD_FILES_ACCOUNT, Account::class.java) } else { intent.getParcelableExtra(NEXTCLOUD_FILES_ACCOUNT) } private fun setCanceledResult(exception: String) { val data = Intent() data.putExtra(Constants.NEXTCLOUD_SSO_EXCEPTION, exception) data.putExtra(NEXTCLOUD_SSO_EXCEPTION, exception) setResult(RESULT_CANCELED, data) finish() } private fun setSuccessResult(result: Bundle) { private fun setSuccessResult(extraData: Map<String, String>) { val bundle = Bundle() extraData.forEach { bundle.putString(it.key, it.value) } val data = Intent() data.putExtra(Constants.NEXTCLOUD_SSO, result) data.putExtra(NEXTCLOUD_SSO, bundle) setResult(RESULT_OK, data) finish() } } app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionViewModel.kt +52 −84 Original line number Diff line number Diff line Loading @@ -18,9 +18,7 @@ package com.owncloud.android.ui.activity import android.accounts.Account import android.accounts.AccountManager import android.content.ComponentName import android.content.Context import android.os.Bundle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import at.bitfire.davdroid.R Loading @@ -31,6 +29,7 @@ import com.nextcloud.android.sso.Constants import com.nextcloud.android.utils.EncryptionUtils import com.owncloud.android.lib.common.OwnCloudAccount import com.owncloud.android.lib.common.accounts.AccountUtils import com.owncloud.android.ui.activity.SsoPermissionGrantResult.PermissionGranted import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers Loading @@ -38,7 +37,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import java.util.UUID import java.util.logging.Level import java.util.logging.Level.SEVERE import javax.inject.Inject @HiltViewModel Loading @@ -47,84 +46,56 @@ class SsoGrantPermissionViewModel @Inject constructor( private val database: AppDatabase, ) : ViewModel() { private val acceptedAccountTypes = listOf(context.getString(R.string.eelo_account_type)) private val acceptedAccountTypes = listOf(context.applicationContext.getString(R.string.eelo_account_type)) private val acceptedPackages = listOf("foundation.e.notes") private val _event = MutableSharedFlow<SsoGrantPermissionEvent>() val event = _event.asSharedFlow() private val _permissionGrantResult = MutableSharedFlow<SsoPermissionGrantResult>() val permissionGrantResult = _permissionGrantResult.asSharedFlow() fun initValidation(callingActivity: ComponentName?, account: Account?) { viewModelScope.launch(Dispatchers.IO) { val packageName = getCallingPackageName(callingActivity) ?: return@launch validate(packageName, account) } } private suspend fun getCallingPackageName(callingActivity: ComponentName?): String? { if (callingActivity == null) { log.log(Level.SEVERE, "SsoGrantPermissionViewModel: Calling Package is null") _event.emit( SsoGrantPermissionEvent.PermissionDenied( errorMessage = Constants.EXCEPTION_ACCOUNT_ACCESS_DECLINED ) ) return null } return callingActivity.packageName } fun processNextCloudAccount(account: Account, packageName: String) { val isValidPackage = acceptedPackages.contains(packageName) val isValidAccountType = acceptedAccountTypes.contains(account.type) private suspend fun validate(packageName: String?, account: Account?) { if (!isValidRequest(packageName, account)) { log.log(Level.SEVERE, "SsoGrantPermissionViewModel: Invalid request") _event.emit( SsoGrantPermissionEvent.PermissionDenied( errorMessage = Constants.EXCEPTION_ACCOUNT_ACCESS_DECLINED ) ) } val serverUrl = getServerUrl(account!!) ?: return val token = UUID.randomUUID().toString().replace("-".toRegex(), "") viewModelScope.launch(Dispatchers.IO) { try { if (isValidPackage && isValidAccountType) { val token = getToken() val userId = getUserId(account) saveToken(token = token, accountName = userId, packageName = packageName) saveToken( token = token, accountName = userId, packageName = packageName!! ) passSuccessfulData( val extraData = getExtraData( account = account, token = token, userId = userId, serverUrl = serverUrl serverUrl = getServerUrl(account) ) updatePermissionGrantedState(extraData) } } catch (e: AccountUtils.AccountNotFoundException) { log.log(SEVERE, "SsoGrantPermissionViewModel: Account not found") updatePermissionDeniedState() } } private fun isValidRequest(packageName: String?, account: Account?): Boolean { if (packageName == null || account == null) { return false } return acceptedPackages.contains(packageName) && acceptedAccountTypes.contains(account.type) private suspend fun updatePermissionGrantedState(extraData: Map<String, String>) { _permissionGrantResult.emit(PermissionGranted(extraData)) } private suspend fun getServerUrl(account: Account): String? { try { val ocAccount = OwnCloudAccount(account, context) return ocAccount.baseUri.toString() } catch (e: AccountUtils.AccountNotFoundException) { log.log(Level.SEVERE, "SsoGrantPermissionViewModel: Account not found") _event.emit( SsoGrantPermissionEvent.PermissionDenied( private suspend fun updatePermissionDeniedState() { _permissionGrantResult.emit( SsoPermissionGrantResult.PermissionDenied( errorMessage = Constants.EXCEPTION_ACCOUNT_NOT_FOUND ) ) } return null private fun getToken() = UUID.randomUUID().toString().replace("-".toRegex(), "") private fun getServerUrl(account: Account): String { val ocAccount = OwnCloudAccount(account, context) return ocAccount.baseUri.toString() } private fun getUserId(account: Account): String { Loading @@ -139,7 +110,7 @@ class SsoGrantPermissionViewModel @Inject constructor( database.serviceDao().getByAccountName(account.name)?.principal?.toString() ?: return account.name return UserIdFetcher.retrieveUserId(principalUrl) ?: account.name return UserIdFetcher.fetch(principalUrl) ?: account.name } private fun saveToken(token: String, accountName: String, packageName: String) { Loading @@ -151,24 +122,21 @@ class SsoGrantPermissionViewModel @Inject constructor( editor.apply() } private suspend fun passSuccessfulData( private fun getExtraData( account: Account, token: String, userId: String, serverUrl: String ) { val result = Bundle() result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name) result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type) result.putString(AccountManager.KEY_AUTHTOKEN, Constants.NEXTCLOUD_SSO) result.putString(Constants.SSO_USER_ID, userId) result.putString(Constants.SSO_TOKEN, token) result.putString(Constants.SSO_SERVER_URL, serverUrl) _event.emit( SsoGrantPermissionEvent.PermissionGranted( bundle = result ) ) ): Map<String, String> { val extraData = mutableMapOf<String, String>().apply { put(AccountManager.KEY_ACCOUNT_NAME, account.name) put(AccountManager.KEY_ACCOUNT_TYPE, account.type) put(AccountManager.KEY_AUTHTOKEN, Constants.NEXTCLOUD_SSO) put(Constants.SSO_USER_ID, userId) put(Constants.SSO_TOKEN, token) put(Constants.SSO_SERVER_URL, serverUrl) } return extraData } } app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionEvent.kt→app/src/main/java/com/owncloud/android/ui/activity/SsoPermissionGrantResult.kt +3 −5 Original line number Diff line number Diff line Loading @@ -16,11 +16,9 @@ package com.owncloud.android.ui.activity import android.os.Bundle sealed class SsoPermissionGrantResult { sealed class SsoGrantPermissionEvent { data class PermissionGranted(val extraData: Map<String, String>) : SsoPermissionGrantResult() data class PermissionGranted(val bundle: Bundle) : SsoGrantPermissionEvent() data class PermissionDenied(val errorMessage: String) : SsoGrantPermissionEvent() data class PermissionDenied(val errorMessage: String) : SsoPermissionGrantResult() } app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt +1 −1 Original line number Diff line number Diff line Loading @@ -170,7 +170,7 @@ class AccountSettings( } private fun addUserIdToBundle(bundle: Bundle, userName: String?, url: String?) { val userId = UserIdFetcher.retrieveUserId(url) ?: userName val userId = UserIdFetcher.fetch(url) ?: userName userId?.let { bundle.putString(NCAccountUtils.Constants.KEY_USER_ID, userId) Loading app/src/main/kotlin/at/bitfire/davdroid/util/UserIdFetcher.kt +1 −1 Original line number Diff line number Diff line Loading @@ -26,7 +26,7 @@ object UserIdFetcher { * 1. provided url is null * 2. `/users/` part is missing */ fun retrieveUserId(principalUrl: String?): String? { fun fetch(principalUrl: String?): String? { if (principalUrl == null) { return null } Loading Loading
app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionActivity.kt +46 −19 Original line number Diff line number Diff line Loading @@ -26,10 +26,17 @@ import androidx.lifecycle.Lifecycle import androidx.lifecycle.flowWithLifecycle import androidx.lifecycle.lifecycleScope import at.bitfire.davdroid.R import com.nextcloud.android.sso.Constants import at.bitfire.davdroid.log.Logger import com.nextcloud.android.sso.Constants.EXCEPTION_ACCOUNT_ACCESS_DECLINED import com.nextcloud.android.sso.Constants.NEXTCLOUD_FILES_ACCOUNT import com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO import com.nextcloud.android.sso.Constants.NEXTCLOUD_SSO_EXCEPTION import com.owncloud.android.ui.activity.SsoPermissionGrantResult.PermissionDenied import com.owncloud.android.ui.activity.SsoPermissionGrantResult.PermissionGranted import dagger.hilt.android.AndroidEntryPoint import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.launch import java.util.logging.Level @AndroidEntryPoint class SsoGrantPermissionActivity : AppCompatActivity() { Loading @@ -41,46 +48,66 @@ class SsoGrantPermissionActivity : AppCompatActivity() { setContentView(R.layout.activity_sso_grant_permission) lifecycleScope.launch { viewModel.event viewModel.permissionGrantResult .flowWithLifecycle( lifecycle = lifecycle, minActiveState = Lifecycle.State.CREATED ).collectLatest { when (it) { is SsoGrantPermissionEvent.PermissionGranted -> setSuccessResult(it.bundle) is SsoGrantPermissionEvent.PermissionDenied -> setCanceledResult(it.errorMessage) is PermissionGranted -> setSuccessResult(it.extraData) is PermissionDenied -> setCanceledResult(it.errorMessage) } } } initiateValidation() processNextCloudAccount() } private fun initiateValidation() { val account: Account? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { intent.getParcelableExtra(Constants.NEXTCLOUD_FILES_ACCOUNT, Account::class.java) } else { intent.getParcelableExtra(Constants.NEXTCLOUD_FILES_ACCOUNT) private fun processNextCloudAccount() { val account = getNextCloudFilesAccount() if (account == null) { Logger.log.log(Level.SEVERE, "SsoGrantPermissionViewModel: Invalid request") setCanceledResult(EXCEPTION_ACCOUNT_ACCESS_DECLINED) return } viewModel.initValidation( callingActivity = callingActivity, account = account if (callingActivity == null) { Logger.log.log(Level.SEVERE, "SsoGrantPermissionViewModel: Calling Package is null") setCanceledResult(EXCEPTION_ACCOUNT_ACCESS_DECLINED) return } viewModel.processNextCloudAccount( account = account, packageName = callingActivity!!.packageName ) } private fun getNextCloudFilesAccount(): Account? = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { intent.getParcelableExtra(NEXTCLOUD_FILES_ACCOUNT, Account::class.java) } else { intent.getParcelableExtra(NEXTCLOUD_FILES_ACCOUNT) } private fun setCanceledResult(exception: String) { val data = Intent() data.putExtra(Constants.NEXTCLOUD_SSO_EXCEPTION, exception) data.putExtra(NEXTCLOUD_SSO_EXCEPTION, exception) setResult(RESULT_CANCELED, data) finish() } private fun setSuccessResult(result: Bundle) { private fun setSuccessResult(extraData: Map<String, String>) { val bundle = Bundle() extraData.forEach { bundle.putString(it.key, it.value) } val data = Intent() data.putExtra(Constants.NEXTCLOUD_SSO, result) data.putExtra(NEXTCLOUD_SSO, bundle) setResult(RESULT_OK, data) finish() } }
app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionViewModel.kt +52 −84 Original line number Diff line number Diff line Loading @@ -18,9 +18,7 @@ package com.owncloud.android.ui.activity import android.accounts.Account import android.accounts.AccountManager import android.content.ComponentName import android.content.Context import android.os.Bundle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import at.bitfire.davdroid.R Loading @@ -31,6 +29,7 @@ import com.nextcloud.android.sso.Constants import com.nextcloud.android.utils.EncryptionUtils import com.owncloud.android.lib.common.OwnCloudAccount import com.owncloud.android.lib.common.accounts.AccountUtils import com.owncloud.android.ui.activity.SsoPermissionGrantResult.PermissionGranted import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers Loading @@ -38,7 +37,7 @@ import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.launch import java.util.UUID import java.util.logging.Level import java.util.logging.Level.SEVERE import javax.inject.Inject @HiltViewModel Loading @@ -47,84 +46,56 @@ class SsoGrantPermissionViewModel @Inject constructor( private val database: AppDatabase, ) : ViewModel() { private val acceptedAccountTypes = listOf(context.getString(R.string.eelo_account_type)) private val acceptedAccountTypes = listOf(context.applicationContext.getString(R.string.eelo_account_type)) private val acceptedPackages = listOf("foundation.e.notes") private val _event = MutableSharedFlow<SsoGrantPermissionEvent>() val event = _event.asSharedFlow() private val _permissionGrantResult = MutableSharedFlow<SsoPermissionGrantResult>() val permissionGrantResult = _permissionGrantResult.asSharedFlow() fun initValidation(callingActivity: ComponentName?, account: Account?) { viewModelScope.launch(Dispatchers.IO) { val packageName = getCallingPackageName(callingActivity) ?: return@launch validate(packageName, account) } } private suspend fun getCallingPackageName(callingActivity: ComponentName?): String? { if (callingActivity == null) { log.log(Level.SEVERE, "SsoGrantPermissionViewModel: Calling Package is null") _event.emit( SsoGrantPermissionEvent.PermissionDenied( errorMessage = Constants.EXCEPTION_ACCOUNT_ACCESS_DECLINED ) ) return null } return callingActivity.packageName } fun processNextCloudAccount(account: Account, packageName: String) { val isValidPackage = acceptedPackages.contains(packageName) val isValidAccountType = acceptedAccountTypes.contains(account.type) private suspend fun validate(packageName: String?, account: Account?) { if (!isValidRequest(packageName, account)) { log.log(Level.SEVERE, "SsoGrantPermissionViewModel: Invalid request") _event.emit( SsoGrantPermissionEvent.PermissionDenied( errorMessage = Constants.EXCEPTION_ACCOUNT_ACCESS_DECLINED ) ) } val serverUrl = getServerUrl(account!!) ?: return val token = UUID.randomUUID().toString().replace("-".toRegex(), "") viewModelScope.launch(Dispatchers.IO) { try { if (isValidPackage && isValidAccountType) { val token = getToken() val userId = getUserId(account) saveToken(token = token, accountName = userId, packageName = packageName) saveToken( token = token, accountName = userId, packageName = packageName!! ) passSuccessfulData( val extraData = getExtraData( account = account, token = token, userId = userId, serverUrl = serverUrl serverUrl = getServerUrl(account) ) updatePermissionGrantedState(extraData) } } catch (e: AccountUtils.AccountNotFoundException) { log.log(SEVERE, "SsoGrantPermissionViewModel: Account not found") updatePermissionDeniedState() } } private fun isValidRequest(packageName: String?, account: Account?): Boolean { if (packageName == null || account == null) { return false } return acceptedPackages.contains(packageName) && acceptedAccountTypes.contains(account.type) private suspend fun updatePermissionGrantedState(extraData: Map<String, String>) { _permissionGrantResult.emit(PermissionGranted(extraData)) } private suspend fun getServerUrl(account: Account): String? { try { val ocAccount = OwnCloudAccount(account, context) return ocAccount.baseUri.toString() } catch (e: AccountUtils.AccountNotFoundException) { log.log(Level.SEVERE, "SsoGrantPermissionViewModel: Account not found") _event.emit( SsoGrantPermissionEvent.PermissionDenied( private suspend fun updatePermissionDeniedState() { _permissionGrantResult.emit( SsoPermissionGrantResult.PermissionDenied( errorMessage = Constants.EXCEPTION_ACCOUNT_NOT_FOUND ) ) } return null private fun getToken() = UUID.randomUUID().toString().replace("-".toRegex(), "") private fun getServerUrl(account: Account): String { val ocAccount = OwnCloudAccount(account, context) return ocAccount.baseUri.toString() } private fun getUserId(account: Account): String { Loading @@ -139,7 +110,7 @@ class SsoGrantPermissionViewModel @Inject constructor( database.serviceDao().getByAccountName(account.name)?.principal?.toString() ?: return account.name return UserIdFetcher.retrieveUserId(principalUrl) ?: account.name return UserIdFetcher.fetch(principalUrl) ?: account.name } private fun saveToken(token: String, accountName: String, packageName: String) { Loading @@ -151,24 +122,21 @@ class SsoGrantPermissionViewModel @Inject constructor( editor.apply() } private suspend fun passSuccessfulData( private fun getExtraData( account: Account, token: String, userId: String, serverUrl: String ) { val result = Bundle() result.putString(AccountManager.KEY_ACCOUNT_NAME, account.name) result.putString(AccountManager.KEY_ACCOUNT_TYPE, account.type) result.putString(AccountManager.KEY_AUTHTOKEN, Constants.NEXTCLOUD_SSO) result.putString(Constants.SSO_USER_ID, userId) result.putString(Constants.SSO_TOKEN, token) result.putString(Constants.SSO_SERVER_URL, serverUrl) _event.emit( SsoGrantPermissionEvent.PermissionGranted( bundle = result ) ) ): Map<String, String> { val extraData = mutableMapOf<String, String>().apply { put(AccountManager.KEY_ACCOUNT_NAME, account.name) put(AccountManager.KEY_ACCOUNT_TYPE, account.type) put(AccountManager.KEY_AUTHTOKEN, Constants.NEXTCLOUD_SSO) put(Constants.SSO_USER_ID, userId) put(Constants.SSO_TOKEN, token) put(Constants.SSO_SERVER_URL, serverUrl) } return extraData } }
app/src/main/java/com/owncloud/android/ui/activity/SsoGrantPermissionEvent.kt→app/src/main/java/com/owncloud/android/ui/activity/SsoPermissionGrantResult.kt +3 −5 Original line number Diff line number Diff line Loading @@ -16,11 +16,9 @@ package com.owncloud.android.ui.activity import android.os.Bundle sealed class SsoPermissionGrantResult { sealed class SsoGrantPermissionEvent { data class PermissionGranted(val extraData: Map<String, String>) : SsoPermissionGrantResult() data class PermissionGranted(val bundle: Bundle) : SsoGrantPermissionEvent() data class PermissionDenied(val errorMessage: String) : SsoGrantPermissionEvent() data class PermissionDenied(val errorMessage: String) : SsoPermissionGrantResult() }
app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt +1 −1 Original line number Diff line number Diff line Loading @@ -170,7 +170,7 @@ class AccountSettings( } private fun addUserIdToBundle(bundle: Bundle, userName: String?, url: String?) { val userId = UserIdFetcher.retrieveUserId(url) ?: userName val userId = UserIdFetcher.fetch(url) ?: userName userId?.let { bundle.putString(NCAccountUtils.Constants.KEY_USER_ID, userId) Loading
app/src/main/kotlin/at/bitfire/davdroid/util/UserIdFetcher.kt +1 −1 Original line number Diff line number Diff line Loading @@ -26,7 +26,7 @@ object UserIdFetcher { * 1. provided url is null * 2. `/users/` part is missing */ fun retrieveUserId(principalUrl: String?): String? { fun fetch(principalUrl: String?): String? { if (principalUrl == null) { return null } Loading