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

Commit 21706020 authored by Mohammed Althaf T's avatar Mohammed Althaf T 😊
Browse files

AM: Logout from browser for OAuth

parent c48769b2
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -7,6 +7,7 @@ stages:
before_script:
  - echo MURENA_CLIENT_ID=$MURENA_CLIENT_ID >> local.properties
  - echo MURENA_REDIRECT_URI=$MURENA_REDIRECT_URI >> local.properties
  - echo MURENA_LOGOUT_REDIRECT_URI=$MURENA_LOGOUT_REDIRECT_URI >> local.properties
  - echo MURENA_BASE_URL=$MURENA_BASE_URL >> local.properties
  - echo MURENA_DISCOVERY_END_POINT=$MURENA_DISCOVERY_END_POINT >> local.properties
  - export JAVA_HOME=/usr/lib/jvm/java-21-openjdk-amd64
+2 −0
Original line number Diff line number Diff line
@@ -73,12 +73,14 @@ android {

            buildConfigField("String", "MURENA_CLIENT_ID", "\"${retrieveKey("MURENA_CLIENT_ID")}\"")
            buildConfigField("String", "MURENA_REDIRECT_URI", "\"${retrieveKey("MURENA_REDIRECT_URI")}\"")
            buildConfigField("String", "MURENA_LOGOUT_REDIRECT_URI", "\"${retrieveKey("MURENA_LOGOUT_REDIRECT_URI")}\"")
            buildConfigField("String", "MURENA_BASE_URL", "\"${retrieveKey("MURENA_BASE_URL")}\"")
            buildConfigField("String", "MURENA_DISCOVERY_END_POINT", "\"${retrieveKey("MURENA_DISCOVERY_END_POINT")}\"")

            manifestPlaceholders.putAll(
                mapOf<String, Any>(
                    "murenaAuthRedirectScheme" to retrieveKey("MURENA_REDIRECT_URI"),
                    "murenaAuthLogoutRedirectScheme" to retrieveKey("MURENA_LOGOUT_REDIRECT_URI")
                )
            )
        }
+2 −0
Original line number Diff line number Diff line
@@ -31,6 +31,7 @@ import dagger.Lazy
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.CoroutineDispatcher
import foundation.e.accountmanager.AccountTypes
import foundation.e.accountmanager.pref.AuthStatePrefUtils
import foundation.e.accountmanager.utils.AccountHelper
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
@@ -79,6 +80,7 @@ class AccountRepository @Inject constructor(

        // create Android account
        val userData = AccountSettings.initialUserData(credentials)
        AuthStatePrefUtils.saveAuthState(context, account, credentials?.authState?.jsonSerializeString())
        logger.log(Level.INFO, "Creating Android account with initial config", arrayOf(account, userData))

        if (!SystemAccountUtils.createAccount(context, account, userData, credentials?.password))
+2 −0
Original line number Diff line number Diff line
@@ -25,6 +25,7 @@ import dagger.assisted.AssistedFactory
import dagger.assisted.AssistedInject
import dagger.hilt.android.qualifiers.ApplicationContext
import foundation.e.accountmanager.AccountTypes
import foundation.e.accountmanager.pref.AuthStatePrefUtils
import net.openid.appauth.AuthState
import java.util.Collections
import java.util.logging.Level
@@ -132,6 +133,7 @@ class AccountSettings @AssistedInject constructor(

    fun updateAuthState(authState: AuthState) {
        accountManager.setAndVerifyUserData(account, KEY_AUTH_STATE, authState.jsonSerializeString())
        AuthStatePrefUtils.saveAuthState(context, account, authState.jsonSerializeString())
    }

    /**
+107 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2025 eFoundation
 *
 * 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.accountmanager.auth

import android.accounts.AccountManager
import android.app.PendingIntent
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import at.bitfire.davdroid.settings.AccountSettings
import at.bitfire.davdroid.settings.AccountSettings.Companion.KEY_AUTH_STATE
import foundation.e.accountmanager.AccountTypes
import foundation.e.accountmanager.pref.AuthStatePrefUtils
import foundation.e.accountmanager.ui.setup.MurenaLogoutActivity
import foundation.e.accountmanager.utils.AccountHelper
import foundation.e.accountmanager.utils.SystemUtils.sendWithBackgroundLaunchAllowed
import java.util.logging.Level
import java.util.logging.Logger

class AccountReceiver : BroadcastReceiver() {
    val logger: Logger = Logger.getLogger(this.javaClass.name)

    override fun onReceive(context: Context?, intent: Intent?) {
        if (context == null || intent == null) return

        when (intent.action) {
            AccountManager.LOGIN_ACCOUNTS_CHANGED_ACTION -> handleAccountChange(context)
            AccountManager.ACTION_ACCOUNT_REMOVED -> handleAccountRemoval(context, intent)
            else -> logger.log(Level.WARNING, "Unhandled action: ${intent.action}")
        }
    }

    private fun handleAccountChange(context: Context) {
        val accountManager = AccountManager.get(context)
        val murenaAccounts = AccountHelper.getAllAccounts(accountManager)
            .filter { it.type == AccountTypes.Murena.accountType }

        murenaAccounts.forEach { account ->
            logger.log(Level.INFO, "Account change detected for ${account.name}")
            val authState = accountManager.getUserData(account, KEY_AUTH_STATE)
            AuthStatePrefUtils.saveAuthState(context, account, authState)
        }
    }

    private fun handleAccountRemoval(context: Context, intent: Intent) {
        val accountType = intent.getStringExtra(AccountManager.KEY_ACCOUNT_TYPE) ?: return
        val accountName = intent.getStringExtra(AccountManager.KEY_ACCOUNT_NAME) ?: return

        logger.log(Level.INFO, "Account removed - Type: $accountType, Name: $accountName")

        if (accountType != AccountTypes.Murena.accountType) {
            logger.log(Level.INFO, "Unrecognized account type $accountType, ignoring")
            return
        }

        clearOidcSession(context, accountName, accountType)

        val accountManager = AccountManager.get(context)
        val addressBooks = accountManager.getAccountsByType(AccountTypes.Murena.addressBookType)
        addressBooks.forEach {
            accountManager.removeAccountExplicitly(it)
        }
    }

    private fun clearOidcSession(context: Context, accountName: String, accountType: String) {
        val authState = AuthStatePrefUtils.loadAuthState(context, accountName, accountType)
        if (authState == null) {
            logger.log(Level.SEVERE, "AuthState is null, cannot initiate end session for $accountName and $accountType")
            return
        }

        AuthStatePrefUtils.removeAuthState(context, accountName, accountType)
        startOidcEndSessionActivity(context, authState, accountType)
    }

    private fun startOidcEndSessionActivity(context: Context, authState: String, accountType: String) {
        val intent = Intent(context, MurenaLogoutActivity::class.java).apply {
            putExtra(AccountSettings.KEY_AUTH_STATE, authState)
            putExtra(AccountManager.KEY_ACCOUNT_TYPE, accountType)
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }

        val pendingIntent = PendingIntent.getActivity(
            context,
            0,
            intent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
        )

        pendingIntent.sendWithBackgroundLaunchAllowed()
    }
}
Loading