diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index bce6ab210e2241e39c9ee64f6d53c8f4b1247e7d..7f3af0812442b3056c3baeb27cc6d82186083b5a 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -129,7 +129,6 @@ android:name=".ui.setup.LoginActivity" android:label="@string/login_title" android:parentActivityName=".ui.AccountsActivity" - android:excludeFromRecents="true" android:exported="true"> @@ -213,6 +212,9 @@ android:exported="true" /> + @@ -785,4 +787,4 @@ - \ No newline at end of file + diff --git a/app/src/main/java/com/nextcloud/android/utils/AccountManagerUtils.kt b/app/src/main/java/com/nextcloud/android/utils/WebViewUtils.kt similarity index 100% rename from app/src/main/java/com/nextcloud/android/utils/AccountManagerUtils.kt rename to app/src/main/java/com/nextcloud/android/utils/WebViewUtils.kt diff --git a/app/src/main/kotlin/at/bitfire/davdroid/murenasso/MurenaSsoMigrationPreferences.kt b/app/src/main/kotlin/at/bitfire/davdroid/murenasso/MurenaSsoMigrationPreferences.kt new file mode 100644 index 0000000000000000000000000000000000000000..164df10f6e257cc29338b21221455495e6c7c568 --- /dev/null +++ b/app/src/main/kotlin/at/bitfire/davdroid/murenasso/MurenaSsoMigrationPreferences.kt @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2025 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 . + */ + +package at.bitfire.davdroid.murenasso + +import android.content.Context +import androidx.core.content.edit + +object MurenaSsoMigrationPreferences { + + private const val PREFERENCES_NAME = "murena_sso_migration" + private const val KEY_IS_MURENA_SSO_MIGRATION_RUNNING = "is_sso_migration_running" + + fun isSsoMigrationRunning(context: Context): Boolean { + val preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) + return preferences.getBoolean(KEY_IS_MURENA_SSO_MIGRATION_RUNNING, false) + } + + fun updateSsoMigrationStatus(context: Context, status: MigrationStatus) { + val preferences = context.getSharedPreferences(PREFERENCES_NAME, Context.MODE_PRIVATE) + val isRunning = (status == MigrationStatus.InProgress) + preferences.edit { putBoolean(KEY_IS_MURENA_SSO_MIGRATION_RUNNING, isRunning) } + } + + enum class MigrationStatus { + InProgress, Completed + } +} diff --git a/app/src/main/kotlin/at/bitfire/davdroid/murenasso/MurenaSsoMigrationService.kt b/app/src/main/kotlin/at/bitfire/davdroid/murenasso/MurenaSsoMigrationService.kt new file mode 100644 index 0000000000000000000000000000000000000000..b501a9b9c93c90966c9117b1d0987c8cae99a9dc --- /dev/null +++ b/app/src/main/kotlin/at/bitfire/davdroid/murenasso/MurenaSsoMigrationService.kt @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2025 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 . + * + */ +package at.bitfire.davdroid.murenasso + +import android.accounts.AccountManager +import android.accounts.AccountManagerFuture +import android.app.Service +import android.content.Intent +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES +import android.os.Bundle +import android.os.IBinder +import androidx.core.app.NotificationManagerCompat +import androidx.core.content.ContextCompat +import at.bitfire.davdroid.R +import at.bitfire.davdroid.log.Logger +import at.bitfire.davdroid.murenasso.MurenaSsoMigrationPreferences.MigrationStatus +import at.bitfire.davdroid.ui.NotificationUtils +import at.bitfire.davdroid.ui.setup.LoginActivity + +class MurenaSsoMigrationService : Service() { + + companion object { + const val NOTIFICATION_TAG_MURENA_SSO = "MIGRATE_TO_MURENA_SSO" + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + val accountName = intent?.getStringExtra(LoginActivity.EXTRA_USERNAME) + if (accountName.isNullOrBlank()) { + stopSelf() + return START_NOT_STICKY + } + openMurenaLoginUi(accountName) + + return START_STICKY + } + + private fun openMurenaLoginUi(accountName: String?) { + val accountManager = AccountManager.get(applicationContext) + val eAccountType = applicationContext.getString(R.string.eelo_account_type) + + accountManager.addAccount( + eAccountType, + null, + null, + null, + null, + { future -> + val intent = getIntentFromFuture(future) + if (intent == null) { + stopSelf() // Stops the service and dismisses the notification as migration isn't possible. + return@addAccount + } + + setMigrationStatusInProgress() + + // Calling startActivity() from outside of an Activity context + // requires the FLAG_ACTIVITY_NEW_TASK flag + intent.apply { + putExtra(LoginActivity.EXTRA_USERNAME, accountName) + addFlags(Intent.FLAG_ACTIVITY_NEW_TASK) + } + ContextCompat.startActivity(applicationContext, intent, null) + }, + null + ) + } + + private fun getIntentFromFuture(future: AccountManagerFuture): Intent? { + val bundle = future.result + + return try { + if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) { + bundle?.getParcelable(AccountManager.KEY_INTENT, Intent::class.java) + } else { + @Suppress("DEPRECATION") + bundle?.getParcelable(AccountManager.KEY_INTENT) + } + } catch (exception: Exception) { + Logger.log.warning("${exception.javaClass}: can't add account: ${exception.message}") + null + } + } + + private fun setMigrationStatusInProgress() { + MurenaSsoMigrationPreferences.updateSsoMigrationStatus( + applicationContext, + MigrationStatus.InProgress + ) + } + + override fun onBind(intent: Intent?): IBinder? { + return null + } + + override fun onDestroy() { + NotificationManagerCompat.from(applicationContext).cancel( + NOTIFICATION_TAG_MURENA_SSO, + NotificationUtils.NOTIFY_MIGRATE_TO_MURENA_SSO + ) + Logger.log.info("Stopping MurenaSsoMigrationService.") + super.onDestroy() + } +} diff --git a/app/src/main/kotlin/at/bitfire/davdroid/receiver/BootCompletedReceiver.kt b/app/src/main/kotlin/at/bitfire/davdroid/receiver/BootCompletedReceiver.kt index d769750815e6293becf5334a2a931129bcf60fb6..39b843b61f2227f6e5af1a8422ff071069bb0467 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/receiver/BootCompletedReceiver.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/receiver/BootCompletedReceiver.kt @@ -4,12 +4,31 @@ package at.bitfire.davdroid.receiver +import android.Manifest.permission.POST_NOTIFICATIONS +import android.accounts.Account +import android.app.PendingIntent import android.content.BroadcastReceiver import android.content.Context import android.content.Intent +import android.content.pm.PackageManager.PERMISSION_GRANTED +import android.os.Build.VERSION +import android.os.Build.VERSION_CODES +import androidx.core.app.NotificationManagerCompat +import at.bitfire.davdroid.R import at.bitfire.davdroid.log.Logger +import at.bitfire.davdroid.murenasso.MurenaSsoMigrationService +import at.bitfire.davdroid.murenasso.MurenaSsoMigrationService.Companion.NOTIFICATION_TAG_MURENA_SSO import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.syncadapter.AccountUtils +import at.bitfire.davdroid.ui.NotificationUtils +import at.bitfire.davdroid.ui.NotificationUtils.CHANNEL_GENERAL +import at.bitfire.davdroid.ui.NotificationUtils.NOTIFY_MIGRATE_TO_MURENA_SSO +import at.bitfire.davdroid.ui.NotificationUtils.notifyIfPossible +import at.bitfire.davdroid.ui.setup.LoginActivity +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.SupervisorJob +import kotlinx.coroutines.launch /** * There are circumstances when Android drops automatic sync of accounts and resets them @@ -19,15 +38,90 @@ import at.bitfire.davdroid.syncadapter.AccountUtils * is started, it checks (and repairs, if necessary) the sync intervals in [App.onCreate]. */ class BootCompletedReceiver: BroadcastReceiver() { - override fun onReceive(context: Context, intent: Intent) { - Logger.log.info("Device has been rebooted; checking sync intervals etc.") - // sync intervals are checked in App.onCreate() + val pendingResult = goAsync() + val job = SupervisorJob() + val scope = CoroutineScope(job + Dispatchers.IO) + + scope.launch { + try { + initializeSync(context) + handleMurenaSsoMigration(context) + } finally { + pendingResult.finish() + job.cancel() + } + } + } + + private fun initializeSync(context: Context) { AccountUtils.getMainAccounts(context) .forEach { + // sync intervals are checked in App.onCreate() val accountSettings = AccountSettings(context, it) accountSettings.initSync() } } -} \ No newline at end of file + private fun handleMurenaSsoMigration( + context: Context + ) { + val account = findAccountForSsoMigration(context) + if (account != null) { + notifySsoMigration(context, account) + } + } + + private fun findAccountForSsoMigration(context: Context): Account? { + return AccountUtils.getMainAccounts(context) + .firstOrNull { + AccountUtils.isMurenaAccount(context, it) && + !AccountUtils.isLoggedInWithMurenaSso(context, it) + } + } + + private fun notifySsoMigration(context: Context, account: Account) { + if (!hasPermission(context)) { + Logger.log.warning("Notification permission is not granted, skipping SSO migration.") + return + } + + val title = context.getString(R.string.notification_title_murena_sso, account.name) + val contentText = context.getString(R.string.notification_text_murena_sso) + val intent = createSsoMigrationIntent(context, account.name) + + val notification = NotificationUtils.newBuilder(context, CHANNEL_GENERAL) + .setOngoing(true) + .setContentTitle(title) + .setContentText(contentText) + .setSmallIcon(R.drawable.ic_info) + .setContentIntent(intent) + .setAutoCancel(false) + .build() + + val notificationManager = NotificationManagerCompat.from(context) + notificationManager.notifyIfPossible(NOTIFICATION_TAG_MURENA_SSO, NOTIFY_MIGRATE_TO_MURENA_SSO, notification) + } + + private fun hasPermission(context: Context): Boolean { + if (VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) { + val permission = POST_NOTIFICATIONS + if (context.checkSelfPermission(permission) != PERMISSION_GRANTED) { + return false + } + } + return true + } + + private fun createSsoMigrationIntent(context: Context, accountName: String): PendingIntent { + val serviceIntent = Intent(context, MurenaSsoMigrationService::class.java).apply { + putExtra(LoginActivity.EXTRA_USERNAME, accountName) + flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP + } + + return PendingIntent.getService(context, + 0, + serviceIntent, + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT) + } +} diff --git a/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt b/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt index abbb5855ee3323f29a50e8f5e8db953477e002e6..c027c872c4696682a045e761d5f7a7fbbb2fae95 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt @@ -163,7 +163,7 @@ class AccountSettings( } if (!url.isNullOrEmpty()) { - val baseUrl = AccountUtils.getOwnCloudBaseUrl(url) + val baseUrl = AccountUtils.extractBaseUrl(url) bundle.putString(NCAccountUtils.Constants.KEY_OC_BASE_URL, baseUrl) } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/syncadapter/AccountUtils.kt b/app/src/main/kotlin/at/bitfire/davdroid/syncadapter/AccountUtils.kt index d94c06b0c688ac83e32ab619a36ce1dcd79d8a73..17dfd311e5622ab0e76e997851ed96c36cb2f9d4 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/syncadapter/AccountUtils.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/syncadapter/AccountUtils.kt @@ -8,11 +8,13 @@ import android.accounts.Account import android.accounts.AccountManager import android.content.Context import android.os.Bundle +import at.bitfire.davdroid.BuildConfig import at.bitfire.davdroid.R import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.util.setAndVerifyUserData import com.owncloud.android.lib.common.accounts.AccountUtils +import com.owncloud.android.lib.common.accounts.AccountUtils.Constants object AccountUtils { @@ -131,12 +133,12 @@ object AccountUtils { return accounts } - fun getOwnCloudBaseUrl(baseURL: String): String { - if (baseURL.contains("/remote.php")) { - return baseURL.split("/remote.php")[0] + fun extractBaseUrl(url: String): String { + if (url.contains("/remote.php")) { + return url.split("/remote.php")[0] } - return baseURL + return url } fun getAccount(context: Context, userName: String?, requestedBaseUrl: String?): Account? { @@ -144,7 +146,7 @@ object AccountUtils { return null } - val baseUrl = getOwnCloudBaseUrl(requestedBaseUrl) + val baseUrl = extractBaseUrl(requestedBaseUrl) val accountManager = AccountManager.get(context.applicationContext) @@ -156,12 +158,37 @@ object AccountUtils { continue } - val url = accountManager.getUserData(account, AccountUtils.Constants.KEY_OC_BASE_URL) - if (url != null && getOwnCloudBaseUrl(url) == baseUrl) { + val url = accountManager.getUserData(account, Constants.KEY_OC_BASE_URL) + if (url != null && extractBaseUrl(url) == baseUrl) { return account } } return null } + + fun isMurenaAccount(context: Context, account: Account): Boolean { + val accountManager = AccountManager.get(context) + + val urlData = + accountManager.getUserData(account, Constants.KEY_OC_BASE_URL) ?: return false + + val baseUrl = extractBaseUrl(urlData) + val murenaBaseUrl = extractBaseUrl(BuildConfig.MURENA_BASE_URL) + + // User can have their own Murena account set up with custom Nextcloud instance, + // so a check for base URL is necessary. + val isMurenaCloud = (baseUrl == murenaBaseUrl) + val isMurenaAccountType = (account.type == context.getString(R.string.eelo_account_type)) + + return isMurenaCloud && isMurenaAccountType + } + + fun isLoggedInWithMurenaSso(context: Context, account: Account): Boolean { + val accountManager = AccountManager.get(context) + val hasAuthStateData = accountManager.getUserData(account, AccountSettings.KEY_AUTH_STATE) != null + val isPasswordNull = accountManager.getPassword(account).isNullOrEmpty() + + return isPasswordNull && hasAuthStateData + } } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/NotificationUtils.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/NotificationUtils.kt index e41544d538bb2a6f08bda820781ad4d7361c0c77..047bf4e689b20144a79b131bb01dd56eb4176442 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/NotificationUtils.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/NotificationUtils.kt @@ -34,6 +34,7 @@ object NotificationUtils { const val NOTIFY_SYNC_EXPEDITED = 14 const val NOTIFY_TASKS_PROVIDER_TOO_OLD = 20 const val NOTIFY_PERMISSIONS = 21 + const val NOTIFY_MIGRATE_TO_MURENA_SSO = 22 const val NOTIFY_LICENSE = 100 @@ -92,4 +93,4 @@ object NotificationUtils { fun NotificationManagerCompat.notifyIfPossible(id: Int, notification: Notification) = notifyIfPossible(null, id, notification) -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt index a7cf8c1d96ab02fc96755cbd5b1b9ddad27f8316..9a92e7699eb2ec44f940ab0cbc223c8d1170a46e 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt @@ -13,7 +13,6 @@ import android.content.ComponentName import android.content.ContentResolver import android.content.Context import android.content.Intent -import android.net.Uri import android.os.Bundle import android.provider.CalendarContract import android.text.Editable @@ -31,23 +30,26 @@ import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import androidx.lifecycle.viewModelScope -import at.bitfire.davdroid.BuildConfig import at.bitfire.davdroid.Constants import at.bitfire.davdroid.InvalidAccountException import at.bitfire.davdroid.R -import at.bitfire.davdroid.authorization.IdentityProvider import at.bitfire.davdroid.databinding.LoginAccountDetailsBinding import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.Credentials import at.bitfire.davdroid.db.HomeSet import at.bitfire.davdroid.db.Service import at.bitfire.davdroid.log.Logger +import at.bitfire.davdroid.murenasso.MurenaSsoMigrationPreferences +import at.bitfire.davdroid.murenasso.MurenaSsoMigrationPreferences.MigrationStatus.Completed +import at.bitfire.davdroid.murenasso.MurenaSsoMigrationService import at.bitfire.davdroid.resource.TaskUtils import at.bitfire.davdroid.servicedetection.DavResourceFinder import at.bitfire.davdroid.servicedetection.RefreshCollectionsWorker import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.settings.SettingsManager import at.bitfire.davdroid.syncadapter.AccountUtils +import at.bitfire.davdroid.syncadapter.AccountUtils.isLoggedInWithMurenaSso +import at.bitfire.davdroid.syncadapter.AccountUtils.isMurenaAccount import at.bitfire.davdroid.syncadapter.SyncAllAccountWorker import at.bitfire.davdroid.syncadapter.SyncWorker import at.bitfire.davdroid.util.AuthStatePrefUtils @@ -60,8 +62,6 @@ import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.launch -import net.openid.appauth.AuthorizationService -import net.openid.appauth.EndSessionRequest import java.util.logging.Level import javax.inject.Inject @@ -119,12 +119,12 @@ class AccountDetailsFragment : Fragment() { v.createAccountProgress.visibility = View.VISIBLE v.createAccount.visibility = View.GONE - model.createAccount( + model.createOrUpdateAccount( requireActivity(), name, loginModel.credentials, config, - GroupMethod.valueOf(groupMethodName) + GroupMethod.valueOf(groupMethodName), ).observe(viewLifecycleOwner, Observer { success -> if (success) { // close Create account activity @@ -174,12 +174,22 @@ class AccountDetailsFragment : Fragment() { val idx = v.contactGroupMethod.selectedItemPosition val groupMethodName = resources.getStringArray(R.array.settings_contact_group_method_values)[idx] - model.createAccount( + val existingNonSsoMurenaAccount = AccountUtils.getMainAccounts(requireContext()) + .firstOrNull { account -> + isMurenaAccount(requireContext(), account) && + !isLoggedInWithMurenaSso(requireContext(), account) + } + existingNonSsoMurenaAccount?.let { + Logger.log.info("Found non-SSO Murena account: $it") + } + + model.createOrUpdateAccount( requireActivity(), name, loginModel.credentials!!, config, - GroupMethod.valueOf(groupMethodName) + GroupMethod.valueOf(groupMethodName), + existingNonSsoMurenaAccount, ).observe(viewLifecycleOwner, Observer { success -> if (success) { Toast.makeText(context, R.string.message_account_added_successfully, Toast.LENGTH_LONG).show() @@ -196,9 +206,15 @@ class AccountDetailsFragment : Fragment() { } if (requireActivity().intent.getStringExtra(LoginActivity.ACCOUNT_TYPE) == getString(R.string.eelo_account_type)) { - notifyEdrive(name) + val isSsoMigrationRunning = + MurenaSsoMigrationPreferences.isSsoMigrationRunning(requireContext()) + + if (isSsoMigrationRunning) { + handlePostMurenaSsoMigrationOperations() + } else { + notifyEdriveWithAccountAdded(name) + } } - handlePostAuthOperations() } }) @@ -208,7 +224,23 @@ class AccountDetailsFragment : Fragment() { return v.root } - private fun notifyEdrive(name: String) { + private fun handlePostMurenaSsoMigrationOperations() { + val authState = requireActivity().intent.getStringExtra(LoginActivity.AUTH_STATE) + val isMigratedToMurenaSso = !authState.isNullOrEmpty() + + if (isMigratedToMurenaSso) { + stopMurenaSsoMigrationService() + MurenaSsoMigrationPreferences.updateSsoMigrationStatus(requireContext(), Completed) + Logger.log.info("Murena SSO migration is complete.") + } + } + + private fun stopMurenaSsoMigrationService() { + val serviceIntent = Intent(requireContext(), MurenaSsoMigrationService::class.java) + requireContext().stopService(serviceIntent) + } + + private fun notifyEdriveWithAccountAdded(name: String) { val intent = Intent("foundation.e.drive.action.ADD_ACCOUNT") intent.addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) intent.component = @@ -272,7 +304,14 @@ class AccountDetailsFragment : Fragment() { * @param groupMethod Whether CardDAV contact groups are separate VCards or as contact categories * @return *true* if account creation was succesful; *false* otherwise (for instance because an account with this name already exists) */ - fun createAccount(activity: Activity, name: String, credentials: Credentials?, config: DavResourceFinder.Configuration, groupMethod: GroupMethod): LiveData { + fun createOrUpdateAccount( + activity: Activity, + name: String, + credentials: Credentials?, + config: DavResourceFinder.Configuration, + groupMethod: GroupMethod, + accountToUpdate: Account? = null + ): LiveData { val result = MutableLiveData() viewModelScope.launch(Dispatchers.Default + NonCancellable) { var accountType = context.getString(R.string.account_type) @@ -304,23 +343,38 @@ class AccountDetailsFragment : Fragment() { } } - val account = Account(name, accountType) + val account = accountToUpdate ?: Account(name, accountType) - // create Android account val userData = AccountSettings.initialUserData(credentials, baseURL, config.cookies, config.calDAV?.emails?.firstOrNull()) AuthStatePrefUtils.saveAuthState(context, account, credentials?.authState?.jsonSerializeString()) - Logger.log.log(Level.INFO, "Creating Android account with initial config", arrayOf(account, userData)) - val accountManager = AccountManager.get(context) - if (!AccountUtils.createAccount(context, account, userData, credentials?.password)) { - if (accountType in AccountUtils.getOpenIdMainAccountTypes(context) && credentials?.authState != null) { - updateAuthState(userData, accountManager, account) - } else { - result.postValue(false) - return@launch + if (accountToUpdate != null) { + Logger.log.info("Updating auth state for existing non-SSO Murena account: $account") + updateAuthState(userData, accountManager, account) + + // Clear password for SSO account + val authState = AuthStatePrefUtils.loadAuthState(context, name, accountType) + if (!authState.isNullOrBlank()) { + accountManager.clearPassword(account) + Logger.log.info("Cleared password for account: $account") + } + } else { + if (!AccountUtils.createAccount( + context, + account, + userData, + credentials?.password + ) + ) { + if (accountType in AccountUtils.getOpenIdMainAccountTypes(context) && credentials?.authState != null) { + updateAuthState(userData, accountManager, account) + } else { + result.postValue(false) + return@launch + } } } diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt index be7d2750ebfd1a313d2f533a49030241b5e9de35..3c80ddf72e6ffae3da6679b913c81c138abfb84a 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/DetectConfigurationFragment.kt @@ -21,6 +21,7 @@ import at.bitfire.davdroid.ECloudAccountHelper import at.bitfire.davdroid.R import at.bitfire.davdroid.db.Credentials import at.bitfire.davdroid.log.Logger +import at.bitfire.davdroid.murenasso.MurenaSsoMigrationPreferences import at.bitfire.davdroid.servicedetection.DavResourceFinder import at.bitfire.davdroid.ui.DebugInfoActivity import com.google.android.material.dialog.MaterialAlertDialogBuilder @@ -40,9 +41,11 @@ class DetectConfigurationFragment: Fragment() { val accountType = requireActivity().intent.getStringExtra(LoginActivity.ACCOUNT_TYPE) - if (model.blockProceedWithLogin(accountType)) { - ECloudAccountHelper.showMultipleECloudAccountNotAcceptedDialog(requireActivity()) - return + if (!MurenaSsoMigrationPreferences.isSsoMigrationRunning(requireContext())) { + if (model.blockProceedWithLogin(accountType)) { + ECloudAccountHelper.showMultipleECloudAccountNotAcceptedDialog(requireActivity()) + return + } } val blockOnUnauthorizedException = (accountType == getString(R.string.eelo_account_type)) && !EeloAuthenticatorModel.ENABLE_OIDC_SUPPORT @@ -159,4 +162,4 @@ class DetectConfigurationFragment: Fragment() { } -} \ No newline at end of file +} diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/EeloAuthenticatorFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/EeloAuthenticatorFragment.kt index 47bbafcf0a99699cb6dd64f25363688010eca628..50d1b90e2d2f6079dae2693743f7b22c231c99fa 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/EeloAuthenticatorFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/EeloAuthenticatorFragment.kt @@ -37,6 +37,7 @@ import at.bitfire.davdroid.ECloudAccountHelper import at.bitfire.davdroid.R import at.bitfire.davdroid.databinding.FragmentEeloAuthenticatorBinding import at.bitfire.davdroid.db.Credentials +import at.bitfire.davdroid.murenasso.MurenaSsoMigrationPreferences import at.bitfire.davdroid.ui.ShowUrlActivity import com.google.android.material.dialog.MaterialAlertDialogBuilder import com.google.android.material.textfield.TextInputEditText @@ -119,6 +120,9 @@ class EeloAuthenticatorFragment : Fragment() { override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + if (MurenaSsoMigrationPreferences.isSsoMigrationRunning(requireContext())) { + return + } if (model.blockProceedWithLogin()) { ECloudAccountHelper.showMultipleECloudAccountNotAcceptedDialog(requireActivity()) } @@ -133,6 +137,11 @@ class EeloAuthenticatorFragment : Fragment() { login() requireActivity().intent.putExtra(LoginActivity.RETRY_ON_401, false) // disable retry option to mitigate infinite looping } + + val accountName = requireActivity().intent.getStringExtra(LoginActivity.EXTRA_USERNAME) + if (accountName != null) { + userIdEditText.setText(accountName) + } } /*** diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/EeloAuthenticatorModel.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/EeloAuthenticatorModel.kt index 6e785ffc6ebfd7511ddb5656c30e209151135e61..9b05b2e607c7b685007da8fa8d3b8e480db0fa75 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/EeloAuthenticatorModel.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/EeloAuthenticatorModel.kt @@ -27,7 +27,7 @@ class EeloAuthenticatorModel(application: Application) : AndroidViewModel(applic companion object { // as https://gitlab.e.foundation/e/backlog/-/issues/6287 is blocked, the openId implementation is not ready yet. // But we want to push the changes so later we won't face any conflict. So we are disabling the openId feature for now. - const val ENABLE_OIDC_SUPPORT = false + const val ENABLE_OIDC_SUPPORT = true } private var initialized = false diff --git a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/MurenaOpenIdAuthFragment.kt b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/MurenaOpenIdAuthFragment.kt index ed1bea7a027eccd935de43e8662365a4248136a5..b7b37c9a67dcc647191c31de5010061396d3f5e8 100644 --- a/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/MurenaOpenIdAuthFragment.kt +++ b/app/src/main/kotlin/at/bitfire/davdroid/ui/setup/MurenaOpenIdAuthFragment.kt @@ -20,6 +20,7 @@ import android.os.Bundle import android.view.View import at.bitfire.davdroid.ECloudAccountHelper import at.bitfire.davdroid.authorization.IdentityProvider +import at.bitfire.davdroid.murenasso.MurenaSsoMigrationPreferences import org.json.JSONObject class MurenaOpenIdAuthFragment : OpenIdAuthenticationBaseFragment(IdentityProvider.MURENA) { @@ -27,6 +28,11 @@ class MurenaOpenIdAuthFragment : OpenIdAuthenticationBaseFragment(IdentityProvid override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) + if (MurenaSsoMigrationPreferences.isSsoMigrationRunning(requireContext())) { + startAuthFLow() + return + } + if (!isAuthFlowComplete() && ECloudAccountHelper.alreadyHasECloudAccount(requireContext())) { ECloudAccountHelper.showMultipleECloudAccountNotAcceptedDialog(requireActivity()) return diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 68501ba08c9574de92e082de4b784f8352de06b2..b13b238a48e8d205e92724f34009b1e49bbc5671 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -53,6 +53,9 @@ Non-fatal synchronization problems like certain invalid files Network and I/O errors Timeouts, connection problems, etc. (often temporary) + Your account %1$s + Account update for %1$s + A new login service for a better experience is available. Tap the notification to start using it. Your data. Your choice.