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

Commit 2d907726 authored by Abhishek Aggarwal's avatar Abhishek Aggarwal
Browse files

refactor(login): hide system Google access behind gateway

parent 7fbf831e
Loading
Loading
Loading
Loading
+36 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2026 e Foundation
 *
 * 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 foundation.e.apps.data.di.bindings

import dagger.Binds
import dagger.Module
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import foundation.e.apps.data.login.microg.MicrogSystemGoogleAccountGateway
import foundation.e.apps.feature.auth.login.SystemGoogleAccountGateway
import javax.inject.Singleton

@Module
@InstallIn(SingletonComponent::class)
interface SystemGoogleAccountBindingModule {
    @Binds
    @Singleton
    fun bindSystemGoogleAccountGateway(
        microgSystemGoogleAccountGateway: MicrogSystemGoogleAccountGateway,
    ): SystemGoogleAccountGateway
}
+89 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2026 e Foundation
 *
 * 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 foundation.e.apps.data.login.microg

import android.accounts.AccountManager
import android.app.Activity
import android.content.Context
import android.content.Intent
import foundation.e.apps.data.login.api.MicrogAccountFetchResult
import foundation.e.apps.data.login.api.MicrogAccountFetcher
import foundation.e.apps.feature.auth.login.SystemGoogleAccountFetchResult
import foundation.e.apps.feature.auth.login.SystemGoogleAccountGateway
import javax.inject.Inject
import javax.inject.Singleton

@Singleton
class MicrogSystemGoogleAccountGateway @Inject constructor(
    private val microgAccountFetcher: MicrogAccountFetcher,
) : SystemGoogleAccountGateway {
    override fun hasSupportedProvider(context: Context): Boolean {
        return MicrogSupportChecker.hasSupportedMicrog(context)
    }

    override fun hasAccount(): Boolean {
        return microgAccountFetcher.hasMicrogAccount()
    }

    override fun createAccountPickerIntent(): Intent {
        return AccountManager.newChooseAccountIntent(
            null,
            null,
            arrayOf(MicrogCertUtil.GOOGLE_ACCOUNT_TYPE),
            null,
            null,
            null,
            null,
        )
    }

    override fun selectedAccountName(data: Intent?): String? {
        return data?.getStringExtra(AccountManager.KEY_ACCOUNT_NAME)
    }

    override fun startAddAccount(
        activity: Activity,
        onComplete: () -> Unit,
    ) {
        AccountManager.get(activity).addAccount(
            MicrogCertUtil.GOOGLE_ACCOUNT_TYPE,
            null,
            null,
            null,
            activity,
            { onComplete() },
            null,
        )
    }

    override suspend fun fetchAccount(accountName: String): SystemGoogleAccountFetchResult {
        return when (val result = microgAccountFetcher.fetchAccount(accountName)) {
            is MicrogAccountFetchResult.Success ->
                SystemGoogleAccountFetchResult.Success(
                    email = result.email,
                    oauthToken = result.oauthToken,
                )

            is MicrogAccountFetchResult.RequiresUserAction ->
                SystemGoogleAccountFetchResult.RequiresUserAction(result.intent)

            is MicrogAccountFetchResult.Error ->
                SystemGoogleAccountFetchResult.Error(result.throwable)
        }
    }
}
+20 −3
Original line number Diff line number Diff line
@@ -17,8 +17,6 @@

package foundation.e.apps.feature.auth.login

import foundation.e.apps.data.playstore.utils.AC2DMUtil

internal object GoogleOAuthCredentialsExtractor {
    const val EMBEDDED_SETUP_URL = "https://accounts.google.com/EmbeddedSetup"
    private const val AUTH_TOKEN_COOKIE = "oauth_token"
@@ -28,7 +26,7 @@ internal object GoogleOAuthCredentialsExtractor {
            return null
        }

        return AC2DMUtil.parseCookieString(cookieHeader)[AUTH_TOKEN_COOKIE]
        return parseCookieString(cookieHeader)[AUTH_TOKEN_COOKIE]
            ?.takeUnless(String::isBlank)
    }

@@ -47,4 +45,23 @@ internal object GoogleOAuthCredentialsExtractor {
    ): String {
        return "$email|$oauthToken"
    }

    private fun parseCookieString(cookies: String): Map<String, String> {
        return cookies
            .split(";")
            .mapNotNull { rawCookie ->
                val separatorIndex = rawCookie.indexOf("=")
                if (separatorIndex <= 0) {
                    return@mapNotNull null
                }

                val key = rawCookie.substring(0, separatorIndex).trim()
                val value = rawCookie.substring(separatorIndex + 1).trim()
                if (key.isBlank() || value.isBlank()) {
                    null
                } else {
                    key to value
                }
            }.toMap()
    }
}
+2 −3
Original line number Diff line number Diff line
@@ -16,7 +16,6 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint
import foundation.e.apps.R
import foundation.e.apps.data.login.api.MicrogAccountFetcher
import foundation.e.apps.ui.compose.theme.AppTheme
import foundation.e.apps.ui.navigation.safeNavigate
import javax.inject.Inject
@@ -27,7 +26,7 @@ class SignInFragment : Fragment() {
    private val viewModel: LoginViewModel by activityViewModels()

    @Inject
    lateinit var microgAccountFetcher: MicrogAccountFetcher
    lateinit var systemGoogleAccountGateway: SystemGoogleAccountGateway

    private var dialogState: LoginSelectionDialogState? by mutableStateOf(null)
    private lateinit var systemWideGoogleLoginLauncher: SystemWideGoogleLoginLauncher
@@ -38,7 +37,7 @@ class SignInFragment : Fragment() {
        super.onCreate(savedInstanceState)
        systemWideGoogleLoginLauncher = SystemWideGoogleLoginLauncher(
            fragment = this,
            microgAccountFetcher = microgAccountFetcher,
            systemGoogleAccountGateway = systemGoogleAccountGateway,
            onCredentialsReady = { email, oauthToken ->
                viewModel.onEvent(LoginUiEvent.MicrogLoginSubmitted(email, oauthToken))
            },
+54 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2026 e Foundation
 *
 * 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 foundation.e.apps.feature.auth.login

import android.app.Activity
import android.content.Context
import android.content.Intent

interface SystemGoogleAccountGateway {
    fun hasSupportedProvider(context: Context): Boolean

    fun hasAccount(): Boolean

    fun createAccountPickerIntent(): Intent

    fun selectedAccountName(data: Intent?): String?

    fun startAddAccount(
        activity: Activity,
        onComplete: () -> Unit,
    )

    suspend fun fetchAccount(accountName: String): SystemGoogleAccountFetchResult
}

sealed interface SystemGoogleAccountFetchResult {
    data class Success(
        val email: String,
        val oauthToken: String,
    ) : SystemGoogleAccountFetchResult

    data class RequiresUserAction(
        val intent: Intent,
    ) : SystemGoogleAccountFetchResult

    data class Error(
        val throwable: Throwable,
    ) : SystemGoogleAccountFetchResult
}
Loading