From 4cb157fe72b3628546e3a80b65b3236d9a00ea86 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Fri, 19 Aug 2022 11:31:42 +0600 Subject: [PATCH 001/285] Update applicationId to foundation.e.accountmanager --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index bdb6d3118..49f28aeef 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,7 +13,7 @@ android { buildToolsVersion '32.0.0' defaultConfig { - applicationId "at.bitfire.davdroid" + applicationId "foundation.e.accountmanager" versionCode 402020000 versionName '4.2.2' -- GitLab From 85977be99d4be099878fc309cbb4efb987d26b6a Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Fri, 19 Aug 2022 11:35:29 +0600 Subject: [PATCH 002/285] build.gradle: set minSdkVersion to 24(Android 7.1) --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 49f28aeef..012ff381e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -21,7 +21,7 @@ android { setProperty "archivesBaseName", "davx5-ose-" + getVersionName() - minSdkVersion 21 // Android 5 + minSdkVersion 24 // Android 7.1 targetSdkVersion 32 // Android 12v2 buildConfigField "String", "userAgent", "\"DAVx5\"" -- GitLab From 96cbb72fc95fb0e21435f7c990dd7aea5c52e469 Mon Sep 17 00:00:00 2001 From: Nihar Thakkar Date: Thu, 7 Jun 2018 12:58:14 +0530 Subject: [PATCH 003/285] Created new account types for Google and eelo accounts. --- app/src/main/AndroidManifest.xml | 26 +++ .../EeloAccountAuthenticatorService.kt | 155 ++++++++++++++++++ .../GoogleAccountAuthenticatorService.kt | 154 +++++++++++++++++ .../ic_account_provider_eelo.png | Bin 0 -> 17628 bytes .../ic_account_provider_google.png | Bin 0 -> 21709 bytes app/src/main/res/values/strings.xml | 8 +- .../res/xml/eelo_account_authenticator.xml | 17 ++ .../res/xml/google_account_authenticator.xml | 16 ++ 8 files changed, 374 insertions(+), 2 deletions(-) create mode 100644 app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt create mode 100644 app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt create mode 100644 app/src/main/res/drawable-xxhdpi/ic_account_provider_eelo.png create mode 100644 app/src/main/res/drawable-xxhdpi/ic_account_provider_google.png create mode 100644 app/src/main/res/xml/eelo_account_authenticator.xml create mode 100644 app/src/main/res/xml/google_account_authenticator.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 957d80859..5c69b7e8e 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -259,6 +259,32 @@ + + + + + + + + + + + + + + + + + + . + */ + +package at.bitfire.davdroid.syncadapter + +import android.accounts.* +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.Bundle +import at.bitfire.davdroid.R +import at.bitfire.davdroid.db.AppDatabase +import at.bitfire.davdroid.log.Logger +import at.bitfire.davdroid.resource.LocalAddressBook +import at.bitfire.davdroid.ui.setup.LoginActivity +import dagger.hilt.android.AndroidEntryPoint +import java.util.logging.Level +import javax.inject.Inject +import kotlin.concurrent.thread + +/** + * Account authenticator for the eelo account type. + * + * Gets started when an eelo account is removed, too, so it also watches for account removals + * and contains the corresponding cleanup code. + */ + +@AndroidEntryPoint +class EeloAccountAuthenticatorService : Service(), OnAccountsUpdateListener { + + companion object { + + fun cleanupAccounts(context: Context, db: AppDatabase) { + Logger.log.info("Cleaning up orphaned accounts") + + val accountManager = AccountManager.get(context) + val accountNames = + accountManager.getAccountsByType(context.getString(R.string.account_type)) + .map { it.name } + + // delete orphaned address book accounts + accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)) + .map { LocalAddressBook(context, it, null) } + .forEach { + try { + if (!accountNames.contains(it.mainAccount.name)) + it.delete() + } catch (e: Exception) { + Logger.log.log(Level.SEVERE, "Couldn't delete address book account", e) + } + } + + // delete orphaned services in DB + val serviceDao = db.serviceDao() + if (accountNames.isEmpty()) + serviceDao.deleteAll() + else + serviceDao.deleteExceptAccounts(accountNames.toTypedArray()) + } + + } + + @Inject + lateinit var db: AppDatabase + + private lateinit var accountManager: AccountManager + private lateinit var accountAuthenticator: AccountAuthenticator + + override fun onCreate() { + accountManager = AccountManager.get(this) + accountManager.addOnAccountsUpdatedListener(this, null, true) + + accountAuthenticator = AccountAuthenticator(this) + } + + override fun onDestroy() { + super.onDestroy() + accountManager.removeOnAccountsUpdatedListener(this) + } + + override fun onBind(intent: Intent?) = + accountAuthenticator.iBinder.takeIf { intent?.action == android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT } + + + override fun onAccountsUpdated(accounts: Array?) { + thread { + cleanupAccounts(this, db) + } + } + + + private class AccountAuthenticator( + val context: Context + ) : AbstractAccountAuthenticator(context) { + + override fun addAccount( + response: AccountAuthenticatorResponse?, + accountType: String?, + authTokenType: String?, + requiredFeatures: Array?, + options: Bundle? + ): Bundle { + val intent = Intent(context, LoginActivity::class.java) + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response) + val bundle = Bundle(1) + bundle.putParcelable(AccountManager.KEY_INTENT, intent) + return bundle + } + + override fun editProperties(response: AccountAuthenticatorResponse?, accountType: String?) = + null + + override fun getAuthTokenLabel(p0: String?) = null + override fun confirmCredentials( + p0: AccountAuthenticatorResponse?, + p1: Account?, + p2: Bundle? + ) = null + + override fun updateCredentials( + p0: AccountAuthenticatorResponse?, + p1: Account?, + p2: String?, + p3: Bundle? + ) = null + + override fun getAuthToken( + p0: AccountAuthenticatorResponse?, + p1: Account?, + p2: String?, + p3: Bundle? + ) = null + + override fun hasFeatures( + p0: AccountAuthenticatorResponse?, + p1: Account?, + p2: Array? + ) = null + + } +} + diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt new file mode 100644 index 000000000..b25532ad1 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt @@ -0,0 +1,154 @@ +/* + * Copyright ECORP SAS 2022 + * 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.syncadapter + +import android.accounts.* +import android.app.Service +import android.content.Context +import android.content.Intent +import android.os.Bundle +import at.bitfire.davdroid.R +import at.bitfire.davdroid.db.AppDatabase +import at.bitfire.davdroid.log.Logger +import at.bitfire.davdroid.resource.LocalAddressBook +import at.bitfire.davdroid.ui.setup.LoginActivity +import dagger.hilt.android.AndroidEntryPoint +import java.util.logging.Level +import javax.inject.Inject +import kotlin.concurrent.thread + +/** + * Account authenticator for the Google account type. + * + * Gets started when a Google account is removed, too, so it also watches for account removals + * and contains the corresponding cleanup code. + */ + +@AndroidEntryPoint +class GoogleAccountAuthenticatorService : Service(), OnAccountsUpdateListener { + + companion object { + fun cleanupAccounts(context: Context, db: AppDatabase) { + Logger.log.info("Cleaning up orphaned accounts") + + val accountManager = AccountManager.get(context) + val accountNames = + accountManager.getAccountsByType(context.getString(R.string.account_type)) + .map { it.name } + + // delete orphaned address book accounts + accountManager.getAccountsByType(context.getString(R.string.account_type_address_book)) + .map { LocalAddressBook(context, it, null) } + .forEach { + try { + if (!accountNames.contains(it.mainAccount.name)) + it.delete() + } catch (e: Exception) { + Logger.log.log(Level.SEVERE, "Couldn't delete address book account", e) + } + } + + // delete orphaned services in DB + val serviceDao = db.serviceDao() + if (accountNames.isEmpty()) + serviceDao.deleteAll() + else + serviceDao.deleteExceptAccounts(accountNames.toTypedArray()) + } + + } + + @Inject + lateinit var db: AppDatabase + + private lateinit var accountManager: AccountManager + private lateinit var accountAuthenticator: AccountAuthenticator + + override fun onCreate() { + accountManager = AccountManager.get(this) + accountManager.addOnAccountsUpdatedListener(this, null, true) + + accountAuthenticator = AccountAuthenticator(this) + } + + override fun onDestroy() { + super.onDestroy() + accountManager.removeOnAccountsUpdatedListener(this) + } + + override fun onBind(intent: Intent?) = + accountAuthenticator.iBinder.takeIf { intent?.action == android.accounts.AccountManager.ACTION_AUTHENTICATOR_INTENT } + + + override fun onAccountsUpdated(accounts: Array?) { + thread { + cleanupAccounts(this, db) + } + } + + + private class AccountAuthenticator( + val context: Context + ) : AbstractAccountAuthenticator(context) { + + override fun addAccount( + response: AccountAuthenticatorResponse?, + accountType: String?, + authTokenType: String?, + requiredFeatures: Array?, + options: Bundle? + ): Bundle { + val intent = Intent(context, LoginActivity::class.java) + intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response) + val bundle = Bundle(1) + bundle.putParcelable(AccountManager.KEY_INTENT, intent) + return bundle + } + + override fun editProperties(response: AccountAuthenticatorResponse?, accountType: String?) = + null + + override fun getAuthTokenLabel(p0: String?) = null + override fun confirmCredentials( + p0: AccountAuthenticatorResponse?, + p1: Account?, + p2: Bundle? + ) = null + + override fun updateCredentials( + p0: AccountAuthenticatorResponse?, + p1: Account?, + p2: String?, + p3: Bundle? + ) = null + + override fun getAuthToken( + p0: AccountAuthenticatorResponse?, + p1: Account?, + p2: String?, + p3: Bundle? + ) = null + + override fun hasFeatures( + p0: AccountAuthenticatorResponse?, + p1: Account?, + p2: Array? + ) = null + + } +} + diff --git a/app/src/main/res/drawable-xxhdpi/ic_account_provider_eelo.png b/app/src/main/res/drawable-xxhdpi/ic_account_provider_eelo.png new file mode 100644 index 0000000000000000000000000000000000000000..435feabb03767ec8b09e320225926ffdf44990e2 GIT binary patch literal 17628 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/drawable-xxhdpi/ic_account_provider_google.png b/app/src/main/res/drawable-xxhdpi/ic_account_provider_google.png new file mode 100644 index 0000000000000000000000000000000000000000..2154694f964da71ead68a6ff4baa784a21e76a03 GIT binary patch literal 21709 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 literal 0 HcmV?d00001 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e37ce368c..b2302a1c3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -6,8 +6,12 @@ davx5app Account does not exist (anymore) - bitfire.at.davdroid - at.bitfire.davdroid.address_book + e.foundation.webdav + Google + e.foundation.webdav.google + eelo + e.foundation.webdav.eelo + foundation.e.accountmanager.address_book DAVx⁵ Address book at.bitfire.davdroid.addressbooks Address books diff --git a/app/src/main/res/xml/eelo_account_authenticator.xml b/app/src/main/res/xml/eelo_account_authenticator.xml new file mode 100644 index 000000000..90b164e22 --- /dev/null +++ b/app/src/main/res/xml/eelo_account_authenticator.xml @@ -0,0 +1,17 @@ + + + + + diff --git a/app/src/main/res/xml/google_account_authenticator.xml b/app/src/main/res/xml/google_account_authenticator.xml new file mode 100644 index 000000000..0ba6c7a61 --- /dev/null +++ b/app/src/main/res/xml/google_account_authenticator.xml @@ -0,0 +1,16 @@ + + + + -- GitLab From 8a6c1652a7b892559c96e1e64991785174a593fc Mon Sep 17 00:00:00 2001 From: Nihar Thakkar Date: Thu, 7 Jun 2018 20:04:58 +0530 Subject: [PATCH 004/285] Auto-fill URL for the eelo Contacts and Calendar service. --- .../EeloAccountAuthenticatorService.kt | 1 + .../davdroid/ui/setup/LoginActivity.kt | 23 ++++++++++++++++--- 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt index f4b1a3c7f..26c7d9d3c 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/EeloAccountAuthenticatorService.kt @@ -115,6 +115,7 @@ class EeloAccountAuthenticatorService : Service(), OnAccountsUpdateListener { ): Bundle { val intent = Intent(context, LoginActivity::class.java) intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response) + intent.putExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE, LoginActivity.ACCOUNT_PROVIDER_EELO) val bundle = Bundle(1) bundle.putParcelable(AccountManager.KEY_INTENT, intent) return bundle diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt index e03c39899..35758c735 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt @@ -42,6 +42,9 @@ class LoginActivity: AppCompatActivity() { */ const val EXTRA_PASSWORD = "password" + const val SETUP_ACCOUNT_PROVIDER_TYPE = "setup_account_provider_type" + const val ACCOUNT_PROVIDER_EELO = "eelo" + const val ACCOUNT_PROVIDER_GOOGLE = "google" } @Inject @@ -63,9 +66,23 @@ class LoginActivity: AppCompatActivity() { } if (fragment != null) { - supportFragmentManager.beginTransaction() - .replace(android.R.id.content, fragment) - .commit() + when (intent.getStringExtra(SETUP_ACCOUNT_PROVIDER_TYPE)) { + ACCOUNT_PROVIDER_EELO -> { + intent.putExtra(EXTRA_URL, "https://drive.eelo.io") + // first call, add first login fragment + supportFragmentManager.beginTransaction() + .replace(android.R.id.content, fragment) + .commit() + } + ACCOUNT_PROVIDER_GOOGLE -> { + print("starting google account sign-in") + } + else -> + // first call, add first login fragment + supportFragmentManager.beginTransaction() + .replace(android.R.id.content, fragment) + .commit() + } } else Logger.log.severe("Couldn't create LoginFragment") } -- GitLab From 9baeaef3f06b5912fece626c002be0c50ec28b6f Mon Sep 17 00:00:00 2001 From: Nihar Thakkar Date: Thu, 7 Jun 2018 23:12:08 +0530 Subject: [PATCH 005/285] Implemented Google authentication. --- app/build.gradle | 8 + app/src/main/AndroidManifest.xml | 39 +++ .../authorization/IdentityProvider.java | 236 ++++++++++++++++++ .../GoogleAccountAuthenticatorService.kt | 1 + .../ui/setup/GoogleAuthenticatorFragment.kt | 172 +++++++++++++ .../davdroid/ui/setup/LoginActivity.kt | 6 +- .../layout/fragment_google_authenticator.xml | 23 ++ .../values/email_providers_auth_config.xml | 18 ++ gradle.properties | 1 + 9 files changed, 503 insertions(+), 1 deletion(-) create mode 100644 app/src/main/java/at/bitfire/davdroid/authorization/IdentityProvider.java create mode 100644 app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt create mode 100644 app/src/main/res/layout/fragment_google_authenticator.xml create mode 100644 app/src/main/res/values/email_providers_auth_config.xml diff --git a/app/build.gradle b/app/build.gradle index 012ff381e..7b272ad90 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -6,6 +6,7 @@ apply plugin: 'com.android.application' apply plugin: 'com.mikepenz.aboutlibraries.plugin' apply plugin: 'dagger.hilt.android.plugin' apply plugin: 'kotlin-android' +apply plugin: 'kotlin-android-extensions' apply plugin: 'kotlin-kapt' android { @@ -93,6 +94,12 @@ android { excludes += ['META-INF/*.md'] } } + + defaultConfig { + manifestPlaceholders = [ + 'appAuthRedirectScheme': 'com.googleusercontent.apps.628867657910-7ade6gut5rhabdgjq6k4rln9i1u9ppca' + ] + } } dependencies { @@ -147,6 +154,7 @@ dependencies { implementation "org.apache.commons:commons-lang3:${versions.commonsLang}" //noinspection GradleDependency implementation "org.apache.commons:commons-text:${versions.commonsText}" + implementation 'net.openid:appauth:0.11.1' // for tests androidTestImplementation "com.google.dagger:hilt-android-testing:${versions.hilt}" diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 5c69b7e8e..b89beb982 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -286,6 +286,45 @@ android:name="android.accounts.AccountAuthenticator" android:resource="@xml/eelo_account_authenticator" /> + + + + + + + + + + + + + + + + + + + + + + + + + + . + */ + +package at.bitfire.davdroid.authorization; + +import android.content.Context; +import android.content.res.Resources; +import android.net.Uri; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; + +import net.openid.appauth.AuthorizationServiceConfiguration; +import net.openid.appauth.AuthorizationServiceConfiguration.RetrieveConfigurationCallback; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import at.bitfire.davdroid.R; + +/** + * An abstraction of identity providers, containing all necessary info for the demo app. + */ +public class IdentityProvider { + + /** + * Value used to indicate that a configured property is not specified or required. + */ + public static final int NOT_SPECIFIED = -1; + + public static final IdentityProvider GOOGLE = new IdentityProvider( + "Google", + R.string.google_discovery_uri, + NOT_SPECIFIED, // auth endpoint is discovered + NOT_SPECIFIED, // token endpoint is discovered + R.string.google_client_id, + NOT_SPECIFIED, // client secret is not required for Google + R.string.google_auth_redirect_uri, + R.string.google_scope_string, + R.string.google_name); + + public static final List PROVIDERS = Arrays.asList( + GOOGLE); + + public static List getEnabledProviders(Context context) { + ArrayList providers = new ArrayList<>(); + for (IdentityProvider provider : PROVIDERS) { + provider.readConfiguration(context); + providers.add(provider); + } + return providers; + } + + @NonNull + public final String name; + + @StringRes + public final int buttonContentDescriptionRes; + + @StringRes + private final int mDiscoveryEndpointRes; + + @StringRes + private final int mAuthEndpointRes; + + @StringRes + private final int mTokenEndpointRes; + + @StringRes + private final int mClientIdRes; + + @StringRes + private final int mClientSecretRes; + + @StringRes + private final int mRedirectUriRes; + + @StringRes + private final int mScopeRes; + + private boolean mConfigurationRead = false; + private Uri mDiscoveryEndpoint; + private Uri mAuthEndpoint; + private Uri mTokenEndpoint; + private String mClientId; + private String mClientSecret; + private Uri mRedirectUri; + private String mScope; + + IdentityProvider( + @NonNull String name, + @StringRes int discoveryEndpointRes, + @StringRes int authEndpointRes, + @StringRes int tokenEndpointRes, + @StringRes int clientIdRes, + @StringRes int clientSecretRes, + @StringRes int redirectUriRes, + @StringRes int scopeRes, + @StringRes int buttonContentDescriptionRes) { + if (!isSpecified(discoveryEndpointRes) + && !isSpecified(authEndpointRes) + && !isSpecified(tokenEndpointRes)) { + throw new IllegalArgumentException( + "the discovery endpoint or the auth and token endpoints must be specified"); + } + + this.name = name; + this.mDiscoveryEndpointRes = discoveryEndpointRes; + this.mAuthEndpointRes = authEndpointRes; + this.mTokenEndpointRes = tokenEndpointRes; + this.mClientIdRes = checkSpecified(clientIdRes, "clientIdRes"); + this.mClientSecretRes = clientSecretRes; + this.mRedirectUriRes = checkSpecified(redirectUriRes, "redirectUriRes"); + this.mScopeRes = checkSpecified(scopeRes, "scopeRes"); + this.buttonContentDescriptionRes = + checkSpecified(buttonContentDescriptionRes, "buttonContentDescriptionRes"); + } + + /** + * This must be called before any of the getters will function. + */ + public void readConfiguration(Context context) { + if (mConfigurationRead) { + return; + } + + Resources res = context.getResources(); + + mDiscoveryEndpoint = isSpecified(mDiscoveryEndpointRes) + ? getUriResource(res, mDiscoveryEndpointRes, "discoveryEndpointRes") + : null; + mAuthEndpoint = isSpecified(mAuthEndpointRes) + ? getUriResource(res, mAuthEndpointRes, "authEndpointRes") + : null; + mTokenEndpoint = isSpecified(mTokenEndpointRes) + ? getUriResource(res, mTokenEndpointRes, "tokenEndpointRes") + : null; + mClientId = res.getString(mClientIdRes); + mClientSecret = isSpecified(mClientSecretRes) ? res.getString(mClientSecretRes) : null; + mRedirectUri = getUriResource(res, mRedirectUriRes, "mRedirectUriRes"); + mScope = res.getString(mScopeRes); + + mConfigurationRead = true; + } + + private void checkConfigurationRead() { + if (!mConfigurationRead) { + throw new IllegalStateException("Configuration not read"); + } + } + + @Nullable + public Uri getDiscoveryEndpoint() { + checkConfigurationRead(); + return mDiscoveryEndpoint; + } + + @Nullable + public Uri getAuthEndpoint() { + checkConfigurationRead(); + return mAuthEndpoint; + } + + @Nullable + public Uri getTokenEndpoint() { + checkConfigurationRead(); + return mTokenEndpoint; + } + + @NonNull + public String getClientId() { + checkConfigurationRead(); + return mClientId; + } + + @Nullable + public String getClientSecret() { + checkConfigurationRead(); + return mClientSecret; + } + + @NonNull + public Uri getRedirectUri() { + checkConfigurationRead(); + return mRedirectUri; + } + + @NonNull + public String getScope() { + checkConfigurationRead(); + return mScope; + } + + public void retrieveConfig(Context context, + RetrieveConfigurationCallback callback) { + readConfiguration(context); + if (getDiscoveryEndpoint() != null) { + AuthorizationServiceConfiguration.fetchFromUrl(mDiscoveryEndpoint, callback); + } else { + AuthorizationServiceConfiguration config = + new AuthorizationServiceConfiguration(mAuthEndpoint, mTokenEndpoint, null); + callback.onFetchConfigurationCompleted(config, null); + } + } + + private static boolean isSpecified(int value) { + return value != NOT_SPECIFIED; + } + + private static int checkSpecified(int value, String valueName) { + if (value == NOT_SPECIFIED) { + throw new IllegalArgumentException(valueName + " must be specified"); + } + return value; + } + + private static Uri getUriResource(Resources res, @StringRes int resId, String resName) { + return Uri.parse(res.getString(resId)); + } +} + diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt index b25532ad1..7a8c35c37 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/GoogleAccountAuthenticatorService.kt @@ -114,6 +114,7 @@ class GoogleAccountAuthenticatorService : Service(), OnAccountsUpdateListener { ): Bundle { val intent = Intent(context, LoginActivity::class.java) intent.putExtra(AccountManager.KEY_ACCOUNT_AUTHENTICATOR_RESPONSE, response) + intent.putExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE, LoginActivity.ACCOUNT_PROVIDER_GOOGLE) val bundle = Bundle(1) bundle.putParcelable(AccountManager.KEY_INTENT, intent) return bundle diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt new file mode 100644 index 000000000..cb1c9dacf --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt @@ -0,0 +1,172 @@ +/* + * Copyright ECORP SAS 2022 + * 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.ui.setup + +import android.app.PendingIntent +import android.content.Context +import android.content.Intent +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import androidx.fragment.app.Fragment +import at.bitfire.davdroid.R +import at.bitfire.davdroid.authorization.IdentityProvider +import kotlinx.android.synthetic.main.fragment_google_authenticator.* +import net.openid.appauth.* + + +class GoogleAuthenticatorFragment : Fragment() { + + private val EXTRA_AUTH_SERVICE_DISCOVERY = "authServiceDiscovery" + private val EXTRA_CLIENT_SECRET = "clientSecret" + + private var authState: AuthState? = null + private var authorizationService: AuthorizationService? = null + + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, + savedInstanceState: Bundle?): View? { + val view = inflater.inflate(R.layout.fragment_google_authenticator, container, false) + + // Initialise the authorization service + authorizationService = AuthorizationService(context!!) + + activity?.intent?.let { + if (!with(it) { getBooleanExtra(LoginActivity.ACCOUNT_PROVIDER_GOOGLE_AUTH_COMPLETE, false) }) { + // Get all the account providers + val providers = IdentityProvider.getEnabledProviders(context) + + // Iterate over the account providers + for (idp in providers) { + val retrieveCallback = AuthorizationServiceConfiguration.RetrieveConfigurationCallback { serviceConfiguration, ex -> + if (ex == null && serviceConfiguration != null) { + makeAuthRequest(serviceConfiguration, idp) + } + else { + // TODO Handle error + } + } + + if (idp.name.equals(getString(R.string.google_name))) { + // Get configurations for the Google account provider + idp.retrieveConfig(context, retrieveCallback) + } + } + } + else { + if (authState == null) { + val response = AuthorizationResponse.fromIntent(activity!!.intent) + val ex = AuthorizationException.fromIntent(activity!!.intent) + authState = AuthState(response, ex) + + if (response != null) { + exchangeAuthorizationCode(response) + } + else { + // TODO Handle error + } + } + } + } + + return view + } + + private fun makeAuthRequest( + serviceConfig: AuthorizationServiceConfiguration, + idp: IdentityProvider) { + + val authRequest = AuthorizationRequest.Builder( + serviceConfig, + idp.clientId, + ResponseTypeValues.CODE, + idp.redirectUri) + .setScope(idp.scope) + .build() + + authorizationService?.performAuthorizationRequest( + authRequest, + createPostAuthorizationIntent( + context!!, + authRequest, + serviceConfig.discoveryDoc, + idp.clientSecret), + authorizationService?.createCustomTabsIntentBuilder()!! + .build()) + + activity?.finish() + } + + private fun createPostAuthorizationIntent( + context: Context, + request: AuthorizationRequest, + discoveryDoc: AuthorizationServiceDiscovery?, + clientSecret: String?): PendingIntent { + val intent = Intent(context, LoginActivity::class.java) + + if (discoveryDoc != null) { + intent.putExtra(EXTRA_AUTH_SERVICE_DISCOVERY, discoveryDoc.docJson.toString()) + } + + if (clientSecret != null) { + intent.putExtra(EXTRA_CLIENT_SECRET, clientSecret) + } + + intent.putExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE, LoginActivity.ACCOUNT_PROVIDER_GOOGLE) + intent.putExtra(LoginActivity.ACCOUNT_PROVIDER_GOOGLE_AUTH_COMPLETE, true) + + return PendingIntent.getActivity(context, request.hashCode(), intent, 0) + } + + private fun exchangeAuthorizationCode(authorizationResponse: AuthorizationResponse) { + val additionalParams = HashMap() + if (getClientSecretFromIntent(activity!!.intent) != null) { + additionalParams["client_secret"] = getClientSecretFromIntent(activity!!.intent) + } + performTokenRequest(authorizationResponse.createTokenExchangeRequest(additionalParams)) + } + + private fun getClientSecretFromIntent(intent: Intent): String? { + return if (!intent.hasExtra(EXTRA_CLIENT_SECRET)) { + null + } + else intent.getStringExtra(EXTRA_CLIENT_SECRET) + } + + + private fun performTokenRequest(request: TokenRequest) { + authorizationService?.performTokenRequest( + request, + AuthorizationService.TokenResponseCallback { tokenResponse, ex -> receivedTokenResponse(tokenResponse, ex) }) + } + + private fun receivedTokenResponse( + tokenResponse: TokenResponse?, + authException: AuthorizationException?) { + authState?.update(tokenResponse, authException) + progress_bar.visibility = View.GONE + auth_token_success_text_view.visibility = View.VISIBLE + } + + override fun onDestroy() { + super.onDestroy() + authorizationService?.dispose() + } + + +} + diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt index 35758c735..f963eb96a 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/LoginActivity.kt @@ -45,6 +45,7 @@ class LoginActivity: AppCompatActivity() { const val SETUP_ACCOUNT_PROVIDER_TYPE = "setup_account_provider_type" const val ACCOUNT_PROVIDER_EELO = "eelo" const val ACCOUNT_PROVIDER_GOOGLE = "google" + const val ACCOUNT_PROVIDER_GOOGLE_AUTH_COMPLETE = "google_auth_complete" } @Inject @@ -68,6 +69,7 @@ class LoginActivity: AppCompatActivity() { if (fragment != null) { when (intent.getStringExtra(SETUP_ACCOUNT_PROVIDER_TYPE)) { ACCOUNT_PROVIDER_EELO -> { + // Set the eelo Contacts and Calendar service URL intent.putExtra(EXTRA_URL, "https://drive.eelo.io") // first call, add first login fragment supportFragmentManager.beginTransaction() @@ -75,7 +77,9 @@ class LoginActivity: AppCompatActivity() { .commit() } ACCOUNT_PROVIDER_GOOGLE -> { - print("starting google account sign-in") + supportFragmentManager.beginTransaction() + .replace(android.R.id.content, GoogleAuthenticatorFragment()) + .commit() } else -> // first call, add first login fragment diff --git a/app/src/main/res/layout/fragment_google_authenticator.xml b/app/src/main/res/layout/fragment_google_authenticator.xml new file mode 100644 index 000000000..eca69e0bb --- /dev/null +++ b/app/src/main/res/layout/fragment_google_authenticator.xml @@ -0,0 +1,23 @@ + + + + + + + + + diff --git a/app/src/main/res/values/email_providers_auth_config.xml b/app/src/main/res/values/email_providers_auth_config.xml new file mode 100644 index 000000000..597bf0f9b --- /dev/null +++ b/app/src/main/res/values/email_providers_auth_config.xml @@ -0,0 +1,18 @@ + + + + + Google + + 100496780587-pbiu5eudcjm6cge2phduc6mt8mgbsmsr.apps.googleusercontent.com + + https://accounts.google.com/.well-known/openid-configuration + + openid profile email https://www.googleapis.com/auth/carddav + + + com.googleusercontent.apps.100496780587-pbiu5eudcjm6cge2phduc6mt8mgbsmsr:/oauth2redirect + + + + diff --git a/gradle.properties b/gradle.properties index c6bf06227..824f2fa91 100644 --- a/gradle.properties +++ b/gradle.properties @@ -16,3 +16,4 @@ android.useAndroidX=true org.gradle.daemon=true org.gradle.jvmargs=-Xmx1536M -Dkotlin.daemon.jvm.options\="-Xmx1536M" org.gradle.parallel=true +android.enableJetifier=true -- GitLab From 8432029dc574a9293927d7744a752732cdf968ef Mon Sep 17 00:00:00 2001 From: Nihar Thakkar Date: Fri, 8 Jun 2018 11:04:15 +0530 Subject: [PATCH 006/285] Updated Kotlin plugin version. Get account details upon successful authentication/authorization. Added Google Calendar scope for authorization. --- .../ui/setup/GoogleAuthenticatorFragment.kt | 179 ++++++++++++++++-- .../values/email_providers_auth_config.xml | 2 +- 2 files changed, 166 insertions(+), 15 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt index cb1c9dacf..5a6561f0a 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt @@ -19,7 +19,10 @@ package at.bitfire.davdroid.ui.setup import android.app.PendingIntent import android.content.Context import android.content.Intent +import android.os.AsyncTask import android.os.Bundle +import android.os.Handler +import android.os.Looper import android.view.LayoutInflater import android.view.View import android.view.ViewGroup @@ -28,16 +31,28 @@ import at.bitfire.davdroid.R import at.bitfire.davdroid.authorization.IdentityProvider import kotlinx.android.synthetic.main.fragment_google_authenticator.* import net.openid.appauth.* +import org.json.JSONException +import org.json.JSONObject +import java.io.BufferedReader +import java.io.IOException +import java.io.InputStream +import java.io.InputStreamReader +import java.net.HttpURLConnection +import java.net.MalformedURLException +import java.net.URL -class GoogleAuthenticatorFragment : Fragment() { +class GoogleAuthenticatorFragment : Fragment(), AuthorizationService.TokenResponseCallback { - private val EXTRA_AUTH_SERVICE_DISCOVERY = "authServiceDiscovery" - private val EXTRA_CLIENT_SECRET = "clientSecret" + private val extraAuthServiceDiscovery = "authServiceDiscovery" + private val extraClientSecret = "clientSecret" private var authState: AuthState? = null private var authorizationService: AuthorizationService? = null + private val bufferSize = 1024 + private var userInfoJson: JSONObject? = null + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { val view = inflater.inflate(R.layout.fragment_google_authenticator, container, false) @@ -61,7 +76,7 @@ class GoogleAuthenticatorFragment : Fragment() { } } - if (idp.name.equals(getString(R.string.google_name))) { + if (idp.name == getString(R.string.google_name)) { // Get configurations for the Google account provider idp.retrieveConfig(context, retrieveCallback) } @@ -119,11 +134,11 @@ class GoogleAuthenticatorFragment : Fragment() { val intent = Intent(context, LoginActivity::class.java) if (discoveryDoc != null) { - intent.putExtra(EXTRA_AUTH_SERVICE_DISCOVERY, discoveryDoc.docJson.toString()) + intent.putExtra(extraAuthServiceDiscovery, discoveryDoc.docJson.toString()) } if (clientSecret != null) { - intent.putExtra(EXTRA_CLIENT_SECRET, clientSecret) + intent.putExtra(extraClientSecret, clientSecret) } intent.putExtra(LoginActivity.SETUP_ACCOUNT_PROVIDER_TYPE, LoginActivity.ACCOUNT_PROVIDER_GOOGLE) @@ -141,25 +156,161 @@ class GoogleAuthenticatorFragment : Fragment() { } private fun getClientSecretFromIntent(intent: Intent): String? { - return if (!intent.hasExtra(EXTRA_CLIENT_SECRET)) { + return if (!intent.hasExtra(extraClientSecret)) { null } - else intent.getStringExtra(EXTRA_CLIENT_SECRET) + else intent.getStringExtra(extraClientSecret) } private fun performTokenRequest(request: TokenRequest) { authorizationService?.performTokenRequest( - request, - AuthorizationService.TokenResponseCallback { tokenResponse, ex -> receivedTokenResponse(tokenResponse, ex) }) + request, this) } - private fun receivedTokenResponse( - tokenResponse: TokenResponse?, - authException: AuthorizationException?) { - authState?.update(tokenResponse, authException) + override fun onTokenRequestCompleted(response: TokenResponse?, ex: AuthorizationException?) { + authState?.update(response, ex) progress_bar.visibility = View.GONE auth_token_success_text_view.visibility = View.VISIBLE + + getAccountInfo() + } + + private fun getAccountInfo() { + val discoveryDoc = getDiscoveryDocFromIntent(activity!!.intent) + + if (!authState!!.isAuthorized + || discoveryDoc == null + || discoveryDoc.userinfoEndpoint == null) { + //TODO Error occurred + } + else { + object : AsyncTask() { + override fun doInBackground(vararg params: Void): Void? { + fetchUserInfo() + return null + } + }.execute() + } + } + + private fun getDiscoveryDocFromIntent(intent: Intent): AuthorizationServiceDiscovery? { + if (!intent.hasExtra(extraAuthServiceDiscovery)) { + return null + } + val discoveryJson = intent.getStringExtra(extraAuthServiceDiscovery) + try { + return AuthorizationServiceDiscovery(JSONObject(discoveryJson)) + } + catch (ex: JSONException) { + throw IllegalStateException("Malformed JSON in discovery doc") + } + catch (ex: AuthorizationServiceDiscovery.MissingArgumentException) { + throw IllegalStateException("Malformed JSON in discovery doc") + } + + } + + private fun fetchUserInfo() { + if (authState!!.authorizationServiceConfiguration == null) { + // TODO Handle error due to unavailable service configuration + return + } + + authState!!.performActionWithFreshTokens(authorizationService!!, AuthState.AuthStateAction { accessToken, _, ex -> + if (ex != null) { + // TODO An exception occurred, handle error + return@AuthStateAction + } + + val discoveryDoc = getDiscoveryDocFromIntent(activity!!.intent) + ?: throw IllegalStateException("no available discovery doc") + + val userInfoEndpoint: URL + try { + userInfoEndpoint = URL(discoveryDoc.userinfoEndpoint!!.toString()) + } + catch (urlEx: MalformedURLException) { + // TODO Handle error due to malformed URL + return@AuthStateAction + } + + var userInfoResponse: InputStream? = null + try { + val conn = userInfoEndpoint.openConnection() as HttpURLConnection + conn.setRequestProperty("Authorization", "Bearer " + accessToken!!) + conn.instanceFollowRedirects = false + userInfoResponse = conn.inputStream + val response = readStream(userInfoResponse) + updateUserInfo(JSONObject(response)) + } + catch (ioEx: IOException) { + // TODO Handle network error + } + catch (jsonEx: JSONException) { + // TODO Handle JSON parse error + } + finally { + if (userInfoResponse != null) { + try { + userInfoResponse.close() + } + catch (ioEx: IOException) { + // TODO Handle network exception while closing response stream + } + + } + } + }) + } + + @Throws(IOException::class) + private fun readStream(stream: InputStream?): String { + val br = BufferedReader(InputStreamReader(stream!!)) + val buffer = CharArray(bufferSize) + val sb = StringBuilder() + var readCount = br.read(buffer) + while (readCount != -1) { + sb.append(buffer, 0, readCount) + readCount = br.read(buffer) + } + return sb.toString() + } + + private fun updateUserInfo(jsonObject: JSONObject) { + Handler(Looper.getMainLooper()).post { + userInfoJson = jsonObject + onAccountInfoGotten() + } + } + + private fun onAccountInfoGotten() { + if (userInfoJson != null) { + try { + var name = "Unknown" + if (userInfoJson!!.has("name")) { + name = userInfoJson!!.getString("name") + } + + var emailAddress: String? = null + if (userInfoJson!!.has("email")) { + emailAddress = userInfoJson!!.getString("email") + } + + /*account.setName(name) + account.setEmailAddress(emailAddress) + account.setAuthToken(authState!!.accessToken) + account.setRefreshToken(authState!!.refreshToken)*/ + } + catch (ex: JSONException) { + // TODO Handle JSON parse error + } + + } + else { + //TODO Handle error + } + } override fun onDestroy() { diff --git a/app/src/main/res/values/email_providers_auth_config.xml b/app/src/main/res/values/email_providers_auth_config.xml index 597bf0f9b..f4f123158 100644 --- a/app/src/main/res/values/email_providers_auth_config.xml +++ b/app/src/main/res/values/email_providers_auth_config.xml @@ -8,7 +8,7 @@ https://accounts.google.com/.well-known/openid-configuration - openid profile email https://www.googleapis.com/auth/carddav + openid profile email https://www.googleapis.com/auth/carddav https://www.googleapis.com/auth/calendar com.googleusercontent.apps.100496780587-pbiu5eudcjm6cge2phduc6mt8mgbsmsr:/oauth2redirect -- GitLab From e5604118fd370db56f1b40fffd6974f3f044c2ce Mon Sep 17 00:00:00 2001 From: Sunik Kupfer Date: Fri, 19 Aug 2022 12:16:09 +0200 Subject: [PATCH 007/285] confirmation dialog for removing webdav mount (closes bitfireAT/davx5#47) (#119) * confirmation dialog for removing webdav mount Co-authored-by: Ricki Hirner --- .../ui/webdav/WebdavMountsActivity.kt | 21 ++++++++++++++++--- app/src/main/res/drawable/ic_eject.xml | 5 ----- app/src/main/res/drawable/ic_remove.xml | 5 +++++ .../main/res/layout/webdav_mounts_item.xml | 4 ++-- app/src/main/res/values/strings.xml | 4 ++++ 5 files changed, 29 insertions(+), 10 deletions(-) delete mode 100644 app/src/main/res/drawable/ic_eject.xml create mode 100644 app/src/main/res/drawable/ic_remove.xml diff --git a/app/src/main/java/at/bitfire/davdroid/ui/webdav/WebdavMountsActivity.kt b/app/src/main/java/at/bitfire/davdroid/ui/webdav/WebdavMountsActivity.kt index 149d76b94..3fdd02509 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/webdav/WebdavMountsActivity.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/webdav/WebdavMountsActivity.kt @@ -4,6 +4,7 @@ package at.bitfire.davdroid.ui.webdav +import android.app.AlertDialog import android.content.Context import android.content.Intent import android.os.Bundle @@ -26,6 +27,7 @@ import at.bitfire.davdroid.databinding.WebdavMountsItemBinding import at.bitfire.davdroid.db.AppDatabase import at.bitfire.davdroid.db.WebDavDocument import at.bitfire.davdroid.db.WebDavMount +import at.bitfire.davdroid.log.Logger import at.bitfire.davdroid.ui.UiUtils import at.bitfire.davdroid.webdav.CredentialsStore import at.bitfire.davdroid.webdav.DavDocumentsProvider @@ -35,8 +37,10 @@ import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.apache.commons.io.FileUtils +import java.util.logging.Level import javax.inject.Inject + @AndroidEntryPoint class WebdavMountsActivity: AppCompatActivity() { @@ -162,8 +166,16 @@ class WebdavMountsActivity: AppCompatActivity() { model.browseIntent.value = null } - binding.unmount.setOnClickListener { - model.unmount(info.mount) + binding.removeMountpoint.setOnClickListener { + AlertDialog.Builder(context) + .setTitle(R.string.webdav_remove_mount_title) + .setMessage(R.string.webdav_remove_mount_text) + .setPositiveButton(R.string.dialog_remove) { _, _ -> + Logger.log.log(Level.INFO, "User removes mount point", info.mount) + model.remove(info.mount) + } + .setNegativeButton(R.string.dialog_deny, null) + .show() } } } @@ -212,7 +224,10 @@ class WebdavMountsActivity: AppCompatActivity() { val browseIntent = MutableLiveData() - fun unmount(mount: WebDavMount) { + /** + * Removes the mountpoint (deleting connection information) + */ + fun remove(mount: WebDavMount) { viewModelScope.launch(Dispatchers.IO) { // remove mount from database db.webDavMountDao().delete(mount) diff --git a/app/src/main/res/drawable/ic_eject.xml b/app/src/main/res/drawable/ic_eject.xml deleted file mode 100644 index 0a8c42659..000000000 --- a/app/src/main/res/drawable/ic_eject.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_remove.xml b/app/src/main/res/drawable/ic_remove.xml new file mode 100644 index 000000000..8561a3390 --- /dev/null +++ b/app/src/main/res/drawable/ic_remove.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/layout/webdav_mounts_item.xml b/app/src/main/res/layout/webdav_mounts_item.xml index 3f66c9e33..627b2105d 100644 --- a/app/src/main/res/layout/webdav_mounts_item.xml +++ b/app/src/main/res/layout/webdav_mounts_item.xml @@ -69,13 +69,13 @@ android:text="@string/webdav_mounts_share_content" /> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e37ce368c..e1efc0bac 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -11,6 +11,8 @@ DAVx⁵ Address book at.bitfire.davdroid.addressbooks Address books + Remove + Cancel This field is required Help Manage accounts @@ -462,6 +464,8 @@ Password Add mount No WebDAV service at this URL + Remove mount point + Connection details will be lost, but no files will be deleted. Accessing WebDAV file Downloading WebDAV file Uploading WebDAV file -- GitLab From 885035fadfc5f6feb707b5c72279e9f4d092e0b7 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Mon, 15 Aug 2022 12:39:57 +0200 Subject: [PATCH 008/285] Update dependencies --- app/build.gradle | 4 ++-- build.gradle | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b0970d16e..4308cfb06 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -107,12 +107,12 @@ dependencies { implementation "com.google.dagger:hilt-android:${versions.hilt}" kapt "com.google.dagger:hilt-android-compiler:${versions.hilt}" - implementation 'androidx.appcompat:appcompat:1.4.2' + implementation 'androidx.appcompat:appcompat:1.5.0' implementation 'androidx.browser:browser:1.4.0' implementation 'androidx.cardview:cardview:1.0.0' implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.core:core-ktx:1.8.0' - implementation 'androidx.fragment:fragment-ktx:1.5.1' + implementation 'androidx.fragment:fragment-ktx:1.5.2' implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' implementation 'androidx.paging:paging-runtime-ktx:3.1.1' diff --git a/build.gradle b/build.gradle index d06901aa3..fb131ffaa 100644 --- a/build.gradle +++ b/build.gradle @@ -9,11 +9,11 @@ buildscript { ext.versions = [ aboutLibraries: '8.9.4', - appIntro: '6.1.0', + appIntro: '6.2.0', dav4jvm: 'c61e4b0c80a5a8de1df99b4997445bb323d3ea3d', hilt: '2.42', kotlin: '1.7.0', - okhttp: '4.9.3', + okhttp: '4.10.0', // latest Apache Commons versions that don't require Java 8 (Android 7) commonsCollections: '4.2', commonsLang: '3.8.1', -- GitLab From fec8eae9d0e57cc75860dd9190b561a2fdb5e233 Mon Sep 17 00:00:00 2001 From: Ricki Hirner Date: Fri, 19 Aug 2022 12:33:36 +0200 Subject: [PATCH 009/285] Update dependencies, including ical4android --- ical4android | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ical4android b/ical4android index 41171b57f..ccea6cbd4 160000 --- a/ical4android +++ b/ical4android @@ -1 +1 @@ -Subproject commit 41171b57fdc5f045b17d09df85853a6cc13ef4fd +Subproject commit ccea6cbd487b0872d7e399f290fd00ffbb08f37f -- GitLab From 761ac44e1fcf130f4b0746c07660af7185299794 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Fri, 19 Aug 2022 19:29:51 +0600 Subject: [PATCH 010/285] add dav4android submodule --- .gitmodules | 4 ++++ app/build.gradle | 4 +--- dav4android | 1 + settings.gradle | 1 + 4 files changed, 7 insertions(+), 3 deletions(-) create mode 160000 dav4android diff --git a/.gitmodules b/.gitmodules index 6a6b94f2b..647569050 100644 --- a/.gitmodules +++ b/.gitmodules @@ -7,3 +7,7 @@ [submodule "cert4android"] path = cert4android url = https://github.com/bitfireAT/cert4android.git +[submodule "dav4android"] + path = dav4android + url = https://gitlab.e.foundation/e/apps/dav4android.git + branch = main diff --git a/app/build.gradle b/app/build.gradle index 7b272ad90..bf45cbef8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -106,6 +106,7 @@ dependencies { implementation project(':cert4android') implementation project(':ical4android') implementation project(':vcard4android') + implementation project(':dav4android') implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${versions.kotlin}" implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0" @@ -138,9 +139,6 @@ dependencies { implementation 'com.jaredrummler:colorpicker:1.1.0' implementation "com.github.AppIntro:AppIntro:${versions.appIntro}" - implementation("com.github.bitfireAT:dav4jvm:${versions.dav4jvm}") { - exclude group: 'junit' - } implementation "com.mikepenz:aboutlibraries:${versions.aboutLibraries}" implementation "com.squareup.okhttp3:okhttp:${versions.okhttp}" implementation "com.squareup.okhttp3:okhttp-brotli:${versions.okhttp}" diff --git a/dav4android b/dav4android new file mode 160000 index 000000000..05b97db35 --- /dev/null +++ b/dav4android @@ -0,0 +1 @@ +Subproject commit 05b97db35f73185cb8fefc708e882cbf0b555ed1 diff --git a/settings.gradle b/settings.gradle index e5c3923c5..e9ca5a41c 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,3 +3,4 @@ include ':app' include ':cert4android' include ':ical4android' include ':vcard4android' +include ':dav4android' \ No newline at end of file -- GitLab From 714a63f2ed6343bf272a84d277f2481413345580 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Fri, 19 Aug 2022 20:32:22 +0600 Subject: [PATCH 011/285] Implemented Google Account setup --- .../at/bitfire/davdroid/db/Credentials.kt | 2 + .../davdroid/settings/AccountSettings.kt | 4 ++ .../syncadapter/CalendarSyncManager.kt | 2 +- .../syncadapter/ContactsSyncManager.kt | 2 +- .../davdroid/syncadapter/JtxSyncManager.kt | 2 +- .../davdroid/syncadapter/TasksSyncManager.kt | 2 +- .../davdroid/ui/setup/DavResourceFinder.kt | 8 +-- .../ui/setup/GoogleAuthenticatorFragment.kt | 66 ++++++++++++++++--- .../ui/setup/GoogleAuthenticatorModel.kt | 48 ++++++++++++++ .../davdroid/webdav/DavDocumentsProvider.kt | 2 +- .../layout/fragment_google_authenticator.xml | 44 ++++++++----- app/src/main/res/values/strings.xml | 6 +- 12 files changed, 151 insertions(+), 37 deletions(-) create mode 100644 app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorModel.kt diff --git a/app/src/main/java/at/bitfire/davdroid/db/Credentials.kt b/app/src/main/java/at/bitfire/davdroid/db/Credentials.kt index f2f4758b1..cc80bf405 100644 --- a/app/src/main/java/at/bitfire/davdroid/db/Credentials.kt +++ b/app/src/main/java/at/bitfire/davdroid/db/Credentials.kt @@ -7,6 +7,8 @@ package at.bitfire.davdroid.db data class Credentials( val userName: String? = null, val password: String? = null, + val accessToken: String? = null, + val refreshToken: String? = null, val certificateAlias: String? = null ) { diff --git a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt index 8d414c121..13d3ee569 100644 --- a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt +++ b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt @@ -89,6 +89,8 @@ class AccountSettings( const val KEY_SYNC_INTERVAL_TASKS = "sync_interval_tasks" const val KEY_USERNAME = "user_name" + const val KEY_ACCESS_TOKEN = "access_token" + const val KEY_REFRESH_TOKEN = "refresh_token" const val KEY_CERTIFICATE_ALIAS = "certificate_alias" const val KEY_WIFI_ONLY = "wifi_only" // sync on WiFi only (default: false) @@ -232,6 +234,8 @@ class AccountSettings( fun credentials() = Credentials( accountManager.getUserData(account, KEY_USERNAME), accountManager.getPassword(account), + accountManager.getUserData(account, KEY_ACCESS_TOKEN), + accountManager.getUserData(account, KEY_REFRESH_TOKEN), accountManager.getUserData(account, KEY_CERTIFICATE_ALIAS) ) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt index c38400551..8540d3034 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/CalendarSyncManager.kt @@ -55,7 +55,7 @@ class CalendarSyncManager( override fun prepare(): Boolean { collectionURL = (localCollection.name ?: return false).toHttpUrlOrNull() ?: return false - davCollection = DavCalendar(httpClient.okHttpClient, collectionURL) + davCollection = DavCalendar(httpClient.okHttpClient, collectionURL, accountSettings.credentials().accessToken) // if there are dirty exceptions for events, mark their master events as dirty, too localCollection.processDirtyExceptions() diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt index 2401a0a0d..d9d901bdb 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt @@ -121,7 +121,7 @@ class ContactsSyncManager( } collectionURL = localCollection.url.toHttpUrlOrNull() ?: return false - davCollection = DavAddressBook(httpClient.okHttpClient, collectionURL) + davCollection = DavAddressBook(httpClient.okHttpClient, collectionURL, accountSettings.credentials().accessToken) resourceDownloader = ResourceDownloader(davCollection.location) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/JtxSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/JtxSyncManager.kt index 5ecdc496a..a644d35ff 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/JtxSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/JtxSyncManager.kt @@ -47,7 +47,7 @@ class JtxSyncManager( override fun prepare(): Boolean { collectionURL = (localCollection.url ?: return false).toHttpUrlOrNull() ?: return false - davCollection = DavCalendar(httpClient.okHttpClient, collectionURL) + davCollection = DavCalendar(httpClient.okHttpClient, collectionURL, accountSettings.credentials().accessToken) return true } diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt index d1dbc363e..68ad8d06a 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/TasksSyncManager.kt @@ -50,7 +50,7 @@ class TasksSyncManager( override fun prepare(): Boolean { collectionURL = (localCollection.syncId ?: return false).toHttpUrlOrNull() ?: return false - davCollection = DavCalendar(httpClient.okHttpClient, collectionURL) + davCollection = DavCalendar(httpClient.okHttpClient, collectionURL, accountSettings.credentials().accessToken) return true } diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.kt index f4f4b7bc0..cd346e432 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/DavResourceFinder.kt @@ -163,7 +163,7 @@ class DavResourceFinder( private fun checkUserGivenURL(baseURL: HttpUrl, service: Service, config: Configuration.ServiceInfo) { log.info("Checking user-given URL: $baseURL") - val davBase = DavResource(httpClient.okHttpClient, baseURL, log) + val davBase = DavResource(httpClient.okHttpClient, baseURL, loginModel.credentials!!.accessToken, log) try { when (service) { Service.CARDDAV -> { @@ -199,7 +199,7 @@ class DavResourceFinder( fun queryEmailAddress(principal: HttpUrl): List { val mailboxes = LinkedList() try { - DavResource(httpClient.okHttpClient, principal, log).propfind(0, CalendarUserAddressSet.NAME) { response, _ -> + DavResource(httpClient.okHttpClient, principal, null, log).propfind(0, CalendarUserAddressSet.NAME) { response, _ -> response[CalendarUserAddressSet::class.java]?.let { addressSet -> for (href in addressSet.hrefs) try { @@ -313,7 +313,7 @@ class DavResourceFinder( fun providesService(url: HttpUrl, service: Service): Boolean { var provided = false try { - DavResource(httpClient.okHttpClient, url, log).options { capabilities, _ -> + DavResource(httpClient.okHttpClient, url, loginModel.credentials!!.accessToken, log).options { capabilities, _ -> if ((service == Service.CARDDAV && capabilities.contains("addressbook")) || (service == Service.CALDAV && capabilities.contains("calendar-access"))) provided = true @@ -401,7 +401,7 @@ class DavResourceFinder( @Throws(IOException::class, HttpException::class, DavException::class) fun getCurrentUserPrincipal(url: HttpUrl, service: Service?): HttpUrl? { var principal: HttpUrl? = null - DavResource(httpClient.okHttpClient, url, log).propfind(0, CurrentUserPrincipal.NAME) { response, _ -> + DavResource(httpClient.okHttpClient, url, loginModel.credentials!!.accessToken, log).propfind(0, CurrentUserPrincipal.NAME) { response, _ -> response[CurrentUserPrincipal::class.java]?.href?.let { href -> response.requestedUrl.resolve(href)?.let { log.info("Found current-user-principal: $it") diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt index 5a6561f0a..db67858cb 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt @@ -27,8 +27,11 @@ import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment +import androidx.lifecycle.ViewModelProviders import at.bitfire.davdroid.R import at.bitfire.davdroid.authorization.IdentityProvider +import at.bitfire.davdroid.databinding.FragmentGoogleAuthenticatorBinding +import at.bitfire.davdroid.db.Credentials import kotlinx.android.synthetic.main.fragment_google_authenticator.* import net.openid.appauth.* import org.json.JSONException @@ -39,11 +42,15 @@ import java.io.InputStream import java.io.InputStreamReader import java.net.HttpURLConnection import java.net.MalformedURLException +import java.net.URI import java.net.URL class GoogleAuthenticatorFragment : Fragment(), AuthorizationService.TokenResponseCallback { + private lateinit var model: GoogleAuthenticatorModel + private lateinit var loginModel: LoginModel + private val extraAuthServiceDiscovery = "authServiceDiscovery" private val extraClientSecret = "clientSecret" @@ -55,12 +62,19 @@ class GoogleAuthenticatorFragment : Fragment(), AuthorizationService.TokenRespon override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - val view = inflater.inflate(R.layout.fragment_google_authenticator, container, false) + model = ViewModelProviders.of(this).get(GoogleAuthenticatorModel::class.java) + loginModel = ViewModelProviders.of(requireActivity())[LoginModel::class.java] // Initialise the authorization service authorizationService = AuthorizationService(context!!) + val v = FragmentGoogleAuthenticatorBinding.inflate(inflater, container, false) + v.lifecycleOwner = this + v.model = model + activity?.intent?.let { + model.initialize(it) + if (!with(it) { getBooleanExtra(LoginActivity.ACCOUNT_PROVIDER_GOOGLE_AUTH_COMPLETE, false) }) { // Get all the account providers val providers = IdentityProvider.getEnabledProviders(context) @@ -98,7 +112,7 @@ class GoogleAuthenticatorFragment : Fragment(), AuthorizationService.TokenRespon } } - return view + return v.root } private fun makeAuthRequest( @@ -287,16 +301,17 @@ class GoogleAuthenticatorFragment : Fragment(), AuthorizationService.TokenRespon private fun onAccountInfoGotten() { if (userInfoJson != null) { try { - var name = "Unknown" - if (userInfoJson!!.has("name")) { - name = userInfoJson!!.getString("name") - } - - var emailAddress: String? = null + var emailAddress = "" if (userInfoJson!!.has("email")) { emailAddress = userInfoJson!!.getString("email") } + if (validate(emailAddress, authState!!.accessToken!!, authState!!.refreshToken!!)) + requireFragmentManager().beginTransaction() + .replace(android.R.id.content, DetectConfigurationFragment(), null) + .addToBackStack(null) + .commit() + /*account.setName(name) account.setEmailAddress(emailAddress) account.setAuthToken(authState!!.accessToken) @@ -313,6 +328,41 @@ class GoogleAuthenticatorFragment : Fragment(), AuthorizationService.TokenRespon } + private fun validate(emailAddress: String, accessToken: String, refreshToken: String): Boolean { + var valid = false + + fun validateUrl() { + model.baseUrlError.value = null + try { + val uri = URI("https://www.google.com/calendar/dav/$emailAddress/events") + if (uri.scheme.equals("http", true) || uri.scheme.equals("https", true)) { + valid = true + loginModel.baseURI = uri + } else + model.baseUrlError.value = getString(R.string.login_url_must_be_http_or_https) + } catch (e: Exception) { + model.baseUrlError.value = e.localizedMessage + } + } + + when { + + model.loginWithUrlAndTokens.value == true -> { + validateUrl() + + model.usernameError.value = null + + if (loginModel.baseURI != null) { + valid = true + loginModel.credentials = Credentials(emailAddress, null, accessToken, refreshToken, null) + } + } + + } + + return valid + } + override fun onDestroy() { super.onDestroy() authorizationService?.dispose() diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorModel.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorModel.kt new file mode 100644 index 000000000..d77217537 --- /dev/null +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorModel.kt @@ -0,0 +1,48 @@ +package at.bitfire.davdroid.ui.setup + +import android.content.Intent +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel + +class GoogleAuthenticatorModel: ViewModel() { + + private var initialized = false + + val loginWithUrlAndTokens = MutableLiveData() + + val baseUrl = MutableLiveData() + val baseUrlError = MutableLiveData() + + val emailAddress = MutableLiveData() + val emailAddressError = MutableLiveData() + + val username = MutableLiveData() + val usernameError = MutableLiveData() + + val password = MutableLiveData() + val passwordError = MutableLiveData() + + val certificateAlias = MutableLiveData() + val certificateAliasError = MutableLiveData() + + init { + loginWithUrlAndTokens.value = true + } + + fun initialize(intent: Intent) { + if (initialized) + return + + // we've got initial login data + val givenUrl = intent.getStringExtra(LoginActivity.EXTRA_URL) + val givenUsername = intent.getStringExtra(LoginActivity.EXTRA_USERNAME) + val givenPassword = intent.getStringExtra(LoginActivity.EXTRA_PASSWORD) + + baseUrl.value = givenUrl + + password.value = givenPassword + + initialized = true + } + +} diff --git a/app/src/main/java/at/bitfire/davdroid/webdav/DavDocumentsProvider.kt b/app/src/main/java/at/bitfire/davdroid/webdav/DavDocumentsProvider.kt index 87b148239..793bbc02d 100644 --- a/app/src/main/java/at/bitfire/davdroid/webdav/DavDocumentsProvider.kt +++ b/app/src/main/java/at/bitfire/davdroid/webdav/DavDocumentsProvider.kt @@ -242,7 +242,7 @@ class DavDocumentsProvider: DocumentsProvider() { httpClient(parent.mountId).use { client -> val parentUrl = parent.toHttpUrl(db) - val folder = DavCollection(client.okHttpClient, parentUrl) + val folder = DavCollection(client.okHttpClient, parentUrl, null) folder.propfind(1, *DAV_FILE_FIELDS) { response, relation -> Logger.log.fine("$relation $response") diff --git a/app/src/main/res/layout/fragment_google_authenticator.xml b/app/src/main/res/layout/fragment_google_authenticator.xml index eca69e0bb..00affa7c9 100644 --- a/app/src/main/res/layout/fragment_google_authenticator.xml +++ b/app/src/main/res/layout/fragment_google_authenticator.xml @@ -1,23 +1,33 @@ - + xmlns:app="http://schemas.android.com/apk/res-auto"> - + + + + - + - + + + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b2302a1c3..4208fe05b 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -13,7 +13,7 @@ e.foundation.webdav.eelo foundation.e.accountmanager.address_book DAVx⁵ Address book - at.bitfire.davdroid.addressbooks + foundation.e.accountmanager.addressbooks Address books This field is required Help @@ -414,7 +414,7 @@ Owner: - at.bitfire.davdroid.debug + foundation.e.accountmanager.debug Debug info ZIP archive Contains debug info and logs @@ -451,7 +451,7 @@ Almost no free space left - at.bitfire.davdroid.webdav + foundation.e.accountmanager.webdav WebDAV mounts Quota used: %1$s / available: %2$s Share content -- GitLab From 8c037d647dada0e0550788f1d1c0f3817f54cfa3 Mon Sep 17 00:00:00 2001 From: Nihar Thakkar Date: Mon, 11 Jun 2018 12:28:32 +0530 Subject: [PATCH 012/285] Added new columns to the Service table, fixed some authentication issues --- .../java/at/bitfire/davdroid/DavService.kt | 202 +++++++++++------- .../at/bitfire/davdroid/db/AppDatabase.kt | 4 +- .../java/at/bitfire/davdroid/db/Service.kt | 4 + .../davdroid/settings/AccountSettings.kt | 4 + .../ui/setup/AccountDetailsFragment.kt | 8 +- 5 files changed, 142 insertions(+), 80 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/DavService.kt b/app/src/main/java/at/bitfire/davdroid/DavService.kt index eefa5c484..5e3e66974 100644 --- a/app/src/main/java/at/bitfire/davdroid/DavService.kt +++ b/app/src/main/java/at/bitfire/davdroid/DavService.kt @@ -305,89 +305,141 @@ class DavService: IntentService("DavService") { .build().use { client -> val httpClient = client.okHttpClient - // refresh home set list (from principal) - service.principal?.let { principalUrl -> - Logger.log.fine("Querying principal $principalUrl for home sets") - queryHomeSets(httpClient, principalUrl) - } + // refresh home set list (from principal) + service.accessToken?.let { accessToken -> + service.principal?.let { principalUrl -> + Logger.log.fine("Querying principal $principalUrl for home sets") + queryHomeSets(httpClient, principalUrl) + } - // now refresh homesets and their member collections - val itHomeSets = homeSets.iterator() - while (itHomeSets.hasNext()) { - val (homeSetUrl, homeSet) = itHomeSets.next() - Logger.log.fine("Listing home set $homeSetUrl") + // now refresh homesets and their member collections + val itHomeSets = homeSets.iterator() + while (itHomeSets.hasNext()) { + val homeSet = itHomeSets.next() + Logger.log.fine("Listing home set ${homeSet.key}") + + try { + DavResource(httpClient, homeSet.key, accessToken).propfind( + 1, + *DAV_COLLECTION_PROPERTIES + ) { response, relation -> + if (!response.isSuccess()) + return@propfind + + if (relation == Response.HrefRelation.SELF) { + // this response is about the homeset itself + homeSet.value.displayName = + response[DisplayName::class.java]?.displayName + homeSet.value.privBind = + response[CurrentUserPrivilegeSet::class.java]?.mayBind + ?: true + } - try { - DavResource(httpClient, homeSetUrl).propfind(1, *DAV_COLLECTION_PROPERTIES) { response, relation -> - if (!response.isSuccess()) - return@propfind - - if (relation == Response.HrefRelation.SELF) { - // this response is about the homeset itself - homeSet.displayName = response[DisplayName::class.java]?.displayName - homeSet.privBind = response[CurrentUserPrivilegeSet::class.java]?.mayBind ?: true + // in any case, check whether the response is about a useable collection + val info = + Collection.fromDavResponse(response) ?: return@propfind + info.serviceId = serviceId + info.confirmed = true + Logger.log.log(Level.FINE, "Found collection", info) + + // remember usable collections + if ((service.type == Service.TYPE_CARDDAV && info.type == Collection.TYPE_ADDRESSBOOK) || + (service.type == Service.TYPE_CALDAV && arrayOf( + Collection.TYPE_CALENDAR, + Collection.TYPE_WEBCAL + ).contains(info.type)) + ) + collections[response.href] = info + } + } catch (e: HttpException) { + if (e.code in arrayOf(403, 404, 410)) + // delete home set only if it was not accessible (40x) + itHomeSets.remove() } + } - // in any case, check whether the response is about a useable collection - val info = Collection.fromDavResponse(response) ?: return@propfind - info.serviceId = serviceId - info.refHomeSet = homeSet - info.confirmed = true - - // whether new collections are selected for synchronization by default (controlled by managed setting) - info.sync = syncAllCollections - - info.owner = response[Owner::class.java]?.href?.let { response.href.resolve(it) } - Logger.log.log(Level.FINE, "Found collection", info) - - // remember usable collections - if ((service.type == Service.TYPE_CARDDAV && info.type == Collection.TYPE_ADDRESSBOOK) || - (service.type == Service.TYPE_CALDAV && arrayOf(Collection.TYPE_CALENDAR, Collection.TYPE_WEBCAL).contains(info.type))) - collections[response.href] = info + // check/refresh unconfirmed collections + val collectionsIter = collections.entries.iterator() + while (collectionsIter.hasNext()) { + val currentCollection = collectionsIter.next() + val (url, info) = currentCollection + if (!info.confirmed) + try { + // this collection doesn't belong to a homeset anymore, otherwise it would have been confirmed + info.homeSetId = null + + DavResource(httpClient, url).propfind( + 0, + *DAV_COLLECTION_PROPERTIES + ) { response, _ -> + if (!response.isSuccess()) + return@propfind + + val collection = + Collection.fromDavResponse(response) ?: return@propfind + collection.serviceId = + info.serviceId // use same service ID as previous entry + collection.confirmed = true + + // remove unusable collections + if ((service.type == Service.TYPE_CARDDAV && collection.type != Collection.TYPE_ADDRESSBOOK) || + (service.type == Service.TYPE_CALDAV && !arrayOf( + Collection.TYPE_CALENDAR, + Collection.TYPE_WEBCAL + ).contains(collection.type)) || + (collection.type == Collection.TYPE_WEBCAL && collection.source == null) + ) + collectionsIter.remove() + else + // update this collection in list + currentCollection.setValue(collection) + } + } catch (e: HttpException) { + if (e.code in arrayOf(403, 404, 410)) + // delete collection only if it was not accessible (40x) + collectionsIter.remove() + else + throw e + } } - } catch(e: HttpException) { - if (e.code in arrayOf(403, 404, 410)) - // delete home set only if it was not accessible (40x) - itHomeSets.remove() - } - } - // check/refresh unconfirmed collections - val collectionsIter = collections.entries.iterator() - while (collectionsIter.hasNext()) { - val currentCollection = collectionsIter.next() - val (url, info) = currentCollection - if (!info.confirmed) - try { - // this collection doesn't belong to a homeset anymore, otherwise it would have been confirmed - info.homeSetId = null - - DavResource(httpClient, url).propfind(0, *DAV_COLLECTION_PROPERTIES) { response, _ -> - if (!response.isSuccess()) - return@propfind - - val collection = Collection.fromDavResponse(response) ?: return@propfind - collection.serviceId = info.serviceId // use same service ID as previous entry - collection.confirmed = true - - // remove unusable collections - if ((service.type == Service.TYPE_CARDDAV && collection.type != Collection.TYPE_ADDRESSBOOK) || - (service.type == Service.TYPE_CALDAV && !arrayOf(Collection.TYPE_CALENDAR, Collection.TYPE_WEBCAL).contains(collection.type)) || - (collection.type == Collection.TYPE_WEBCAL && collection.source == null)) - collectionsIter.remove() - else - // update this collection in list - currentCollection.setValue(collection) - } - } catch(e: HttpException) { - if (e.code in arrayOf(403, 404, 410)) - // delete collection only if it was not accessible (40x) - collectionsIter.remove() - else - throw e + // check/refresh unconfirmed collections + val itCollections = collections.entries.iterator() + while (itCollections.hasNext()) { + val (url, info) = itCollections.next() + if (!info.confirmed) + try { + DavResource(httpClient, url, accessToken).propfind( + 0, + *DAV_COLLECTION_PROPERTIES + ) { response, _ -> + if (!response.isSuccess()) + return@propfind + + val collection = + Collection.fromDavResponse(response) ?: return@propfind + collection.confirmed = true + + // remove unusable collections + if ((service.type == Service.TYPE_CARDDAV && collection.type != Collection.TYPE_ADDRESSBOOK) || + (service.type == Service.TYPE_CALDAV && !arrayOf( + Collection.TYPE_CALENDAR, + Collection.TYPE_WEBCAL + ).contains(collection.type)) || + (collection.type == Collection.TYPE_WEBCAL && collection.source == null) + ) + itCollections.remove() + } + } catch (e: HttpException) { + if (e.code in arrayOf(403, 404, 410)) + // delete collection only if it was not accessible (40x) + itCollections.remove() + else + throw e + } } + } } - } db.runInTransaction { saveHomesets() diff --git a/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt b/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt index fc56d4c69..b23e43d3f 100644 --- a/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt +++ b/app/src/main/java/at/bitfire/davdroid/db/AppDatabase.kt @@ -116,11 +116,13 @@ abstract class AppDatabase: RoomDatabase() { "CREATE TABLE service(" + "id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT," + "accountName TEXT NOT NULL," + + "accessToken TEXT NOT NULL," + + "refreshToken TEXT NOT NULL," + "type TEXT NOT NULL," + "principal TEXT DEFAULT NULL" + ")", "CREATE UNIQUE INDEX index_service_accountName_type ON service(accountName, type)", - "INSERT INTO service(id, accountName, type, principal) SELECT _id, accountName, service, principal FROM services", + "INSERT INTO service(id, accountName, accessToken, refreshToken, type, principal) SELECT _id, accountName, accessToken, refreshToken, service, principal FROM services", "DROP TABLE services", // migrate "homesets" to "homeset": rename columns, make id NOT NULL diff --git a/app/src/main/java/at/bitfire/davdroid/db/Service.kt b/app/src/main/java/at/bitfire/davdroid/db/Service.kt index 0287f6c21..8a4a823ff 100644 --- a/app/src/main/java/at/bitfire/davdroid/db/Service.kt +++ b/app/src/main/java/at/bitfire/davdroid/db/Service.kt @@ -19,6 +19,10 @@ data class Service( override var id: Long, var accountName: String, + + var accessToken: String?, + var refreshToken: String?, + var type: String, var principal: HttpUrl? diff --git a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt index 13d3ee569..f030ab022 100644 --- a/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt +++ b/app/src/main/java/at/bitfire/davdroid/settings/AccountSettings.kt @@ -145,6 +145,10 @@ class AccountSettings( bundle.putString(KEY_USERNAME, credentials.userName) if (credentials.certificateAlias != null) bundle.putString(KEY_CERTIFICATE_ALIAS, credentials.certificateAlias) + if (credentials.accessToken != null) + bundle.putString(KEY_ACCESS_TOKEN, credentials.accessToken) + if (credentials.refreshToken != null) + bundle.putString(KEY_REFRESH_TOKEN, credentials.refreshToken) } return bundle diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt index 074aabd3d..279934af5 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/AccountDetailsFragment.kt @@ -180,7 +180,7 @@ class AccountDetailsFragment : Fragment() { val addrBookAuthority = context.getString(R.string.address_books_authority) if (config.cardDAV != null) { // insert CardDAV service - val id = insertService(name, Service.TYPE_CARDDAV, config.cardDAV) + val id = insertService(name, credentials?.accessToken, credentials?.refreshToken, Service.TYPE_CARDDAV, config.cardDAV) // initial CardDAV account settings accountSettings.setGroupMethod(groupMethod) @@ -197,7 +197,7 @@ class AccountDetailsFragment : Fragment() { if (config.calDAV != null) { // insert CalDAV service - val id = insertService(name, Service.TYPE_CALDAV, config.calDAV) + val id = insertService(name, credentials?.accessToken, credentials?.refreshToken, Service.TYPE_CALDAV, config.calDAV) // start CalDAV service detection (refresh collections) refreshIntent.putExtra(DavService.EXTRA_DAV_SERVICE_ID, id) @@ -226,9 +226,9 @@ class AccountDetailsFragment : Fragment() { return result } - private fun insertService(accountName: String, type: String, info: DavResourceFinder.Configuration.ServiceInfo): Long { + private fun insertService(accountName: String, accessToken: String?, refreshToken: String?, type: String, info: DavResourceFinder.Configuration.ServiceInfo): Long { // insert service - val service = Service(0, accountName, type, info.principal) + val service = Service(0, accountName, accessToken, refreshToken, type, info.principal) val serviceId = db.serviceDao().insertOrReplace(service) // insert home sets -- GitLab From e10fd0030c8145b942e1ac86addb9ff88fbaa066 Mon Sep 17 00:00:00 2001 From: Fahim Salam Chowdhury Date: Mon, 22 Aug 2022 17:53:14 +0600 Subject: [PATCH 013/285] Implemented OAuth token support. Refreshing of tokens not working yet. --- .../davdroid/syncadapter/ContactsSyncManager.kt | 16 ++++++++++++---- .../ui/setup/GoogleAuthenticatorFragment.kt | 5 +---- .../res/layout/fragment_google_authenticator.xml | 9 --------- 3 files changed, 13 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt index d9d901bdb..0fe983edb 100644 --- a/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt +++ b/app/src/main/java/at/bitfire/davdroid/syncadapter/ContactsSyncManager.kt @@ -95,6 +95,7 @@ class ContactsSyncManager( } private val readOnly = localAddressBook.readOnly + private val accessToken: String? = accountSettings.credentials().accessToken private var hasVCard4 = false private var hasJCard = false @@ -377,10 +378,17 @@ class ContactsSyncManager( .build() try { - val response = client.okHttpClient.newCall(Request.Builder() - .get() - .url(httpUrl) - .build()).execute() + val requestBuilder = Request.Builder() + .get() + .url(httpUrl) + + if (accessToken != null && accessToken.isNotEmpty()) { + requestBuilder.header("Authorization", "Bearer $accessToken") + } + + val response = client.okHttpClient.newCall(requestBuilder + .build()) + .execute() if (response.isSuccessful) return response.body?.bytes() diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt index db67858cb..e4aeb89be 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt @@ -184,10 +184,7 @@ class GoogleAuthenticatorFragment : Fragment(), AuthorizationService.TokenRespon override fun onTokenRequestCompleted(response: TokenResponse?, ex: AuthorizationException?) { authState?.update(response, ex) - progress_bar.visibility = View.GONE - auth_token_success_text_view.visibility = View.VISIBLE - - getAccountInfo() + getAccountInfo() } private fun getAccountInfo() { diff --git a/app/src/main/res/layout/fragment_google_authenticator.xml b/app/src/main/res/layout/fragment_google_authenticator.xml index 00affa7c9..1b3ed112c 100644 --- a/app/src/main/res/layout/fragment_google_authenticator.xml +++ b/app/src/main/res/layout/fragment_google_authenticator.xml @@ -19,15 +19,6 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_centerInParent="true" /> - - - -- GitLab From baeab8fce5a3d8be92fc9a1fda9a902509e5cd50 Mon Sep 17 00:00:00 2001 From: Nihar Thakkar Date: Tue, 12 Jun 2018 11:13:01 +0530 Subject: [PATCH 014/285] Don't show the login activity on the Android recent apps screen. --- app/src/main/AndroidManifest.xml | 1 + .../davdroid/ui/setup/GoogleAuthenticatorFragment.kt | 6 ++++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index b89beb982..19c8529de 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -104,6 +104,7 @@ android:name=".ui.setup.LoginActivity" android:label="@string/login_title" android:parentActivityName=".ui.AccountsActivity" + android:excludeFromRecents="true" android:exported="true"> diff --git a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt index e4aeb89be..e4246a053 100644 --- a/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt +++ b/app/src/main/java/at/bitfire/davdroid/ui/setup/GoogleAuthenticatorFragment.kt @@ -16,6 +16,7 @@ package at.bitfire.davdroid.ui.setup +import android.app.Activity import android.app.PendingIntent import android.content.Context import android.content.Intent @@ -136,8 +137,9 @@ class GoogleAuthenticatorFragment : Fragment(), AuthorizationService.TokenRespon idp.clientSecret), authorizationService?.createCustomTabsIntentBuilder()!! .build()) - - activity?.finish() + + requireActivity().setResult(Activity.RESULT_OK) + requireActivity().finish() } private fun createPostAuthorizationIntent( -- GitLab From 89c14b96578fba31a7ac8f8093b37f58ad1f6959 Mon Sep 17 00:00:00 2001 From: Nihar Thakkar Date: Tue, 12 Jun 2018 11:45:35 +0530 Subject: [PATCH 015/285] Changed app icon and renamed app. --- app/src/main/ic_launcher-web.png | Bin 23286 -> 19896 bytes .../res/mipmap-anydpi-v26/ic_launcher.xml | 4 ++-- .../mipmap-anydpi-v26/ic_launcher_round.xml | 6 ++++++ app/src/main/res/mipmap-hdpi/ic_launcher.png | Bin 3410 -> 1667 bytes .../mipmap-hdpi/ic_launcher_foreground.png | Bin 0 -> 1720 bytes .../res/mipmap-hdpi/ic_launcher_round.png | Bin 0 -> 3615 bytes app/src/main/res/mipmap-mdpi/ic_launcher.png | Bin 2239 -> 1440 bytes .../mipmap-mdpi/ic_launcher_foreground.png | Bin 0 -> 945 bytes .../res/mipmap-mdpi/ic_launcher_round.png | Bin 0 -> 2568 bytes app/src/main/res/mipmap-xhdpi/ic_launcher.png | Bin 4391 -> 2748 bytes .../mipmap-xhdpi/ic_launcher_foreground.png | Bin 0 -> 2442 bytes .../res/mipmap-xhdpi/ic_launcher_round.png | Bin 0 -> 5640 bytes .../main/res/mipmap-xxhdpi/ic_launcher.png | Bin 6521 -> 3760 bytes .../mipmap-xxhdpi/ic_launcher_foreground.png | Bin 0 -> 4070 bytes .../res/mipmap-xxhdpi/ic_launcher_round.png | Bin 0 -> 8379 bytes .../main/res/mipmap-xxxhdpi/ic_launcher.png | Bin 8597 -> 5950 bytes .../mipmap-xxxhdpi/ic_launcher_foreground.png | Bin 0 -> 5732 bytes .../res/mipmap-xxxhdpi/ic_launcher_round.png | Bin 0 -> 12741 bytes .../res/values/ic_launcher_background.xml | 5 +++++ app/src/main/res/values/strings.xml | 10 +++++----- .../main/res/xml/account_authenticator.xml | 4 ++-- .../account_authenticator_address_book.xml | 2 +- 22 files changed, 21 insertions(+), 10 deletions(-) create mode 100644 app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-hdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-mdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png create mode 100644 app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png create mode 100644 app/src/main/res/values/ic_launcher_background.xml diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png index 4cc81409080c7754124235d4d31c11ce7a8434f0..cafde5df9d1e89e1bdc0c9800cacbdabf7bd084a 100644 GIT binary patch literal 19896 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