1
0
mirror of https://github.com/etesync/android synced 2024-12-23 07:08:16 +00:00

Login dialog: add support for login into etebase accounts

This commit is contained in:
Tom Hacohen 2020-08-25 15:00:27 +03:00
parent 476f756307
commit 5da8edd54d
4 changed files with 197 additions and 9 deletions

View File

@ -247,10 +247,10 @@ constructor(internal val context: Context, internal val account: Account) {
val SYNC_INTERVAL_MANUALLY: Long = -1 val SYNC_INTERVAL_MANUALLY: Long = -1
// XXX: Workaround a bug in Android where passing a bundle to addAccountExplicitly doesn't work. // XXX: Workaround a bug in Android where passing a bundle to addAccountExplicitly doesn't work.
fun setUserData(accountManager: AccountManager, account: Account, uri: URI, userName: String) { fun setUserData(accountManager: AccountManager, account: Account, uri: URI?, userName: String) {
accountManager.setUserData(account, KEY_SETTINGS_VERSION, CURRENT_VERSION.toString()) accountManager.setUserData(account, KEY_SETTINGS_VERSION, CURRENT_VERSION.toString())
accountManager.setUserData(account, KEY_USERNAME, userName) accountManager.setUserData(account, KEY_USERNAME, userName)
accountManager.setUserData(account, KEY_URI, uri.toString()) accountManager.setUserData(account, KEY_URI, uri?.toString())
} }
} }
} }

View File

@ -8,6 +8,9 @@
package com.etesync.syncadapter.ui.setup package com.etesync.syncadapter.ui.setup
import android.content.Context import android.content.Context
import com.etebase.client.Account
import com.etebase.client.Client
import com.etebase.client.exceptions.EtebaseException
import com.etesync.syncadapter.HttpClient import com.etesync.syncadapter.HttpClient
import com.etesync.journalmanager.Crypto import com.etesync.journalmanager.Crypto
import com.etesync.journalmanager.Exceptions import com.etesync.journalmanager.Exceptions
@ -19,6 +22,7 @@ import com.etesync.syncadapter.model.CollectionInfo
import okhttp3.HttpUrl import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.Request
import java.io.IOException import java.io.IOException
import java.io.Serializable import java.io.Serializable
import java.net.URI import java.net.URI
@ -31,8 +35,26 @@ class BaseConfigurationFinder(protected val context: Context, protected val cred
httpClient = HttpClient.Builder(context).build().okHttpClient httpClient = HttpClient.Builder(context).build().okHttpClient
} }
private fun isServerEtebase(): Boolean {
if (credentials.uri != null) {
val remote = credentials.uri.toHttpUrlOrNull()!!.newBuilder()
.addPathSegments("api/v1/authentication/is_etebase/")
.build()
fun findInitialConfiguration(): Configuration { val request = Request.Builder()
.get()
.url(remote)
.build()
val response = httpClient.newCall(request).execute()
return response.isSuccessful
} else {
return !credentials.userName.contains("@")
}
}
fun findInitialConfigurationLegacy(): Configuration {
var exception: Throwable? = null var exception: Throwable? = null
val uri = credentials.uri ?: URI(Constants.serviceUrl.toString()) val uri = credentials.uri ?: URI(Constants.serviceUrl.toString())
@ -57,18 +79,63 @@ class BaseConfigurationFinder(protected val context: Context, protected val cred
return Configuration( return Configuration(
uri, uri,
credentials.userName, authtoken, credentials.userName,
null,
authtoken,
userInfo, userInfo,
exception exception
) )
} }
fun findInitialConfigurationEtebase(): Configuration {
var exception: Throwable? = null
val uri = credentials.uri
var etebaseSession: String? = null
try {
val client = Client.create(httpClient, uri.toString())
val etebase = Account.login(client, credentials.userName, credentials.password)
etebaseSession = etebase.save(null)
} catch (e: EtebaseException) {
exception = e
}
return Configuration(
uri,
credentials.userName,
etebaseSession,
null,
null,
exception
)
}
fun findInitialConfiguration(): Configuration {
try {
if (isServerEtebase()) {
return findInitialConfigurationEtebase()
} else {
return findInitialConfigurationLegacy()
}
} catch (e: Exception) {
return Configuration(
credentials.uri,
credentials.userName,
null,
null,
null,
e
)
}
}
// data classes // data classes
class Configuration class Configuration
// We have to use URI here because HttpUrl is not serializable! // We have to use URI here because HttpUrl is not serializable!
(val url: URI, val userName: String, val authtoken: String?, var userInfo: UserInfoManager.UserInfo?, var error: Throwable?) : Serializable { (val url: URI?, val userName: String, val etebaseSession: String?, val authtoken: String?, var userInfo: UserInfoManager.UserInfo?, var error: Throwable?) : Serializable {
var rawPassword: String? = null var rawPassword: String? = null
var password: String? = null var password: String? = null
var keyPair: Crypto.AsymmetricKeyPair? = null var keyPair: Crypto.AsymmetricKeyPair? = null
@ -76,6 +143,9 @@ class BaseConfigurationFinder(protected val context: Context, protected val cred
val isFailed: Boolean val isFailed: Boolean
get() = this.error != null get() = this.error != null
val isLegacy: Boolean
get() = this.authtoken != null
class ServiceInfo : Serializable { class ServiceInfo : Serializable {
val collections: Map<String, CollectionInfo> = HashMap() val collections: Map<String, CollectionInfo> = HashMap()

View File

@ -0,0 +1,111 @@
/*
* Copyright © 2013 2016 Ricki Hirner (bitfire web engineering).
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the GNU Public License v3.0
* which accompanies this distribution, and is available at
* http://www.gnu.org/licenses/gpl.html
*/
package com.etesync.syncadapter.ui.setup
import android.accounts.Account
import android.accounts.AccountManager
import android.app.Activity
import android.app.Dialog
import android.app.ProgressDialog
import android.content.Context
import android.os.AsyncTask
import android.os.Bundle
import android.provider.CalendarContract
import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment
import at.bitfire.ical4android.TaskProvider.Companion.OPENTASK_PROVIDERS
import com.etesync.journalmanager.Crypto
import com.etesync.journalmanager.Exceptions
import com.etesync.syncadapter.*
import com.etesync.syncadapter.log.Logger
import com.etesync.syncadapter.model.CollectionInfo
import com.etesync.syncadapter.model.JournalEntity
import com.etesync.syncadapter.model.ServiceEntity
import com.etesync.syncadapter.ui.setup.BaseConfigurationFinder.Configuration
import com.etesync.syncadapter.utils.AndroidCompat
import com.etesync.syncadapter.utils.TaskProviderHandling
import java.util.logging.Level
class CreateAccountFragment : DialogFragment() {
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val progress = ProgressDialog(activity)
progress.setTitle(R.string.login_encryption_setup_title)
progress.setMessage(getString(R.string.login_encryption_setup))
progress.isIndeterminate = true
progress.setCanceledOnTouchOutside(false)
isCancelable = false
return progress
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val config = requireArguments().getSerializable(KEY_CONFIG) as Configuration
val activity = requireActivity()
if (createAccount(config.userName, config)) {
activity.setResult(Activity.RESULT_OK)
activity.finish()
}
}
@Throws(InvalidAccountException::class)
protected fun createAccount(accountName: String, config: Configuration): Boolean {
val account = Account(accountName, App.accountType)
// create Android account
Logger.log.log(Level.INFO, "Creating Android account with initial config", arrayOf(account, config.userName, config.url))
val accountManager = AccountManager.get(context)
if (!accountManager.addAccountExplicitly(account, config.password, null))
return false
AccountSettings.setUserData(accountManager, account, config.url, config.userName)
// add entries for account to service DB
Logger.log.log(Level.INFO, "Writing account configuration to database", config)
try {
val settings = AccountSettings(requireContext(), account)
settings.etebaseSession = config.etebaseSession
// contact sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_contacts.xml
settings.setSyncInterval(App.addressBooksAuthority, Constants.DEFAULT_SYNC_INTERVAL.toLong())
// calendar sync is automatically enabled by isAlwaysSyncable="true" in res/xml/sync_contacts.xml
settings.setSyncInterval(CalendarContract.AUTHORITY, Constants.DEFAULT_SYNC_INTERVAL.toLong())
OPENTASK_PROVIDERS.forEach {
// enable task sync if OpenTasks is installed
// further changes will be handled by PackageChangedReceiver
TaskProviderHandling.updateTaskSync(requireContext(), it)
}
} catch (e: InvalidAccountException) {
Logger.log.log(Level.SEVERE, "Couldn't access account settings", e)
AndroidCompat.removeAccount(accountManager, account)
throw e
}
return true
}
companion object {
private val KEY_CONFIG = "config"
fun newInstance(config: Configuration): CreateAccountFragment {
val frag = CreateAccountFragment()
val args = Bundle(1)
args.putSerializable(KEY_CONFIG, config)
frag.arguments = args
return frag
}
}
}

View File

@ -13,6 +13,7 @@ import android.app.ProgressDialog
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AlertDialog
import androidx.fragment.app.DialogFragment import androidx.fragment.app.DialogFragment
import androidx.loader.app.LoaderManager import androidx.loader.app.LoaderManager
@ -47,17 +48,23 @@ class DetectConfigurationFragment : DialogFragment(), LoaderManager.LoaderCallba
override fun onLoadFinished(loader: Loader<Configuration>, data: Configuration?) { override fun onLoadFinished(loader: Loader<Configuration>, data: Configuration?) {
if (data != null) { if (data != null) {
if (data.isFailed) if (data.isFailed) {
// no service found: show error message // no service found: show error message
requireFragmentManager().beginTransaction() requireFragmentManager().beginTransaction()
.add(NothingDetectedFragment.newInstance(data.error!!.localizedMessage), null) .add(NothingDetectedFragment.newInstance(data.error!!.localizedMessage), null)
.commitAllowingStateLoss() .commitAllowingStateLoss()
else } else if (data.isLegacy) {
// service found: continue // legacy service found: continue
requireFragmentManager().beginTransaction() requireFragmentManager().beginTransaction()
.replace(android.R.id.content, EncryptionDetailsFragment.newInstance(data)) .replace(android.R.id.content, EncryptionDetailsFragment.newInstance(data))
.addToBackStack(null) .addToBackStack(null)
.commitAllowingStateLoss() .commitAllowingStateLoss()
} else {
requireFragmentManager().beginTransaction()
.replace(android.R.id.content, CreateAccountFragment.newInstance(data))
.addToBackStack(null)
.commitAllowingStateLoss()
}
} else } else
Logger.log.severe("Configuration detection failed") Logger.log.severe("Configuration detection failed")