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

Commit a6a658b4 authored by Fahim Salam Chowdhury's avatar Fahim Salam Chowdhury 👽
Browse files

Fix openId account login via mail app failed issue

If user wants to login to openId acocunt (ex: google, yahoo), it jumps
from mail app -> accountManager app -> browser app on the authorization
stage. On this jumping causes accountManager to be cleared out, so
`onActivityResult` is failed to be called perfectly. To resolve this,
insead of calling via onActivityResult, we pass pendingIntntent to
AppAuth library to re-open accountManager when the authorization stage
from browser is done.
parent 5c0a3d1f
Loading
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -38,6 +38,7 @@ class LoginActivity: AppCompatActivity() {
         */
        const val EXTRA_PASSWORD = "password"

        const val AUTH_STATE = "authState"
        const val ACCOUNT_TYPE = "account_type"
        const val OPENID_AUTH_FLOW_COMPLETE = "openId_authFlow_complete"

+23 −0
Original line number Diff line number Diff line
/*
 * Copyright MURENA SAS 2023
 * This program 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.
 *
 * This program 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 this program.  If not, see <https://www.gnu.org/licenses/>.
 */

package at.bitfire.davdroid.ui.setup

enum class OpenIdAuthStateSetupState {
    ALREADY_SET_UP,
    SET_UP_FAILED,
    SET_UP_SUCCEED
}
+34 −21
Original line number Diff line number Diff line
@@ -16,13 +16,12 @@

package at.bitfire.davdroid.ui.setup

import android.app.Activity
import android.os.Bundle
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.activity.result.ActivityResult
import androidx.activity.result.contract.ActivityResultContracts
import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.viewModels
@@ -49,23 +48,6 @@ abstract class OpenIdAuthenticationBaseFragment(private val identityProvider: Id
    private val viewModel by viewModels<OpenIdAuthenticationViewModel>()
    private val loginModel by activityViewModels<LoginModel>()

    private val authReqActivityResultLauncher =
        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result: ActivityResult ->
            val intent = result.data
            intent?.let {
                val response = AuthorizationResponse.fromIntent(it)
                val exception = AuthorizationException.fromIntent(it)

                if (response == null || exception != null) {
                    Logger.log.log(Level.SEVERE, "Failed to retrieve auth response", exception)
                    handleLoginFailedToast()
                    return@let
                }

                performTokenRequest(response, exception)
            }
        }

    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
@@ -76,6 +58,36 @@ abstract class OpenIdAuthenticationBaseFragment(private val identityProvider: Id
        return inflater.inflate(R.layout.frament_openid_auth, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        if (isAuthFlowComplete()) {
            val setupState = viewModel.setUpAuthState(requireActivity().intent)
            if (setupState == OpenIdAuthStateSetupState.ALREADY_SET_UP) {
                return
            }

            if (setupState == OpenIdAuthStateSetupState.SET_UP_FAILED) {
                handleLoginFailedToast()
                return
            }

            extractAuthCodeResponse()
        }
    }

    private fun extractAuthCodeResponse() {
        val response = AuthorizationResponse.fromIntent(requireActivity().intent)
        val exception = AuthorizationException.fromIntent(requireActivity().intent)

        if (response == null || exception != null) {
            Logger.log.log(Level.SEVERE, "Failed to retrieve auth response", exception)
            handleLoginFailedToast()
            return
        }

        performTokenRequest(response, exception)
    }

    protected fun isAuthFlowComplete(): Boolean {
        return activity?.intent?.getBooleanExtra(
            LoginActivity.OPENID_AUTH_FLOW_COMPLETE,
@@ -108,8 +120,9 @@ abstract class OpenIdAuthenticationBaseFragment(private val identityProvider: Id
            return
        }

        val authIntent = viewModel.getAuthIntent(serviceConfiguration)
        authReqActivityResultLauncher.launch(authIntent)
        viewModel.requestAuthCode(serviceConfiguration, requireActivity().intent)
        requireActivity().setResult(Activity.RESULT_OK)
        requireActivity().finish()
    }

    private fun performTokenRequest(
+67 −6
Original line number Diff line number Diff line
@@ -17,14 +17,24 @@
package at.bitfire.davdroid.ui.setup

import android.app.Application
import android.app.PendingIntent
import android.content.Intent
import android.os.Build
import androidx.annotation.WorkerThread
import androidx.lifecycle.AndroidViewModel
import at.bitfire.davdroid.authorization.IdentityProvider
import at.bitfire.davdroid.log.Logger
import net.openid.appauth.*
import net.openid.appauth.AuthState
import net.openid.appauth.AuthState.AuthStateAction
import net.openid.appauth.AuthorizationException
import net.openid.appauth.AuthorizationRequest
import net.openid.appauth.AuthorizationResponse
import net.openid.appauth.AuthorizationService
import net.openid.appauth.AuthorizationService.TokenResponseCallback
import net.openid.appauth.AuthorizationServiceConfiguration
import net.openid.appauth.ClientSecretBasic
import net.openid.appauth.ResponseTypeValues
import net.openid.appauth.TokenResponse
import okio.buffer
import okio.source
import org.json.JSONObject
@@ -48,19 +58,69 @@ class OpenIdAuthenticationViewModel(application: Application) : AndroidViewModel
        return authState!!
    }

    fun getAuthIntent(serviceConfiguration: AuthorizationServiceConfiguration): Intent {
    fun setUpAuthState(intent: Intent): OpenIdAuthStateSetupState {
        if (authState != null) {
            return OpenIdAuthStateSetupState.ALREADY_SET_UP
        }

        val authStateString =
            intent.getStringExtra(LoginActivity.AUTH_STATE) ?: return OpenIdAuthStateSetupState.SET_UP_FAILED

        authState = AuthState.jsonDeserialize(authStateString)

        if (authState == null) {
            return OpenIdAuthStateSetupState.SET_UP_FAILED
        }

        return OpenIdAuthStateSetupState.SET_UP_SUCCEED
    }

    fun requestAuthCode(serviceConfiguration: AuthorizationServiceConfiguration, intent: Intent) {
        authState = AuthState(serviceConfiguration)

        val authRequestBuilder = AuthorizationRequest.Builder(
        val authRequest = AuthorizationRequest.Builder(
            serviceConfiguration,
            identityProvider!!.clientId,
            ResponseTypeValues.CODE,
            identityProvider!!.redirectUri
        ).setScope(identityProvider!!.scope)
            .build()

        authorizationService.performAuthorizationRequest(
            authRequest,
            createPostAuthorizationIntent(authRequest, intent),
            authorizationService.createCustomTabsIntentBuilder().build()
        )
    }

    private fun createPostAuthorizationIntent(
        request: AuthorizationRequest,
        providedIntent: Intent
    ): PendingIntent {
        val intent = Intent(getApplication(), LoginActivity::class.java)

        intent.putExtra(LoginActivity.AUTH_STATE, authState?.jsonSerializeString())

        intent.putExtra(
            LoginActivity.ACCOUNT_TYPE,
            providedIntent.getStringExtra(LoginActivity.ACCOUNT_TYPE)
        )
        intent.putExtra(LoginActivity.OPENID_AUTH_FLOW_COMPLETE, true)
        intent.putExtra(
            LoginActivity.OPEN_APP_PACKAGE_AFTER_AUTH,
            providedIntent.getStringExtra(LoginActivity.OPEN_APP_PACKAGE_AFTER_AUTH)
        )
        intent.putExtra(
            LoginActivity.OPEN_APP_ACTIVITY_AFTER_AUTH,
            providedIntent.getStringExtra(LoginActivity.OPEN_APP_ACTIVITY_AFTER_AUTH)
        )

        authRequestBuilder.setScopes(identityProvider!!.scope)
        var flag = 0
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
            flag = flag or PendingIntent.FLAG_MUTABLE
        }

        return authorizationService.getAuthorizationRequestIntent(authRequestBuilder.build())
        return PendingIntent.getActivity(getApplication(), request.hashCode(), intent, flag)
    }

    fun performTokenRequest(
@@ -102,7 +162,8 @@ class OpenIdAuthenticationViewModel(application: Application) : AndroidViewModel
        var infoEndpoint = identityProvider?.userInfoEndpoint

        if (infoEndpoint == null) {
            val discovery = authState?.authorizationServiceConfiguration?.discoveryDoc ?: return null
            val discovery =
                authState?.authorizationServiceConfiguration?.discoveryDoc ?: return null
            infoEndpoint = discovery.userinfoEndpoint.toString()
        }