Loading app/build.gradle.kts +6 −0 Original line number Diff line number Diff line import org.gradle.internal.impldep.org.junit.experimental.categories.Categories.CategoryFilter.exclude import java.io.FileInputStream import java.util.Properties Loading Loading @@ -289,6 +290,11 @@ dependencies { implementation(libs.okhttp.logging) implementation(libs.openid.appauth) implementation(libs.unifiedpush) implementation(libs.nextcloud.android.lib) { exclude(group="com.github.bitfireAT", module="dav4jvm") exclude(group="org.ogce", module="xpp3") exclude(group="com.squareup.okhttp3") } // for tests androidTestImplementation(libs.androidx.arch.core.testing) Loading app/src/main/AndroidManifest.xml +24 −0 Original line number Diff line number Diff line Loading @@ -257,6 +257,30 @@ android:resource="@xml/contacts"/> </service> <!-- account type "Eelo" --> <service android:name=".sync.account.EeloAccountAuthenticatorService" android:exported="false"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator" /> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/eelo_account_authenticator" /> </service> <service android:name=".sync.account.EeloAddressBookAuthenticatorService" android:exported="false"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator" /> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/account_authenticator_eelo_address_book" /> </service> <!-- provider to share debug info/logs --> <provider android:name="androidx.core.content.FileProvider" Loading app/src/main/kotlin/at/bitfire/davdroid/OpenIdUtils.kt 0 → 100644 +32 −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 import net.openid.appauth.ClientAuthentication import net.openid.appauth.ClientSecretBasic import net.openid.appauth.NoClientAuthentication object OpenIdUtils { fun getClientAuthentication(secret: String?): ClientAuthentication { if (secret == null) { return NoClientAuthentication.INSTANCE } return ClientSecretBasic(secret) } } app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt +1 −1 Original line number Diff line number Diff line Loading @@ -356,9 +356,9 @@ class AccountSettings @AssistedInject constructor( /** Stores the tasks sync interval (in seconds) so that it can be set again when the provider is switched */ const val KEY_SYNC_INTERVAL_TASKS = "sync_interval_tasks" const val KEY_USERNAME = "user_name" const val KEY_CERTIFICATE_ALIAS = "certificate_alias" const val KEY_CLIENT_SECRET = "client_secret" const val CREDENTIALS_LOCK = "login_credentials_lock" const val CREDENTIALS_LOCK_NO_LOCK = 0 Loading app/src/main/kotlin/at/bitfire/davdroid/sync/account/AccountUtils.kt 0 → 100644 +165 −0 Original line number Diff line number Diff line /*************************************************************************************************** * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. **************************************************************************************************/ package at.bitfire.davdroid.syncadapter import android.accounts.Account import android.accounts.AccountManager import android.content.Context import android.os.Bundle import at.bitfire.davdroid.R import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.sync.account.setAndVerifyUserData import com.owncloud.android.lib.common.accounts.AccountUtils object AccountUtils { /** * Creates an account and makes sure the user data are set correctly. * * @param context operating context * @param account account to create * @param userData user data to set * * @return whether the account has been created * * @throws IllegalArgumentException when user data contains non-String values * @throws IllegalStateException if user data can't be set */ fun createAccount( context: Context, account: Account, userData: Bundle, password: String? = null ): Boolean { // validate user data for (key in userData.keySet()) { userData.get(key)?.let { entry -> if (entry !is String) throw IllegalArgumentException("userData[$key] is ${entry::class.java} (expected: String)") } } // create account val manager = AccountManager.get(context) if (!manager.addAccountExplicitly(account, password, userData)) return false // Android seems to lose the initial user data sometimes, so set it a second time if that happens // https://forums.bitfire.at/post/11644 if (!verifyUserData(context, account, userData)) for (key in userData.keySet()) manager.setAndVerifyUserData(account, key, userData.getString(key)) if (!verifyUserData(context, account, userData)) throw IllegalStateException("Android doesn't store user data in account") return true } private fun verifyUserData(context: Context, account: Account, userData: Bundle): Boolean { val accountManager = AccountManager.get(context) userData.keySet().forEach { key -> val stored = accountManager.getUserData(account, key) val expected = userData.getString(key) if (stored != expected) { return false } } return true } fun getMainAccountTypes(context: Context) = listOf( context.getString(R.string.account_type), context.getString(R.string.eelo_account_type), context.getString(R.string.google_account_type), context.getString(R.string.yahoo_account_type) ) fun getMainAccounts(context: Context): List<Account> { val accountManager = AccountManager.get(context) val accounts = mutableListOf<Account>() getMainAccountTypes(context) .forEach { accounts.addAll(accountManager.getAccountsByType(it)) } return accounts } fun getAddressBookAccountTypes(context: Context) = listOf( context.getString(R.string.account_type_address_book), context.getString(R.string.account_type_eelo_address_book), context.getString(R.string.account_type_google_address_book), context.getString(R.string.account_type_yahoo_address_book) ) fun getAddressBookAccounts(context: Context): List<Account> { val accountManager = AccountManager.get(context) val accounts = mutableListOf<Account>() getAddressBookAccountTypes(context) .forEach { accounts.addAll(accountManager.getAccountsByType(it)) } return accounts } fun getOpenIdMainAccountTypes(context: Context) = listOf( context.getString(R.string.eelo_account_type), context.getString(R.string.google_account_type), context.getString(R.string.yahoo_account_type) ) fun getOpenIdMainAccounts(context: Context): List<Account> { val accountManager = AccountManager.get(context) val accounts = mutableListOf<Account>() getOpenIdMainAccountTypes(context) .forEach { accounts.addAll(accountManager.getAccountsByType(it)) } return accounts } fun getOwnCloudBaseUrl(baseURL: String): String { if (baseURL.contains("/remote.php")) { return baseURL.split("/remote.php")[0] } return baseURL } fun getAccount(context: Context, userName: String?, requestedBaseUrl: String?): Account? { if (userName == null || requestedBaseUrl == null) { return null } val baseUrl = getOwnCloudBaseUrl(requestedBaseUrl) val accountManager = AccountManager.get(context.applicationContext) val accounts = getMainAccounts(context) for(account in accounts) { val name = accountManager.getUserData(account, AccountSettings.KEY_USERNAME) if (name != userName) { continue } val url = accountManager.getUserData(account, AccountUtils.Constants.KEY_OC_BASE_URL) if (url != null && getOwnCloudBaseUrl(url) == baseUrl) { return account } } return null } } Loading
app/build.gradle.kts +6 −0 Original line number Diff line number Diff line import org.gradle.internal.impldep.org.junit.experimental.categories.Categories.CategoryFilter.exclude import java.io.FileInputStream import java.util.Properties Loading Loading @@ -289,6 +290,11 @@ dependencies { implementation(libs.okhttp.logging) implementation(libs.openid.appauth) implementation(libs.unifiedpush) implementation(libs.nextcloud.android.lib) { exclude(group="com.github.bitfireAT", module="dav4jvm") exclude(group="org.ogce", module="xpp3") exclude(group="com.squareup.okhttp3") } // for tests androidTestImplementation(libs.androidx.arch.core.testing) Loading
app/src/main/AndroidManifest.xml +24 −0 Original line number Diff line number Diff line Loading @@ -257,6 +257,30 @@ android:resource="@xml/contacts"/> </service> <!-- account type "Eelo" --> <service android:name=".sync.account.EeloAccountAuthenticatorService" android:exported="false"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator" /> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/eelo_account_authenticator" /> </service> <service android:name=".sync.account.EeloAddressBookAuthenticatorService" android:exported="false"> <intent-filter> <action android:name="android.accounts.AccountAuthenticator" /> </intent-filter> <meta-data android:name="android.accounts.AccountAuthenticator" android:resource="@xml/account_authenticator_eelo_address_book" /> </service> <!-- provider to share debug info/logs --> <provider android:name="androidx.core.content.FileProvider" Loading
app/src/main/kotlin/at/bitfire/davdroid/OpenIdUtils.kt 0 → 100644 +32 −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 import net.openid.appauth.ClientAuthentication import net.openid.appauth.ClientSecretBasic import net.openid.appauth.NoClientAuthentication object OpenIdUtils { fun getClientAuthentication(secret: String?): ClientAuthentication { if (secret == null) { return NoClientAuthentication.INSTANCE } return ClientSecretBasic(secret) } }
app/src/main/kotlin/at/bitfire/davdroid/settings/AccountSettings.kt +1 −1 Original line number Diff line number Diff line Loading @@ -356,9 +356,9 @@ class AccountSettings @AssistedInject constructor( /** Stores the tasks sync interval (in seconds) so that it can be set again when the provider is switched */ const val KEY_SYNC_INTERVAL_TASKS = "sync_interval_tasks" const val KEY_USERNAME = "user_name" const val KEY_CERTIFICATE_ALIAS = "certificate_alias" const val KEY_CLIENT_SECRET = "client_secret" const val CREDENTIALS_LOCK = "login_credentials_lock" const val CREDENTIALS_LOCK_NO_LOCK = 0 Loading
app/src/main/kotlin/at/bitfire/davdroid/sync/account/AccountUtils.kt 0 → 100644 +165 −0 Original line number Diff line number Diff line /*************************************************************************************************** * Copyright © All Contributors. See LICENSE and AUTHORS in the root directory for details. **************************************************************************************************/ package at.bitfire.davdroid.syncadapter import android.accounts.Account import android.accounts.AccountManager import android.content.Context import android.os.Bundle import at.bitfire.davdroid.R import at.bitfire.davdroid.settings.AccountSettings import at.bitfire.davdroid.sync.account.setAndVerifyUserData import com.owncloud.android.lib.common.accounts.AccountUtils object AccountUtils { /** * Creates an account and makes sure the user data are set correctly. * * @param context operating context * @param account account to create * @param userData user data to set * * @return whether the account has been created * * @throws IllegalArgumentException when user data contains non-String values * @throws IllegalStateException if user data can't be set */ fun createAccount( context: Context, account: Account, userData: Bundle, password: String? = null ): Boolean { // validate user data for (key in userData.keySet()) { userData.get(key)?.let { entry -> if (entry !is String) throw IllegalArgumentException("userData[$key] is ${entry::class.java} (expected: String)") } } // create account val manager = AccountManager.get(context) if (!manager.addAccountExplicitly(account, password, userData)) return false // Android seems to lose the initial user data sometimes, so set it a second time if that happens // https://forums.bitfire.at/post/11644 if (!verifyUserData(context, account, userData)) for (key in userData.keySet()) manager.setAndVerifyUserData(account, key, userData.getString(key)) if (!verifyUserData(context, account, userData)) throw IllegalStateException("Android doesn't store user data in account") return true } private fun verifyUserData(context: Context, account: Account, userData: Bundle): Boolean { val accountManager = AccountManager.get(context) userData.keySet().forEach { key -> val stored = accountManager.getUserData(account, key) val expected = userData.getString(key) if (stored != expected) { return false } } return true } fun getMainAccountTypes(context: Context) = listOf( context.getString(R.string.account_type), context.getString(R.string.eelo_account_type), context.getString(R.string.google_account_type), context.getString(R.string.yahoo_account_type) ) fun getMainAccounts(context: Context): List<Account> { val accountManager = AccountManager.get(context) val accounts = mutableListOf<Account>() getMainAccountTypes(context) .forEach { accounts.addAll(accountManager.getAccountsByType(it)) } return accounts } fun getAddressBookAccountTypes(context: Context) = listOf( context.getString(R.string.account_type_address_book), context.getString(R.string.account_type_eelo_address_book), context.getString(R.string.account_type_google_address_book), context.getString(R.string.account_type_yahoo_address_book) ) fun getAddressBookAccounts(context: Context): List<Account> { val accountManager = AccountManager.get(context) val accounts = mutableListOf<Account>() getAddressBookAccountTypes(context) .forEach { accounts.addAll(accountManager.getAccountsByType(it)) } return accounts } fun getOpenIdMainAccountTypes(context: Context) = listOf( context.getString(R.string.eelo_account_type), context.getString(R.string.google_account_type), context.getString(R.string.yahoo_account_type) ) fun getOpenIdMainAccounts(context: Context): List<Account> { val accountManager = AccountManager.get(context) val accounts = mutableListOf<Account>() getOpenIdMainAccountTypes(context) .forEach { accounts.addAll(accountManager.getAccountsByType(it)) } return accounts } fun getOwnCloudBaseUrl(baseURL: String): String { if (baseURL.contains("/remote.php")) { return baseURL.split("/remote.php")[0] } return baseURL } fun getAccount(context: Context, userName: String?, requestedBaseUrl: String?): Account? { if (userName == null || requestedBaseUrl == null) { return null } val baseUrl = getOwnCloudBaseUrl(requestedBaseUrl) val accountManager = AccountManager.get(context.applicationContext) val accounts = getMainAccounts(context) for(account in accounts) { val name = accountManager.getUserData(account, AccountSettings.KEY_USERNAME) if (name != userName) { continue } val url = accountManager.getUserData(account, AccountUtils.Constants.KEY_OC_BASE_URL) if (url != null && getOwnCloudBaseUrl(url) == baseUrl) { return account } } return null } }