Preparations for F-Droid main repo

This commit is contained in:
Julian Raufelder 2022-05-17 00:24:18 +02:00
parent 33dfaed9d7
commit 7ad0178e8b
No known key found for this signature in database
GPG Key ID: 17EE71F6634E381D
63 changed files with 346 additions and 796 deletions

View File

@ -53,19 +53,27 @@ android {
fdroid { fdroid {
dimension "version" dimension "version"
} }
fdroidmain {
dimension "version"
}
} }
sourceSets { sourceSets {
playstore { 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 { 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 { 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 { packagingOptions {
@ -95,7 +103,9 @@ dependencies {
implementation project(':domain') implementation project(':domain')
implementation project(':util') 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 coreLibraryDesugaring dependencies.coreDesugaring
@ -113,9 +123,16 @@ dependencies {
implementation dependencies.jsonWebTokenJson implementation dependencies.jsonWebTokenJson
// cloud // cloud
implementation dependencies.dropbox playstoreImplementation dependencies.dropbox
implementation dependencies.msgraphAuth apkstoreImplementation dependencies.dropbox
implementation dependencies.msgraph fdroidImplementation dependencies.dropbox
playstoreImplementation dependencies.msgraphAuth
apkstoreImplementation dependencies.msgraphAuth
fdroidImplementation dependencies.msgraphAuth
playstoreImplementation dependencies.msgraph
apkstoreImplementation dependencies.msgraph
fdroidImplementation dependencies.msgraph
implementation dependencies.stax implementation dependencies.stax
api dependencies.minIo api dependencies.minIo

View File

@ -1,5 +1,7 @@
package org.cryptomator.data.cloud; package org.cryptomator.data.cloud;
import static java.util.Arrays.asList;
import org.cryptomator.data.cloud.crypto.CryptoCloudContentRepositoryFactory; import org.cryptomator.data.cloud.crypto.CryptoCloudContentRepositoryFactory;
import org.cryptomator.data.cloud.dropbox.DropboxCloudContentRepositoryFactory; import org.cryptomator.data.cloud.dropbox.DropboxCloudContentRepositoryFactory;
import org.cryptomator.data.cloud.googledrive.GoogleDriveCloudContentRepositoryFactory; import org.cryptomator.data.cloud.googledrive.GoogleDriveCloudContentRepositoryFactory;
@ -16,8 +18,6 @@ import java.util.Iterator;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Singleton; import javax.inject.Singleton;
import static java.util.Arrays.asList;
@Singleton @Singleton
public class CloudContentRepositoryFactories implements Iterable<CloudContentRepositoryFactory> { public class CloudContentRepositoryFactories implements Iterable<CloudContentRepositoryFactory> {

View File

@ -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();
}
}

View File

@ -88,19 +88,27 @@ android {
fdroid { fdroid {
dimension "version" dimension "version"
} }
fdroidmain {
dimension "version"
}
} }
sourceSets { sourceSets {
playstore { 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 { 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 { 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 { packagingOptions {
@ -147,9 +155,16 @@ dependencies {
implementation dependencies.androidxBiometric implementation dependencies.androidxBiometric
// cloud // cloud
implementation dependencies.dropbox playstoreImplementation dependencies.dropbox
implementation dependencies.msgraph apkstoreImplementation dependencies.dropbox
implementation dependencies.msgraphAuth fdroidImplementation dependencies.dropbox
playstoreImplementation dependencies.msgraphAuth
apkstoreImplementation dependencies.msgraphAuth
fdroidImplementation dependencies.msgraphAuth
playstoreImplementation dependencies.msgraph
apkstoreImplementation dependencies.msgraph
fdroidImplementation dependencies.msgraph
playstoreImplementation(dependencies.googleApiServicesDrive) { playstoreImplementation(dependencies.googleApiServicesDrive) {
exclude module: 'guava-jdk5' exclude module: 'guava-jdk5'

View File

@ -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()
}
}

View File

@ -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")
}
}
}
}

View File

@ -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()
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -51,6 +51,9 @@ class CryptomatorApp : MultiDexApplication(), HasComponent<ApplicationComponent>
"fdroid" -> { "fdroid" -> {
"F-Droid Edition" "F-Droid Edition"
} }
"fdroidmain" -> {
"F-Droid Main Repo Edition"
}
else -> "Google Play Edition" else -> "Google Play Edition"
} }
Timber.tag("App").i( Timber.tag("App").i(

View File

@ -6,19 +6,6 @@ import android.content.Intent
import android.content.Intent.ACTION_OPEN_DOCUMENT_TREE import android.content.Intent.ACTION_OPEN_DOCUMENT_TREE
import android.provider.DocumentsContract import android.provider.DocumentsContract
import android.widget.Toast 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.data.util.X509CertificateHelper
import org.cryptomator.domain.Cloud import org.cryptomator.domain.Cloud
import org.cryptomator.domain.CloudType 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.GetCloudsUseCase
import org.cryptomator.domain.usecases.cloud.GetUsernameUseCase import org.cryptomator.domain.usecases.cloud.GetUsernameUseCase
import org.cryptomator.generator.Callback import org.cryptomator.generator.Callback
import org.cryptomator.presentation.BuildConfig
import org.cryptomator.presentation.R import org.cryptomator.presentation.R
import org.cryptomator.presentation.exception.ExceptionHandlers import org.cryptomator.presentation.exception.ExceptionHandlers
import org.cryptomator.presentation.intent.AuthenticateCloudIntent import org.cryptomator.presentation.intent.AuthenticateCloudIntent
@ -175,12 +161,12 @@ class AuthenticateCloudPresenter @Inject constructor( //
private fun startAuthentication() { private fun startAuthentication() {
showProgress(ProgressModel(ProgressStateModel.AUTHENTICATION)) showProgress(ProgressModel(ProgressStateModel.AUTHENTICATION))
authenticationStarted = true authenticationStarted = true
Auth.startOAuth2Authentication(context(), BuildConfig.DROPBOX_API_KEY) DropboxAuthHelper.startOAuth2Authentication(context())
view?.skipTransition() view?.skipTransition()
} }
private fun handleAuthenticationResult(cloudModel: CloudModel) { private fun handleAuthenticationResult(cloudModel: CloudModel) {
val authToken = Auth.getOAuth2Token() val authToken = DropboxAuthHelper.getOAuth2Token()
if (authToken == null) { if (authToken == null) {
failAuthentication(cloudModel.name()) failAuthentication(cloudModel.name())
} else { } else {
@ -221,11 +207,10 @@ class AuthenticateCloudPresenter @Inject constructor( //
} }
private fun chooseAccount(cloud: CloudModel) { private fun chooseAccount(cloud: CloudModel) {
val chooseAccountIntent = GoogleAccountCredential.usingOAuth2(context(), setOf(DriveScopes.DRIVE)).newChooseAccountIntent()
try { try {
requestActivityResult( // requestActivityResult( //
ActivityResultCallbacks.onGoogleDriveAuthenticated(cloud), // ActivityResultCallbacks.onGoogleDriveAuthenticated(cloud), //
chooseAccountIntent GoogleAuthHelper.getChooseAccountIntent(context())
) )
} catch (e: ActivityNotFoundException) { } catch (e: ActivityNotFoundException) {
view?.showMessage(R.string.error_play_services_not_available) view?.showMessage(R.string.error_play_services_not_available)
@ -261,6 +246,7 @@ class AuthenticateCloudPresenter @Inject constructor( //
private inner class OnedriveAuthStrategy : AuthStrategy { private inner class OnedriveAuthStrategy : AuthStrategy {
private var authenticationStarted = false private var authenticationStarted = false
override fun supports(cloud: CloudModel): Boolean { override fun supports(cloud: CloudModel): Boolean {
return cloud.cloudType() == CloudTypeModel.ONEDRIVE return cloud.cloudType() == CloudTypeModel.ONEDRIVE
} }
@ -276,108 +262,12 @@ class AuthenticateCloudPresenter @Inject constructor( //
Toast.makeText(context(), R.string.notification_authenticating, Toast.LENGTH_SHORT).show() Toast.makeText(context(), R.string.notification_authenticating, Toast.LENGTH_SHORT).show()
PublicClientApplication.createMultipleAccountPublicClientApplication( OnedriveAuthentication.refreshOrCheckAuth(activity(), cloud.toCloud() as OnedriveCloud, { authenticatedCloud ->
context(), getUsernameAndSuceedAuthentication(authenticatedCloud)
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()) 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 inner class PCloudAuthStrategy : AuthStrategy {

View File

@ -37,6 +37,11 @@ class ChooseCloudServicePresenter @Inject constructor( //
if (BuildConfig.FLAVOR == "fdroid") { if (BuildConfig.FLAVOR == "fdroid") {
cloudTypeModels.remove(CloudTypeModel.GOOGLE_DRIVE) 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) view?.render(cloudTypeModels)

View File

@ -4,13 +4,6 @@ import android.content.ActivityNotFoundException
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.widget.Toast 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.Cloud
import org.cryptomator.domain.LocalStorageCloud import org.cryptomator.domain.LocalStorageCloud
import org.cryptomator.domain.OnedriveCloud import org.cryptomator.domain.OnedriveCloud
@ -137,51 +130,13 @@ class CloudConnectionListPresenter @Inject constructor( //
} }
private fun addOnedriveCloud() { private fun addOnedriveCloud() {
PublicClientApplication.createMultipleAccountPublicClientApplication( OnedriveAuthentication.getAuthenticatedOnedriveCloud(activity(), { cloud ->
context(), saveOnedriveCloud(cloud)
R.raw.auth_config_onedrive, }, { e ->
object : IPublicClientApplication.IMultipleAccountApplicationCreatedListener { showError(e)
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")
}
}
}
private fun saveOnedriveCloud(onedriveSkeleton: OnedriveCloud) { private fun saveOnedriveCloud(onedriveSkeleton: OnedriveCloud) {
getUsernameUseCase // getUsernameUseCase //
.withCloud(onedriveSkeleton) // .withCloud(onedriveSkeleton) //

View File

@ -140,9 +140,28 @@ class CloudSettingsPresenter @Inject constructor( //
it.add(aS3Cloud()) it.add(aS3Cloud())
it.add(aLocalCloud()) it.add(aLocalCloud())
} }
.filter { cloud -> !(BuildConfig.FLAVOR == "fdroidmain" && excludeApiCloudsInFdroidMain(cloud.cloudType())) } //
view?.render(cloudModel) 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 { private fun aOnedriveCloud(): OnedriveCloudModel {
return OnedriveCloudModel(OnedriveCloud.aOnedriveCloud().build()) return OnedriveCloudModel(OnedriveCloud.aOnedriveCloud().build())
} }

View File

@ -83,6 +83,9 @@ class SettingsPresenter @Inject internal constructor(
"fdroid" -> { "fdroid" -> {
"F-Droid" "F-Droid"
} }
"fdroidmain" -> {
"F-Droid Main Repo Edition"
}
else -> "Google Play" else -> "Google Play"
} }
return StringBuilder().append("## ").append(context().getString(R.string.error_report_subject)).append("\n\n") // return StringBuilder().append("## ").append(context().getString(R.string.error_report_subject)).append("\n\n") //

View File

@ -123,7 +123,7 @@ class VaultListPresenter @Inject constructor( //
} }
private fun checkLicense() { private fun checkLicense() {
if (BuildConfig.FLAVOR == "apkstore" || BuildConfig.FLAVOR == "fdroid") { if (BuildConfig.FLAVOR == "apkstore" || BuildConfig.FLAVOR == "fdroid" || BuildConfig.FLAVOR == "fdroidmain") {
licenseCheckUseCase // licenseCheckUseCase //
.withLicense("") // .withLicense("") //
.run(object : NoOpResultHandler<LicenseCheck>() { .run(object : NoOpResultHandler<LicenseCheck>() {

View File

@ -157,7 +157,7 @@ class SettingsFragment : PreferenceFragmentCompat() {
(findPreference(SharedPreferencesHandler.MAIL) as Preference?)?.title = format(getString(R.string.screen_settings_license_mail), sharedPreferencesHandler.mail()) (findPreference(SharedPreferencesHandler.MAIL) as Preference?)?.title = format(getString(R.string.screen_settings_license_mail), sharedPreferencesHandler.mail())
setupUpdateCheck() setupUpdateCheck()
} }
"fdroid" -> { "fdroid", "fdroidmain" -> {
(findPreference(SharedPreferencesHandler.MAIL) as Preference?)?.title = format(getString(R.string.screen_settings_license_mail), sharedPreferencesHandler.mail()) (findPreference(SharedPreferencesHandler.MAIL) as Preference?)?.title = format(getString(R.string.screen_settings_license_mail), sharedPreferencesHandler.mail())
removeUpdateCheck() removeUpdateCheck()
} }