Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Verified Commit 0b8abd4e authored by Fahim M. Choudhury's avatar Fahim M. Choudhury
Browse files

refactor: remove null handling and validation flow

parent 5ca97328
Loading
Loading
Loading
Loading
Loading
+46 −19
Original line number Diff line number Diff line
@@ -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() {
@@ -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()
    }
}
+52 −84
Original line number Diff line number Diff line
@@ -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
@@ -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
@@ -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
@@ -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 {
@@ -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) {
@@ -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
    }
}
+3 −5
Original line number Diff line number Diff line
@@ -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()
}
+1 −1
Original line number Diff line number Diff line
@@ -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)
+1 −1
Original line number Diff line number Diff line
@@ -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