Preparations for F-Droid main repo
This commit is contained in:
parent
33dfaed9d7
commit
7ad0178e8b
@ -53,19 +53,27 @@ android {
|
||||
fdroid {
|
||||
dimension "version"
|
||||
}
|
||||
|
||||
fdroidmain {
|
||||
dimension "version"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
playstore {
|
||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
|
||||
java.srcDirs = ['src/main/java/', 'src/apiKey/java/', 'src/apkStorePlaystore/java/']
|
||||
}
|
||||
|
||||
apkstore {
|
||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
|
||||
java.srcDirs = ['src/main/java/', 'src/apiKey/java/', 'src/apkStorePlaystore/java/']
|
||||
}
|
||||
|
||||
fdroid {
|
||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/foss/java', 'src/foss/java/']
|
||||
java.srcDirs = ['src/main/java/', 'src/apiKey/java/', 'src/fdroid/java/']
|
||||
}
|
||||
|
||||
fdroidmain {
|
||||
java.srcDirs = ['src/main/java/', 'src/fdroidmain/java/']
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
@ -95,7 +103,9 @@ dependencies {
|
||||
|
||||
implementation project(':domain')
|
||||
implementation project(':util')
|
||||
implementation project(':pcloud-sdk-java')
|
||||
playstoreImplementation project(':pcloud-sdk-java')
|
||||
apkstoreImplementation project(':pcloud-sdk-java')
|
||||
fdroidImplementation project(':pcloud-sdk-java')
|
||||
|
||||
coreLibraryDesugaring dependencies.coreDesugaring
|
||||
|
||||
@ -113,9 +123,16 @@ dependencies {
|
||||
implementation dependencies.jsonWebTokenJson
|
||||
|
||||
// cloud
|
||||
implementation dependencies.dropbox
|
||||
implementation dependencies.msgraphAuth
|
||||
implementation dependencies.msgraph
|
||||
playstoreImplementation dependencies.dropbox
|
||||
apkstoreImplementation dependencies.dropbox
|
||||
fdroidImplementation dependencies.dropbox
|
||||
|
||||
playstoreImplementation dependencies.msgraphAuth
|
||||
apkstoreImplementation dependencies.msgraphAuth
|
||||
fdroidImplementation dependencies.msgraphAuth
|
||||
playstoreImplementation dependencies.msgraph
|
||||
apkstoreImplementation dependencies.msgraph
|
||||
fdroidImplementation dependencies.msgraph
|
||||
|
||||
implementation dependencies.stax
|
||||
api dependencies.minIo
|
||||
|
@ -1,5 +1,7 @@
|
||||
package org.cryptomator.data.cloud;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import org.cryptomator.data.cloud.crypto.CryptoCloudContentRepositoryFactory;
|
||||
import org.cryptomator.data.cloud.dropbox.DropboxCloudContentRepositoryFactory;
|
||||
import org.cryptomator.data.cloud.googledrive.GoogleDriveCloudContentRepositoryFactory;
|
||||
@ -16,8 +18,6 @@ import java.util.Iterator;
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
@Singleton
|
||||
public class CloudContentRepositoryFactories implements Iterable<CloudContentRepositoryFactory> {
|
||||
|
@ -0,0 +1,40 @@
|
||||
package org.cryptomator.data.cloud;
|
||||
|
||||
import static java.util.Arrays.asList;
|
||||
|
||||
import org.cryptomator.data.cloud.crypto.CryptoCloudContentRepositoryFactory;
|
||||
import org.cryptomator.data.cloud.local.LocalStorageContentRepositoryFactory;
|
||||
import org.cryptomator.data.cloud.s3.S3CloudContentRepositoryFactory;
|
||||
import org.cryptomator.data.cloud.webdav.WebDavCloudContentRepositoryFactory;
|
||||
import org.cryptomator.data.repository.CloudContentRepositoryFactory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.Iterator;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.inject.Singleton;
|
||||
|
||||
@Singleton
|
||||
public class CloudContentRepositoryFactories implements Iterable<CloudContentRepositoryFactory> {
|
||||
|
||||
private final Iterable<CloudContentRepositoryFactory> factories;
|
||||
|
||||
@Inject
|
||||
public CloudContentRepositoryFactories(
|
||||
S3CloudContentRepositoryFactory s3Factory, //
|
||||
CryptoCloudContentRepositoryFactory cryptoFactory, //
|
||||
LocalStorageContentRepositoryFactory localStorageFactory, //
|
||||
WebDavCloudContentRepositoryFactory webDavFactory) {
|
||||
|
||||
factories = asList(s3Factory, //
|
||||
cryptoFactory, //
|
||||
localStorageFactory, //
|
||||
webDavFactory);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Iterator<CloudContentRepositoryFactory> iterator() {
|
||||
return factories.iterator();
|
||||
}
|
||||
}
|
@ -88,19 +88,27 @@ android {
|
||||
fdroid {
|
||||
dimension "version"
|
||||
}
|
||||
|
||||
fdroidmain {
|
||||
dimension "version"
|
||||
}
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
playstore {
|
||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
|
||||
java.srcDirs = ['src/main/java', 'src/apiKey/java/', 'src/apkStorePlaystore/java/']
|
||||
}
|
||||
|
||||
apkstore {
|
||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/notFoss/java', 'src/notFoss/java/']
|
||||
java.srcDirs = ['src/main/java/', 'src/apiKey/java/', 'src/apkStorePlaystore/java/']
|
||||
}
|
||||
|
||||
fdroid {
|
||||
java.srcDirs = ['src/main/java', 'src/main/java/', 'src/foss/java', 'src/foss/java/']
|
||||
java.srcDirs = ['src/main/java/', 'src/apiKey/java/', 'src/fdroid/java/', 'src/fdroidAndfdroidmain/java/']
|
||||
}
|
||||
|
||||
fdroidmain {
|
||||
java.srcDirs = ['src/main/java/', 'src/fdroidmain/java/', 'src/fdroidAndfdroidmain/java/']
|
||||
}
|
||||
}
|
||||
packagingOptions {
|
||||
@ -147,9 +155,16 @@ dependencies {
|
||||
implementation dependencies.androidxBiometric
|
||||
|
||||
// cloud
|
||||
implementation dependencies.dropbox
|
||||
implementation dependencies.msgraph
|
||||
implementation dependencies.msgraphAuth
|
||||
playstoreImplementation dependencies.dropbox
|
||||
apkstoreImplementation dependencies.dropbox
|
||||
fdroidImplementation dependencies.dropbox
|
||||
|
||||
playstoreImplementation dependencies.msgraphAuth
|
||||
apkstoreImplementation dependencies.msgraphAuth
|
||||
fdroidImplementation dependencies.msgraphAuth
|
||||
playstoreImplementation dependencies.msgraph
|
||||
apkstoreImplementation dependencies.msgraph
|
||||
fdroidImplementation dependencies.msgraph
|
||||
|
||||
playstoreImplementation(dependencies.googleApiServicesDrive) {
|
||||
exclude module: 'guava-jdk5'
|
||||
|
@ -0,0 +1,17 @@
|
||||
package org.cryptomator.presentation.presenter
|
||||
|
||||
import android.content.Context
|
||||
import com.dropbox.core.android.Auth
|
||||
import org.cryptomator.presentation.BuildConfig
|
||||
|
||||
object DropboxAuthHelper {
|
||||
|
||||
fun startOAuth2Authentication(context: Context) {
|
||||
Auth.startOAuth2Authentication(context, BuildConfig.DROPBOX_API_KEY)
|
||||
}
|
||||
|
||||
fun getOAuth2Token(): String? {
|
||||
return Auth.getOAuth2Token()
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
package org.cryptomator.presentation.presenter
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Context
|
||||
import com.microsoft.identity.client.AuthenticationCallback
|
||||
import com.microsoft.identity.client.IAccount
|
||||
import com.microsoft.identity.client.IAuthenticationResult
|
||||
import com.microsoft.identity.client.IMultipleAccountPublicClientApplication
|
||||
import com.microsoft.identity.client.IPublicClientApplication
|
||||
import com.microsoft.identity.client.PublicClientApplication
|
||||
import com.microsoft.identity.client.exception.MsalException
|
||||
import com.microsoft.identity.client.exception.MsalUiRequiredException
|
||||
import org.cryptomator.domain.OnedriveCloud
|
||||
import org.cryptomator.domain.exception.FatalBackendException
|
||||
import org.cryptomator.presentation.R
|
||||
import org.cryptomator.util.crypto.CredentialCryptor
|
||||
import timber.log.Timber
|
||||
|
||||
object OnedriveAuthentication {
|
||||
|
||||
fun refreshOrCheckAuth(activity: Activity, cloud: OnedriveCloud, success: (cloud: OnedriveCloud) -> Unit, failed: (e: FatalBackendException) -> Unit) {
|
||||
PublicClientApplication.createMultipleAccountPublicClientApplication(
|
||||
activity.applicationContext,
|
||||
R.raw.auth_config_onedrive,
|
||||
object : IPublicClientApplication.IMultipleAccountApplicationCreatedListener {
|
||||
override fun onCreated(application: IMultipleAccountPublicClientApplication) {
|
||||
application.getAccounts(object : IPublicClientApplication.LoadAccountsCallback {
|
||||
override fun onTaskCompleted(accounts: List<IAccount>) {
|
||||
if (accounts.isEmpty()) {
|
||||
application.acquireToken(activity, AuthenticateCloudPresenter.onedriveScopes(), getAuthInteractiveCallback(activity.applicationContext, cloud, success, failed))
|
||||
} else {
|
||||
accounts.find { account -> account.username == cloud.username() }?.let {
|
||||
application.acquireTokenSilentAsync(
|
||||
AuthenticateCloudPresenter.onedriveScopes(),
|
||||
it,
|
||||
"https://login.microsoftonline.com/common",
|
||||
getAuthSilentCallback(activity, cloud, success, failed, application)
|
||||
)
|
||||
} ?: application.acquireToken(activity, AuthenticateCloudPresenter.onedriveScopes(), getAuthInteractiveCallback(activity.applicationContext, cloud, success, failed))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Error to get accounts")
|
||||
failed(FatalBackendException(e))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").i(e, "Error in configuration")
|
||||
failed(FatalBackendException(e))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun getAuthSilentCallback(
|
||||
activity: Activity,
|
||||
cloud: OnedriveCloud,
|
||||
success: (cloud: OnedriveCloud) -> Unit,
|
||||
failed: (e: FatalBackendException) -> Unit,
|
||||
application: IMultipleAccountPublicClientApplication
|
||||
): AuthenticationCallback {
|
||||
return object : AuthenticationCallback {
|
||||
|
||||
override fun onSuccess(authenticationResult: IAuthenticationResult) {
|
||||
onTokenObtained(activity.applicationContext, cloud, authenticationResult, success)
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Failed to acquireToken")
|
||||
when (e) {
|
||||
is MsalUiRequiredException -> {
|
||||
/* Tokens expired or no session, retry with interactive */
|
||||
application.acquireToken(activity, AuthenticateCloudPresenter.onedriveScopes(), getAuthInteractiveCallback(activity.applicationContext, cloud, success, failed))
|
||||
}
|
||||
else -> failed(FatalBackendException(e))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
Timber.tag("AuthenticateCloudPresenter").i("User cancelled login")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun onTokenObtained(context: Context, cloud: OnedriveCloud?, authenticationResult: IAuthenticationResult, success: (cloud: OnedriveCloud) -> Unit) {
|
||||
Timber.tag("AuthenticateCloudPresenter").i("Successfully authenticated")
|
||||
val accessToken = CredentialCryptor.getInstance(context).encrypt(authenticationResult.accessToken)
|
||||
val cloudBuilder = cloud?.let { OnedriveCloud.aCopyOf(it) } ?: OnedriveCloud.aOnedriveCloud()
|
||||
val onedriveSkeleton = cloudBuilder.withAccessToken(accessToken).withUsername(authenticationResult.account.username).build()
|
||||
success(onedriveSkeleton)
|
||||
}
|
||||
|
||||
fun getAuthenticatedOnedriveCloud(activity: Activity, success: (cloud: OnedriveCloud) -> Unit, failed: (e: FatalBackendException) -> Unit) {
|
||||
PublicClientApplication.createMultipleAccountPublicClientApplication(
|
||||
activity.applicationContext,
|
||||
R.raw.auth_config_onedrive,
|
||||
object : IPublicClientApplication.IMultipleAccountApplicationCreatedListener {
|
||||
override fun onCreated(application: IMultipleAccountPublicClientApplication) {
|
||||
application.getAccounts(object : IPublicClientApplication.LoadAccountsCallback {
|
||||
override fun onTaskCompleted(accounts: List<IAccount>) {
|
||||
application.acquireToken(activity, AuthenticateCloudPresenter.onedriveScopes(), getAuthInteractiveCallback(activity.applicationContext, null, success, failed))
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Error to get accounts")
|
||||
failed(FatalBackendException(e))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").i(e, "Error in configuration")
|
||||
failed(FatalBackendException(e))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun getAuthInteractiveCallback(context: Context, cloud: OnedriveCloud?, success: (cloud: OnedriveCloud) -> Unit, failed: (e: FatalBackendException) -> Unit): AuthenticationCallback {
|
||||
return object : AuthenticationCallback {
|
||||
|
||||
override fun onSuccess(authenticationResult: IAuthenticationResult) {
|
||||
onTokenObtained(context, cloud, authenticationResult, success)
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Successfully authenticated")
|
||||
failed(FatalBackendException(e))
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
Timber.tag("AuthenticateCloudPresenter").i("User cancelled login")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,13 @@
|
||||
package org.cryptomator.presentation.presenter
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
|
||||
import com.google.api.services.drive.DriveScopes
|
||||
|
||||
object GoogleAuthHelper {
|
||||
|
||||
fun getChooseAccountIntent(context: Context): Intent? {
|
||||
return GoogleAccountCredential.usingOAuth2(context, setOf(DriveScopes.DRIVE)).newChooseAccountIntent()
|
||||
}
|
||||
}
|
@ -0,0 +1,11 @@
|
||||
package org.cryptomator.presentation.presenter
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
|
||||
object GoogleAuthHelper {
|
||||
|
||||
fun getChooseAccountIntent(context: Context): Intent? {
|
||||
return null
|
||||
}
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
package org.cryptomator.presentation.presenter
|
||||
|
||||
import android.content.Context
|
||||
|
||||
object DropboxAuthHelper {
|
||||
|
||||
fun startOAuth2Authentication(context: Context) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
fun getOAuth2Token(): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package org.cryptomator.presentation.presenter
|
||||
|
||||
import android.app.Activity
|
||||
import org.cryptomator.domain.OnedriveCloud
|
||||
import org.cryptomator.domain.exception.FatalBackendException
|
||||
|
||||
object OnedriveAuthentication {
|
||||
|
||||
fun getAuthenticatedOnedriveCloud(activity: Activity, success: (cloud: OnedriveCloud) -> Unit, failed: (e: FatalBackendException) -> Unit) {
|
||||
// no-op
|
||||
}
|
||||
|
||||
fun refreshOrCheckAuth(activity: Activity, cloud: OnedriveCloud, success: (cloud: OnedriveCloud) -> Unit, failed: (e: FatalBackendException) -> Unit) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,610 +0,0 @@
|
||||
package org.cryptomator.presentation.presenter
|
||||
|
||||
import android.accounts.AccountManager
|
||||
import android.content.Intent
|
||||
import android.content.Intent.ACTION_OPEN_DOCUMENT_TREE
|
||||
import android.provider.DocumentsContract
|
||||
import android.widget.Toast
|
||||
import com.dropbox.core.android.Auth
|
||||
import com.microsoft.identity.client.AuthenticationCallback
|
||||
import com.microsoft.identity.client.IAccount
|
||||
import com.microsoft.identity.client.IAuthenticationResult
|
||||
import com.microsoft.identity.client.IMultipleAccountPublicClientApplication
|
||||
import com.microsoft.identity.client.IPublicClientApplication
|
||||
import com.microsoft.identity.client.PublicClientApplication
|
||||
import com.microsoft.identity.client.exception.MsalClientException
|
||||
import com.microsoft.identity.client.exception.MsalException
|
||||
import com.microsoft.identity.client.exception.MsalServiceException
|
||||
import com.microsoft.identity.client.exception.MsalUiRequiredException
|
||||
import org.cryptomator.data.util.X509CertificateHelper
|
||||
import org.cryptomator.domain.Cloud
|
||||
import org.cryptomator.domain.CloudType
|
||||
import org.cryptomator.domain.DropboxCloud
|
||||
import org.cryptomator.domain.GoogleDriveCloud
|
||||
import org.cryptomator.domain.OnedriveCloud
|
||||
import org.cryptomator.domain.PCloud
|
||||
import org.cryptomator.domain.WebDavCloud
|
||||
import org.cryptomator.domain.di.PerView
|
||||
import org.cryptomator.domain.exception.FatalBackendException
|
||||
import org.cryptomator.domain.exception.NetworkConnectionException
|
||||
import org.cryptomator.domain.exception.authentication.AuthenticationException
|
||||
import org.cryptomator.domain.exception.authentication.WebDavCertificateUntrustedAuthenticationException
|
||||
import org.cryptomator.domain.exception.authentication.WebDavNotSupportedException
|
||||
import org.cryptomator.domain.exception.authentication.WebDavServerNotFoundException
|
||||
import org.cryptomator.domain.exception.authentication.WrongCredentialsException
|
||||
import org.cryptomator.domain.usecases.cloud.AddOrChangeCloudConnectionUseCase
|
||||
import org.cryptomator.domain.usecases.cloud.GetCloudsUseCase
|
||||
import org.cryptomator.domain.usecases.cloud.GetUsernameUseCase
|
||||
import org.cryptomator.generator.Callback
|
||||
import org.cryptomator.presentation.BuildConfig
|
||||
import org.cryptomator.presentation.R
|
||||
import org.cryptomator.presentation.exception.ExceptionHandlers
|
||||
import org.cryptomator.presentation.exception.PermissionNotGrantedException
|
||||
import org.cryptomator.presentation.intent.AuthenticateCloudIntent
|
||||
import org.cryptomator.presentation.intent.Intents
|
||||
import org.cryptomator.presentation.model.CloudModel
|
||||
import org.cryptomator.presentation.model.CloudTypeModel
|
||||
import org.cryptomator.presentation.model.LocalStorageModel
|
||||
import org.cryptomator.presentation.model.ProgressModel
|
||||
import org.cryptomator.presentation.model.ProgressStateModel
|
||||
import org.cryptomator.presentation.model.S3CloudModel
|
||||
import org.cryptomator.presentation.model.WebDavCloudModel
|
||||
import org.cryptomator.presentation.model.mappers.CloudModelMapper
|
||||
import org.cryptomator.presentation.ui.activity.view.AuthenticateCloudView
|
||||
import org.cryptomator.presentation.workflow.ActivityResult
|
||||
import org.cryptomator.presentation.workflow.AddExistingVaultWorkflow
|
||||
import org.cryptomator.presentation.workflow.CreateNewVaultWorkflow
|
||||
import org.cryptomator.presentation.workflow.Workflow
|
||||
import org.cryptomator.util.ExceptionUtil
|
||||
import org.cryptomator.util.crypto.CredentialCryptor
|
||||
import java.security.cert.CertificateEncodingException
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.inject.Inject
|
||||
import timber.log.Timber
|
||||
|
||||
@PerView
|
||||
class AuthenticateCloudPresenter @Inject constructor( //
|
||||
exceptionHandlers: ExceptionHandlers, //
|
||||
private val cloudModelMapper: CloudModelMapper, //
|
||||
private val addOrChangeCloudConnectionUseCase: AddOrChangeCloudConnectionUseCase, //
|
||||
private val getCloudsUseCase: GetCloudsUseCase, //
|
||||
private val getUsernameUseCase: GetUsernameUseCase, //
|
||||
private val addExistingVaultWorkflow: AddExistingVaultWorkflow, //
|
||||
private val createNewVaultWorkflow: CreateNewVaultWorkflow
|
||||
) : Presenter<AuthenticateCloudView>(exceptionHandlers) {
|
||||
|
||||
private val strategies = arrayOf( //
|
||||
DropboxAuthStrategy(), //
|
||||
OnedriveAuthStrategy(), //
|
||||
PCloudAuthStrategy(), //
|
||||
WebDAVAuthStrategy(), //
|
||||
S3AuthStrategy(), //
|
||||
LocalStorageAuthStrategy() //
|
||||
)
|
||||
|
||||
override fun workflows(): Iterable<Workflow<*>> {
|
||||
return listOf(createNewVaultWorkflow, addExistingVaultWorkflow)
|
||||
}
|
||||
|
||||
override fun resumed() {
|
||||
val cloud = view?.intent()?.cloud()
|
||||
val error = view?.intent()?.error()
|
||||
handleNetworkConnectionExceptionIfRequired(error)
|
||||
view?.intent()?.let { cloud?.let { cloud -> authStrategyFor(cloud).resumed(it) } }
|
||||
}
|
||||
|
||||
private fun handleNetworkConnectionExceptionIfRequired(error: AuthenticationException?) {
|
||||
if (error != null && ExceptionUtil.contains(error, NetworkConnectionException::class.java)) {
|
||||
view?.showMessage(R.string.error_no_network_connection)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private fun authStrategyFor(cloud: CloudModel): AuthStrategy {
|
||||
strategies.forEach { strategy ->
|
||||
if (strategy.supports(cloud)) {
|
||||
return strategy
|
||||
}
|
||||
}
|
||||
return FailingAuthStrategy()
|
||||
}
|
||||
|
||||
private fun getUsernameAndSuceedAuthentication(cloud: Cloud) {
|
||||
getUsernameUseCase.withCloud(cloud).run(object : DefaultResultHandler<String>() {
|
||||
override fun onSuccess(username: String) {
|
||||
succeedAuthenticationWith(updateUsernameOf(cloud, username))
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
super.onError(e)
|
||||
finish()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateUsernameOf(cloud: Cloud, username: String): Cloud {
|
||||
when (cloud.type()) {
|
||||
CloudType.DROPBOX -> return DropboxCloud.aCopyOf(cloud as DropboxCloud).withUsername(username).build()
|
||||
CloudType.ONEDRIVE -> return OnedriveCloud.aCopyOf(cloud as OnedriveCloud).withUsername(username).build()
|
||||
}
|
||||
throw IllegalStateException("Cloud " + cloud.type() + " is not supported")
|
||||
}
|
||||
|
||||
private fun succeedAuthenticationWith(cloud: Cloud) {
|
||||
addOrChangeCloudConnectionUseCase //
|
||||
.withCloud(cloud) //
|
||||
.run(object : DefaultResultHandler<Void?>() {
|
||||
override fun onSuccess(void: Void?) {
|
||||
finishWithResult(cloudModelMapper.toModel(cloud))
|
||||
}
|
||||
|
||||
override fun onError(e: Throwable) {
|
||||
super.onError(e)
|
||||
finish()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun failAuthentication(cloudName: Int) {
|
||||
activity().runOnUiThread {
|
||||
view?.showMessage(String.format(getString(R.string.screen_authenticate_auth_authentication_failed), getString(cloudName)))
|
||||
}
|
||||
finish()
|
||||
}
|
||||
|
||||
private fun failAuthentication(error: PermissionNotGrantedException) {
|
||||
finishWithResult(error)
|
||||
}
|
||||
|
||||
private inner class DropboxAuthStrategy : AuthStrategy {
|
||||
|
||||
private var authenticationStarted = false
|
||||
override fun supports(cloud: CloudModel): Boolean {
|
||||
return cloud.cloudType() == CloudTypeModel.DROPBOX
|
||||
}
|
||||
|
||||
override fun resumed(intent: AuthenticateCloudIntent) {
|
||||
if (authenticationStarted) {
|
||||
handleAuthenticationResult(intent.cloud())
|
||||
} else {
|
||||
startAuthentication()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAuthentication() {
|
||||
showProgress(ProgressModel(ProgressStateModel.AUTHENTICATION))
|
||||
authenticationStarted = true
|
||||
Auth.startOAuth2Authentication(context(), BuildConfig.DROPBOX_API_KEY)
|
||||
view?.skipTransition()
|
||||
}
|
||||
|
||||
private fun handleAuthenticationResult(cloudModel: CloudModel) {
|
||||
val authToken = Auth.getOAuth2Token()
|
||||
if (authToken == null) {
|
||||
failAuthentication(cloudModel.name())
|
||||
} else {
|
||||
getUsernameAndSuceedAuthentication( //
|
||||
DropboxCloud.aCopyOf(cloudModel.toCloud() as DropboxCloud) //
|
||||
.withAccessToken(encrypt(authToken)) //
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Callback(dispatchResultOkOnly = false)
|
||||
fun onUserRecoveryFinished(result: ActivityResult, cloud: CloudModel) {
|
||||
if (result.isResultOk) {
|
||||
succeedAuthenticationWith(cloud.toCloud())
|
||||
} else {
|
||||
failAuthentication(cloud.name())
|
||||
}
|
||||
}
|
||||
|
||||
@Callback(dispatchResultOkOnly = false)
|
||||
fun onGoogleDriveAuthenticated(result: ActivityResult, cloud: CloudModel) {
|
||||
if (result.isResultOk) {
|
||||
val accountName = result.intent()?.extras?.getString(AccountManager.KEY_ACCOUNT_NAME)
|
||||
succeedAuthenticationWith(
|
||||
GoogleDriveCloud.aCopyOf(cloud.toCloud() as GoogleDriveCloud) //
|
||||
.withUsername(accountName) //
|
||||
.withAccessToken(accountName) //
|
||||
.build()
|
||||
)
|
||||
} else {
|
||||
failAuthentication(cloud.name())
|
||||
}
|
||||
}
|
||||
|
||||
private inner class OnedriveAuthStrategy : AuthStrategy {
|
||||
|
||||
private var authenticationStarted = false
|
||||
override fun supports(cloud: CloudModel): Boolean {
|
||||
return cloud.cloudType() == CloudTypeModel.ONEDRIVE
|
||||
}
|
||||
|
||||
override fun resumed(intent: AuthenticateCloudIntent) {
|
||||
if (!authenticationStarted) {
|
||||
startAuthentication(intent.cloud())
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAuthentication(cloud: CloudModel) {
|
||||
authenticationStarted = true
|
||||
|
||||
Toast.makeText(context(), R.string.notification_authenticating, Toast.LENGTH_SHORT).show()
|
||||
|
||||
PublicClientApplication.createMultipleAccountPublicClientApplication(
|
||||
context(),
|
||||
R.raw.auth_config_onedrive,
|
||||
object : IPublicClientApplication.IMultipleAccountApplicationCreatedListener {
|
||||
override fun onCreated(application: IMultipleAccountPublicClientApplication) {
|
||||
application.getAccounts(object : IPublicClientApplication.LoadAccountsCallback {
|
||||
override fun onTaskCompleted(accounts: List<IAccount>) {
|
||||
if (accounts.isEmpty()) {
|
||||
application.acquireToken(activity(), onedriveScopes(), getAuthInteractiveCallback(cloud))
|
||||
} else {
|
||||
accounts.find { account -> account.username == cloud.username() }?.let {
|
||||
application.acquireTokenSilentAsync(
|
||||
onedriveScopes(),
|
||||
it,
|
||||
"https://login.microsoftonline.com/common",
|
||||
getAuthSilentCallback(cloud, application)
|
||||
)
|
||||
} ?: application.acquireToken(activity(), onedriveScopes(), getAuthInteractiveCallback(cloud))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Error to get accounts")
|
||||
failAuthentication(cloud.name())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").i(e, "Error in configuration")
|
||||
failAuthentication(cloud.name())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun getAuthSilentCallback(cloud: CloudModel, application: IMultipleAccountPublicClientApplication): AuthenticationCallback {
|
||||
return object : AuthenticationCallback {
|
||||
|
||||
override fun onSuccess(authenticationResult: IAuthenticationResult) {
|
||||
Timber.tag("AuthenticateCloudPresenter").i("Successfully authenticated")
|
||||
handleAuthenticationResult(cloud, authenticationResult.accessToken)
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Failed to acquireToken")
|
||||
when (e) {
|
||||
is MsalClientException -> {
|
||||
/* Exception inside MSAL, more info inside MsalError.java */
|
||||
failAuthentication(cloud.name())
|
||||
}
|
||||
is MsalServiceException -> {
|
||||
/* Exception when communicating with the STS, likely config issue */
|
||||
failAuthentication(cloud.name())
|
||||
}
|
||||
is MsalUiRequiredException -> {
|
||||
/* Tokens expired or no session, retry with interactive */
|
||||
application.acquireToken(activity(), onedriveScopes(), getAuthInteractiveCallback(cloud))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
Timber.tag("AuthenticateCloudPresenter").i("User cancelled login")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAuthInteractiveCallback(cloud: CloudModel): AuthenticationCallback {
|
||||
return object : AuthenticationCallback {
|
||||
|
||||
override fun onSuccess(authenticationResult: IAuthenticationResult) {
|
||||
Timber.tag("AuthenticateCloudPresenter").i("Successfully authenticated")
|
||||
handleAuthenticationResult(cloud, authenticationResult.accessToken, authenticationResult.account.username)
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Successfully authenticated")
|
||||
failAuthentication(cloud.name())
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
Timber.tag("AuthenticateCloudPresenter").i("User cancelled login")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAuthenticationResult(cloud: CloudModel, accessToken: String) {
|
||||
getUsernameAndSuceedAuthentication( //
|
||||
OnedriveCloud.aCopyOf(cloud.toCloud() as OnedriveCloud) //
|
||||
.withAccessToken(encrypt(accessToken)) //
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleAuthenticationResult(cloud: CloudModel, accessToken: String, username: String) {
|
||||
getUsernameAndSuceedAuthentication( //
|
||||
OnedriveCloud.aCopyOf(cloud.toCloud() as OnedriveCloud) //
|
||||
.withAccessToken(encrypt(accessToken)) //
|
||||
.withUsername(username)
|
||||
.build()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class PCloudAuthStrategy : AuthStrategy {
|
||||
|
||||
private var authenticationStarted = false
|
||||
|
||||
override fun supports(cloud: CloudModel): Boolean {
|
||||
return cloud.cloudType() == CloudTypeModel.PCLOUD
|
||||
}
|
||||
|
||||
override fun resumed(intent: AuthenticateCloudIntent) {
|
||||
if (authenticationStarted) {
|
||||
finish()
|
||||
} else {
|
||||
startAuthentication(intent.cloud())
|
||||
Toast.makeText(
|
||||
context(),
|
||||
String.format(getString(R.string.error_authentication_failed_re_authenticate), intent.cloud().username()),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAuthentication(cloud: CloudModel) {
|
||||
authenticationStarted = true
|
||||
showProgress(ProgressModel(ProgressStateModel.AUTHENTICATION))
|
||||
view?.skipTransition()
|
||||
requestActivityResult(
|
||||
ActivityResultCallbacks.pCloudReAuthenticationFinished(cloud), //
|
||||
Intents.cloudConnectionListIntent() //
|
||||
.withCloudType(CloudTypeModel.PCLOUD) //
|
||||
.withDialogTitle(context().getString(R.string.screen_update_pcloud_connections_title)) //
|
||||
.withFinishOnCloudItemClick(false) //
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Callback
|
||||
fun pCloudReAuthenticationFinished(activityResult: ActivityResult, cloud: CloudModel) {
|
||||
val code = activityResult.intent().extras?.getString(CloudConnectionListPresenter.PCLOUD_OAUTH_AUTH_CODE, "")
|
||||
val hostname = activityResult.intent().extras?.getString(CloudConnectionListPresenter.PCLOUD_HOSTNAME, "")
|
||||
|
||||
if (!code.isNullOrEmpty() && !hostname.isNullOrEmpty()) {
|
||||
Timber.tag("CloudConnectionListPresenter").i("PCloud OAuth code successfully retrieved")
|
||||
|
||||
val accessToken = CredentialCryptor //
|
||||
.getInstance(this.context()) //
|
||||
.encrypt(code)
|
||||
val pCloudSkeleton = PCloud.aPCloud() //
|
||||
.withAccessToken(accessToken)
|
||||
.withUrl(hostname)
|
||||
.build();
|
||||
getUsernameUseCase //
|
||||
.withCloud(pCloudSkeleton) //
|
||||
.run(object : DefaultResultHandler<String>() {
|
||||
override fun onSuccess(username: String) {
|
||||
Timber.tag("CloudConnectionListPresenter").i("PCloud Authentication successfully")
|
||||
prepareForSavingPCloud(PCloud.aCopyOf(pCloudSkeleton).withUsername(username).build())
|
||||
}
|
||||
})
|
||||
} else {
|
||||
Timber.tag("CloudConnectionListPresenter").i("PCloud Authentication not successful")
|
||||
failAuthentication(cloud.name())
|
||||
}
|
||||
}
|
||||
|
||||
fun prepareForSavingPCloud(cloud: PCloud) {
|
||||
getCloudsUseCase //
|
||||
.withCloudType(cloud.type()) //
|
||||
.run(object : DefaultResultHandler<List<Cloud>>() {
|
||||
override fun onSuccess(clouds: List<Cloud>) {
|
||||
clouds.firstOrNull {
|
||||
(it as PCloud).username() == cloud.username()
|
||||
}?.let {
|
||||
it as PCloud
|
||||
succeedAuthenticationWith(
|
||||
PCloud.aCopyOf(it) //
|
||||
.withUrl(cloud.url())
|
||||
.withAccessToken(cloud.accessToken())
|
||||
.build()
|
||||
)
|
||||
} ?: succeedAuthenticationWith(cloud)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private inner class WebDAVAuthStrategy : AuthStrategy {
|
||||
|
||||
override fun supports(cloud: CloudModel): Boolean {
|
||||
return cloud.cloudType() == CloudTypeModel.WEBDAV
|
||||
}
|
||||
|
||||
override fun resumed(intent: AuthenticateCloudIntent) {
|
||||
handleWebDavAuthenticationExceptionIfRequired(intent.cloud() as WebDavCloudModel, intent.error())
|
||||
}
|
||||
|
||||
private fun handleWebDavAuthenticationExceptionIfRequired(cloud: WebDavCloudModel, e: AuthenticationException) {
|
||||
Timber.tag("AuthicateCloudPrester").e(e)
|
||||
when {
|
||||
ExceptionUtil.contains(e, WrongCredentialsException::class.java) -> {
|
||||
failAuthentication(cloud.name())
|
||||
}
|
||||
ExceptionUtil.contains(e, WebDavCertificateUntrustedAuthenticationException::class.java) -> {
|
||||
handleCertificateUntrustedExceptionIfRequired(cloud, e)
|
||||
}
|
||||
ExceptionUtil.contains(e, WebDavServerNotFoundException::class.java) -> {
|
||||
view?.showMessage(R.string.error_server_not_found)
|
||||
finish()
|
||||
}
|
||||
ExceptionUtil.contains(e, WebDavNotSupportedException::class.java) -> {
|
||||
view?.showMessage(R.string.screen_cloud_error_webdav_not_supported)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleCertificateUntrustedExceptionIfRequired(cloud: WebDavCloudModel, e: AuthenticationException) {
|
||||
val untrustedException = ExceptionUtil.extract(e, WebDavCertificateUntrustedAuthenticationException::class.java)
|
||||
try {
|
||||
val certificate = X509CertificateHelper.convertFromPem(untrustedException.get().certificate)
|
||||
view?.showUntrustedCertificateDialog(cloud.toCloud() as WebDavCloud, certificate)
|
||||
} catch (ex: CertificateException) {
|
||||
Timber.tag("AuthicateCloudPrester").e(ex)
|
||||
throw FatalBackendException(ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun onAcceptWebDavCertificateClicked(cloud: WebDavCloud, certificate: X509Certificate) {
|
||||
try {
|
||||
val webDavCloudWithAcceptedCert = WebDavCloud.aCopyOf(cloud) //
|
||||
.withCertificate(X509CertificateHelper.convertToPem(certificate)) //
|
||||
.build()
|
||||
finishWithResultAndExtra(
|
||||
cloudModelMapper.toModel(webDavCloudWithAcceptedCert), //
|
||||
WEBDAV_ACCEPTED_UNTRUSTED_CERTIFICATE, //
|
||||
true
|
||||
)
|
||||
} catch (e: CertificateEncodingException) {
|
||||
Timber.tag("AuthicateCloudPrester").e(e)
|
||||
throw FatalBackendException(e)
|
||||
}
|
||||
}
|
||||
|
||||
fun onAcceptWebDavCertificateDenied() {
|
||||
finish()
|
||||
}
|
||||
|
||||
private inner class S3AuthStrategy : AuthStrategy {
|
||||
|
||||
private var authenticationStarted = false
|
||||
|
||||
override fun supports(cloud: CloudModel): Boolean {
|
||||
return cloud.cloudType() == CloudTypeModel.S3
|
||||
}
|
||||
|
||||
override fun resumed(intent: AuthenticateCloudIntent) {
|
||||
when {
|
||||
ExceptionUtil.contains(intent.error(), WrongCredentialsException::class.java) -> {
|
||||
if (!authenticationStarted) {
|
||||
startAuthentication(intent.cloud())
|
||||
Toast.makeText(
|
||||
context(),
|
||||
String.format(getString(R.string.error_authentication_failed), intent.cloud().username()),
|
||||
Toast.LENGTH_LONG
|
||||
).show()
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
Timber.tag("AuthicateCloudPrester").e(intent.error())
|
||||
failAuthentication(intent.cloud().name())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAuthentication(cloud: CloudModel) {
|
||||
authenticationStarted = true
|
||||
startIntent(Intents.s3AddOrChangeIntent().withS3Cloud(cloud as S3CloudModel))
|
||||
}
|
||||
}
|
||||
|
||||
private inner class LocalStorageAuthStrategy : AuthStrategy {
|
||||
|
||||
private var authenticationStarted = false
|
||||
|
||||
override fun supports(cloud: CloudModel): Boolean {
|
||||
return cloud.cloudType() == CloudTypeModel.LOCAL
|
||||
}
|
||||
|
||||
override fun resumed(intent: AuthenticateCloudIntent) {
|
||||
if (!authenticationStarted) {
|
||||
startAuthentication(intent.cloud())
|
||||
}
|
||||
}
|
||||
|
||||
private fun startAuthentication(cloud: CloudModel) {
|
||||
authenticationStarted = true
|
||||
|
||||
val uri = (cloud as LocalStorageModel).uri()
|
||||
|
||||
val permissions = context().contentResolver.persistedUriPermissions
|
||||
for (permission in permissions) {
|
||||
if (permission.uri.toString() == uri) {
|
||||
succeedAuthenticationWith(cloud.toCloud())
|
||||
}
|
||||
}
|
||||
|
||||
Timber.tag("AuthicateCloudPrester").e("Permission revoked, ask to re-pick location")
|
||||
|
||||
Toast.makeText(context(), getString(R.string.permission_revoked_re_request_permission), Toast.LENGTH_LONG).show()
|
||||
|
||||
val openDocumentTree = Intent(ACTION_OPEN_DOCUMENT_TREE).apply {
|
||||
putExtra(DocumentsContract.EXTRA_INITIAL_URI, uri)
|
||||
}
|
||||
|
||||
requestActivityResult(ActivityResultCallbacks.rePickedLocalStorageLocation(cloud), openDocumentTree)
|
||||
}
|
||||
}
|
||||
|
||||
@Callback
|
||||
fun rePickedLocalStorageLocation(result: ActivityResult, cloud: LocalStorageModel) {
|
||||
val rootTreeUriOfLocalStorage = result.intent().data
|
||||
rootTreeUriOfLocalStorage?.let {
|
||||
context() //
|
||||
.contentResolver //
|
||||
.takePersistableUriPermission( //
|
||||
it, //
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
Timber.tag("AuthicateCloudPrester").e("Permission granted again")
|
||||
succeedAuthenticationWith(cloud.toCloud())
|
||||
}
|
||||
|
||||
private fun encrypt(password: String): String {
|
||||
return CredentialCryptor //
|
||||
.getInstance(context()) //
|
||||
.encrypt(password)
|
||||
}
|
||||
|
||||
private inner class FailingAuthStrategy : AuthStrategy {
|
||||
|
||||
override fun supports(cloud: CloudModel): Boolean {
|
||||
return false
|
||||
}
|
||||
|
||||
override fun resumed(intent: AuthenticateCloudIntent) {
|
||||
view?.showError(R.string.error_authentication_failed)
|
||||
finish()
|
||||
}
|
||||
}
|
||||
|
||||
private interface AuthStrategy {
|
||||
|
||||
fun supports(cloud: CloudModel): Boolean
|
||||
fun resumed(intent: AuthenticateCloudIntent)
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
const val WEBDAV_ACCEPTED_UNTRUSTED_CERTIFICATE = "acceptedUntrustedCertificate"
|
||||
|
||||
fun onedriveScopes(): Array<String> {
|
||||
return arrayOf("User.Read", "Files.ReadWrite")
|
||||
}
|
||||
}
|
||||
|
||||
init {
|
||||
unsubscribeOnDestroy(addOrChangeCloudConnectionUseCase, getCloudsUseCase, getUsernameUseCase)
|
||||
}
|
||||
}
|
@ -51,6 +51,9 @@ class CryptomatorApp : MultiDexApplication(), HasComponent<ApplicationComponent>
|
||||
"fdroid" -> {
|
||||
"F-Droid Edition"
|
||||
}
|
||||
"fdroidmain" -> {
|
||||
"F-Droid Main Repo Edition"
|
||||
}
|
||||
else -> "Google Play Edition"
|
||||
}
|
||||
Timber.tag("App").i(
|
||||
|
@ -6,19 +6,6 @@ import android.content.Intent
|
||||
import android.content.Intent.ACTION_OPEN_DOCUMENT_TREE
|
||||
import android.provider.DocumentsContract
|
||||
import android.widget.Toast
|
||||
import com.dropbox.core.android.Auth
|
||||
import com.google.api.client.googleapis.extensions.android.gms.auth.GoogleAccountCredential
|
||||
import com.google.api.services.drive.DriveScopes
|
||||
import com.microsoft.identity.client.AuthenticationCallback
|
||||
import com.microsoft.identity.client.IAccount
|
||||
import com.microsoft.identity.client.IAuthenticationResult
|
||||
import com.microsoft.identity.client.IMultipleAccountPublicClientApplication
|
||||
import com.microsoft.identity.client.IPublicClientApplication
|
||||
import com.microsoft.identity.client.PublicClientApplication
|
||||
import com.microsoft.identity.client.exception.MsalClientException
|
||||
import com.microsoft.identity.client.exception.MsalException
|
||||
import com.microsoft.identity.client.exception.MsalServiceException
|
||||
import com.microsoft.identity.client.exception.MsalUiRequiredException
|
||||
import org.cryptomator.data.util.X509CertificateHelper
|
||||
import org.cryptomator.domain.Cloud
|
||||
import org.cryptomator.domain.CloudType
|
||||
@ -39,7 +26,6 @@ import org.cryptomator.domain.usecases.cloud.AddOrChangeCloudConnectionUseCase
|
||||
import org.cryptomator.domain.usecases.cloud.GetCloudsUseCase
|
||||
import org.cryptomator.domain.usecases.cloud.GetUsernameUseCase
|
||||
import org.cryptomator.generator.Callback
|
||||
import org.cryptomator.presentation.BuildConfig
|
||||
import org.cryptomator.presentation.R
|
||||
import org.cryptomator.presentation.exception.ExceptionHandlers
|
||||
import org.cryptomator.presentation.intent.AuthenticateCloudIntent
|
||||
@ -175,12 +161,12 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
||||
private fun startAuthentication() {
|
||||
showProgress(ProgressModel(ProgressStateModel.AUTHENTICATION))
|
||||
authenticationStarted = true
|
||||
Auth.startOAuth2Authentication(context(), BuildConfig.DROPBOX_API_KEY)
|
||||
DropboxAuthHelper.startOAuth2Authentication(context())
|
||||
view?.skipTransition()
|
||||
}
|
||||
|
||||
private fun handleAuthenticationResult(cloudModel: CloudModel) {
|
||||
val authToken = Auth.getOAuth2Token()
|
||||
val authToken = DropboxAuthHelper.getOAuth2Token()
|
||||
if (authToken == null) {
|
||||
failAuthentication(cloudModel.name())
|
||||
} else {
|
||||
@ -221,11 +207,10 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
||||
}
|
||||
|
||||
private fun chooseAccount(cloud: CloudModel) {
|
||||
val chooseAccountIntent = GoogleAccountCredential.usingOAuth2(context(), setOf(DriveScopes.DRIVE)).newChooseAccountIntent()
|
||||
try {
|
||||
requestActivityResult( //
|
||||
ActivityResultCallbacks.onGoogleDriveAuthenticated(cloud), //
|
||||
chooseAccountIntent
|
||||
GoogleAuthHelper.getChooseAccountIntent(context())
|
||||
)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
view?.showMessage(R.string.error_play_services_not_available)
|
||||
@ -261,6 +246,7 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
||||
private inner class OnedriveAuthStrategy : AuthStrategy {
|
||||
|
||||
private var authenticationStarted = false
|
||||
|
||||
override fun supports(cloud: CloudModel): Boolean {
|
||||
return cloud.cloudType() == CloudTypeModel.ONEDRIVE
|
||||
}
|
||||
@ -276,107 +262,11 @@ class AuthenticateCloudPresenter @Inject constructor( //
|
||||
|
||||
Toast.makeText(context(), R.string.notification_authenticating, Toast.LENGTH_SHORT).show()
|
||||
|
||||
PublicClientApplication.createMultipleAccountPublicClientApplication(
|
||||
context(),
|
||||
R.raw.auth_config_onedrive,
|
||||
object : IPublicClientApplication.IMultipleAccountApplicationCreatedListener {
|
||||
override fun onCreated(application: IMultipleAccountPublicClientApplication) {
|
||||
application.getAccounts(object : IPublicClientApplication.LoadAccountsCallback {
|
||||
override fun onTaskCompleted(accounts: List<IAccount>) {
|
||||
if (accounts.isEmpty()) {
|
||||
application.acquireToken(activity(), onedriveScopes(), getAuthInteractiveCallback(cloud))
|
||||
} else {
|
||||
accounts.find { account -> account.username == cloud.username() }?.let {
|
||||
application.acquireTokenSilentAsync(
|
||||
onedriveScopes(),
|
||||
it,
|
||||
"https://login.microsoftonline.com/common",
|
||||
getAuthSilentCallback(cloud, application)
|
||||
)
|
||||
} ?: application.acquireToken(activity(), onedriveScopes(), getAuthInteractiveCallback(cloud))
|
||||
}
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Error to get accounts")
|
||||
failAuthentication(cloud.name())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").i(e, "Error in configuration")
|
||||
failAuthentication(cloud.name())
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun getAuthSilentCallback(cloud: CloudModel, application: IMultipleAccountPublicClientApplication): AuthenticationCallback {
|
||||
return object : AuthenticationCallback {
|
||||
|
||||
override fun onSuccess(authenticationResult: IAuthenticationResult) {
|
||||
Timber.tag("AuthenticateCloudPresenter").i("Successfully authenticated")
|
||||
handleAuthenticationResult(cloud, authenticationResult.accessToken)
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Failed to acquireToken")
|
||||
when (e) {
|
||||
is MsalClientException -> {
|
||||
/* Exception inside MSAL, more info inside MsalError.java */
|
||||
failAuthentication(cloud.name())
|
||||
}
|
||||
is MsalServiceException -> {
|
||||
/* Exception when communicating with the STS, likely config issue */
|
||||
failAuthentication(cloud.name())
|
||||
}
|
||||
is MsalUiRequiredException -> {
|
||||
/* Tokens expired or no session, retry with interactive */
|
||||
application.acquireToken(activity(), onedriveScopes(), getAuthInteractiveCallback(cloud))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
Timber.tag("AuthenticateCloudPresenter").i("User cancelled login")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getAuthInteractiveCallback(cloud: CloudModel): AuthenticationCallback {
|
||||
return object : AuthenticationCallback {
|
||||
|
||||
override fun onSuccess(authenticationResult: IAuthenticationResult) {
|
||||
Timber.tag("AuthenticateCloudPresenter").i("Successfully authenticated")
|
||||
handleAuthenticationResult(cloud, authenticationResult.accessToken, authenticationResult.account.username)
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Successfully authenticated")
|
||||
failAuthentication(cloud.name())
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
Timber.tag("AuthenticateCloudPresenter").i("User cancelled login")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun handleAuthenticationResult(cloud: CloudModel, accessToken: String) {
|
||||
getUsernameAndSuceedAuthentication( //
|
||||
OnedriveCloud.aCopyOf(cloud.toCloud() as OnedriveCloud) //
|
||||
.withAccessToken(encrypt(accessToken)) //
|
||||
.build()
|
||||
)
|
||||
}
|
||||
|
||||
private fun handleAuthenticationResult(cloud: CloudModel, accessToken: String, username: String) {
|
||||
getUsernameAndSuceedAuthentication( //
|
||||
OnedriveCloud.aCopyOf(cloud.toCloud() as OnedriveCloud) //
|
||||
.withAccessToken(encrypt(accessToken)) //
|
||||
.withUsername(username)
|
||||
.build()
|
||||
)
|
||||
OnedriveAuthentication.refreshOrCheckAuth(activity(), cloud.toCloud() as OnedriveCloud, { authenticatedCloud ->
|
||||
getUsernameAndSuceedAuthentication(authenticatedCloud)
|
||||
}, {
|
||||
failAuthentication(cloud.name())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -37,6 +37,11 @@ class ChooseCloudServicePresenter @Inject constructor( //
|
||||
|
||||
if (BuildConfig.FLAVOR == "fdroid") {
|
||||
cloudTypeModels.remove(CloudTypeModel.GOOGLE_DRIVE)
|
||||
} else if (BuildConfig.FLAVOR == "fdroidmain") {
|
||||
cloudTypeModels.remove(CloudTypeModel.GOOGLE_DRIVE)
|
||||
cloudTypeModels.remove(CloudTypeModel.DROPBOX)
|
||||
cloudTypeModels.remove(CloudTypeModel.ONEDRIVE)
|
||||
cloudTypeModels.remove(CloudTypeModel.PCLOUD)
|
||||
}
|
||||
|
||||
view?.render(cloudTypeModels)
|
||||
|
@ -4,13 +4,6 @@ import android.content.ActivityNotFoundException
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.widget.Toast
|
||||
import com.microsoft.identity.client.AuthenticationCallback
|
||||
import com.microsoft.identity.client.IAccount
|
||||
import com.microsoft.identity.client.IAuthenticationResult
|
||||
import com.microsoft.identity.client.IMultipleAccountPublicClientApplication
|
||||
import com.microsoft.identity.client.IPublicClientApplication
|
||||
import com.microsoft.identity.client.PublicClientApplication
|
||||
import com.microsoft.identity.client.exception.MsalException
|
||||
import org.cryptomator.domain.Cloud
|
||||
import org.cryptomator.domain.LocalStorageCloud
|
||||
import org.cryptomator.domain.OnedriveCloud
|
||||
@ -137,49 +130,11 @@ class CloudConnectionListPresenter @Inject constructor( //
|
||||
}
|
||||
|
||||
private fun addOnedriveCloud() {
|
||||
PublicClientApplication.createMultipleAccountPublicClientApplication(
|
||||
context(),
|
||||
R.raw.auth_config_onedrive,
|
||||
object : IPublicClientApplication.IMultipleAccountApplicationCreatedListener {
|
||||
override fun onCreated(application: IMultipleAccountPublicClientApplication) {
|
||||
application.getAccounts(object : IPublicClientApplication.LoadAccountsCallback {
|
||||
override fun onTaskCompleted(accounts: List<IAccount>) {
|
||||
application.acquireToken(activity(), AuthenticateCloudPresenter.onedriveScopes(), getAuthInteractiveCallback())
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Error to get accounts")
|
||||
showError(e);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").i(e, "Error in configuration")
|
||||
showError(e);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun getAuthInteractiveCallback(): AuthenticationCallback {
|
||||
return object : AuthenticationCallback {
|
||||
|
||||
override fun onSuccess(authenticationResult: IAuthenticationResult) {
|
||||
Timber.tag("AuthenticateCloudPresenter").i("Successfully authenticated")
|
||||
val accessToken = CredentialCryptor.getInstance(context()).encrypt(authenticationResult.accessToken)
|
||||
val onedriveSkeleton = OnedriveCloud.aOnedriveCloud().withAccessToken(accessToken).withUsername(authenticationResult.account.username).build()
|
||||
saveOnedriveCloud(onedriveSkeleton)
|
||||
}
|
||||
|
||||
override fun onError(e: MsalException) {
|
||||
Timber.tag("AuthenticateCloudPresenter").e(e, "Successfully authenticated")
|
||||
showError(e);
|
||||
}
|
||||
|
||||
override fun onCancel() {
|
||||
Timber.tag("AuthenticateCloudPresenter").i("User cancelled login")
|
||||
}
|
||||
}
|
||||
OnedriveAuthentication.getAuthenticatedOnedriveCloud(activity(), { cloud ->
|
||||
saveOnedriveCloud(cloud)
|
||||
}, { e ->
|
||||
showError(e)
|
||||
})
|
||||
}
|
||||
|
||||
private fun saveOnedriveCloud(onedriveSkeleton: OnedriveCloud) {
|
||||
|
@ -140,9 +140,28 @@ class CloudSettingsPresenter @Inject constructor( //
|
||||
it.add(aS3Cloud())
|
||||
it.add(aLocalCloud())
|
||||
}
|
||||
.filter { cloud -> !(BuildConfig.FLAVOR == "fdroidmain" && excludeApiCloudsInFdroidMain(cloud.cloudType())) } //
|
||||
view?.render(cloudModel)
|
||||
}
|
||||
|
||||
private fun excludeApiCloudsInFdroidMain(cloudType: CloudTypeModel): Boolean {
|
||||
return when (cloudType) {
|
||||
CloudTypeModel.GOOGLE_DRIVE -> {
|
||||
true
|
||||
}
|
||||
CloudTypeModel.ONEDRIVE -> {
|
||||
true
|
||||
}
|
||||
CloudTypeModel.DROPBOX -> {
|
||||
true
|
||||
}
|
||||
CloudTypeModel.PCLOUD -> {
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
|
||||
private fun aOnedriveCloud(): OnedriveCloudModel {
|
||||
return OnedriveCloudModel(OnedriveCloud.aOnedriveCloud().build())
|
||||
}
|
||||
|
@ -83,6 +83,9 @@ class SettingsPresenter @Inject internal constructor(
|
||||
"fdroid" -> {
|
||||
"F-Droid"
|
||||
}
|
||||
"fdroidmain" -> {
|
||||
"F-Droid Main Repo Edition"
|
||||
}
|
||||
else -> "Google Play"
|
||||
}
|
||||
return StringBuilder().append("## ").append(context().getString(R.string.error_report_subject)).append("\n\n") //
|
||||
|
@ -123,7 +123,7 @@ class VaultListPresenter @Inject constructor( //
|
||||
}
|
||||
|
||||
private fun checkLicense() {
|
||||
if (BuildConfig.FLAVOR == "apkstore" || BuildConfig.FLAVOR == "fdroid") {
|
||||
if (BuildConfig.FLAVOR == "apkstore" || BuildConfig.FLAVOR == "fdroid" || BuildConfig.FLAVOR == "fdroidmain") {
|
||||
licenseCheckUseCase //
|
||||
.withLicense("") //
|
||||
.run(object : NoOpResultHandler<LicenseCheck>() {
|
||||
|
@ -157,7 +157,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
|
||||
(findPreference(SharedPreferencesHandler.MAIL) as Preference?)?.title = format(getString(R.string.screen_settings_license_mail), sharedPreferencesHandler.mail())
|
||||
setupUpdateCheck()
|
||||
}
|
||||
"fdroid" -> {
|
||||
"fdroid", "fdroidmain" -> {
|
||||
(findPreference(SharedPreferencesHandler.MAIL) as Preference?)?.title = format(getString(R.string.screen_settings_license_mail), sharedPreferencesHandler.mail())
|
||||
removeUpdateCheck()
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user